@backtest-kit/cli 5.11.0 → 5.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/build/index.cjs +106 -18
- package/build/index.mjs +108 -20
- package/package.json +1 -1
- package/template/project/CLAUDE.md +158 -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,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
548
553
|
type: "boolean",
|
|
549
554
|
default: false,
|
|
550
555
|
},
|
|
556
|
+
init: {
|
|
557
|
+
type: "boolean",
|
|
558
|
+
default: false,
|
|
559
|
+
},
|
|
551
560
|
},
|
|
552
561
|
strict: false,
|
|
553
562
|
allowPositionals: true,
|
|
@@ -559,7 +568,9 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
559
568
|
});
|
|
560
569
|
const getPositional = functoolsKit.singleshot(() => {
|
|
561
570
|
const { positionals = [] } = getArgs();
|
|
562
|
-
const result = positionals
|
|
571
|
+
const result = positionals
|
|
572
|
+
.filter((value) => !DISALLOWED_PATHS.some((path) => value.includes(path)))
|
|
573
|
+
.find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
|
|
563
574
|
return result || null;
|
|
564
575
|
});
|
|
565
576
|
|
|
@@ -2220,7 +2231,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
2220
2231
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
2221
2232
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2222
2233
|
});
|
|
2223
|
-
const main$
|
|
2234
|
+
const main$7 = async () => {
|
|
2224
2235
|
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
2236
|
return;
|
|
2226
2237
|
}
|
|
@@ -2231,7 +2242,7 @@ const main$6 = async () => {
|
|
|
2231
2242
|
await cli.backtestMainService.connect();
|
|
2232
2243
|
listenGracefulShutdown$4();
|
|
2233
2244
|
};
|
|
2234
|
-
main$
|
|
2245
|
+
main$7();
|
|
2235
2246
|
|
|
2236
2247
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
2237
2248
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2252,7 +2263,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
2252
2263
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
2253
2264
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2254
2265
|
});
|
|
2255
|
-
const main$
|
|
2266
|
+
const main$6 = async () => {
|
|
2256
2267
|
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
2268
|
return;
|
|
2258
2269
|
}
|
|
@@ -2263,7 +2274,7 @@ const main$5 = async () => {
|
|
|
2263
2274
|
cli.paperMainService.connect();
|
|
2264
2275
|
listenGracefulShutdown$3();
|
|
2265
2276
|
};
|
|
2266
|
-
main$
|
|
2277
|
+
main$6();
|
|
2267
2278
|
|
|
2268
2279
|
const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
2269
2280
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2284,7 +2295,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
2284
2295
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
2285
2296
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2286
2297
|
});
|
|
2287
|
-
const main$
|
|
2298
|
+
const main$5 = async () => {
|
|
2288
2299
|
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
2300
|
return;
|
|
2290
2301
|
}
|
|
@@ -2295,7 +2306,7 @@ const main$4 = async () => {
|
|
|
2295
2306
|
await cli.liveMainService.connect();
|
|
2296
2307
|
listenGracefulShutdown$2();
|
|
2297
2308
|
};
|
|
2298
|
-
main$
|
|
2309
|
+
main$5();
|
|
2299
2310
|
|
|
2300
2311
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
2301
2312
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2305,7 +2316,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
|
2305
2316
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
2306
2317
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2307
2318
|
});
|
|
2308
|
-
const main$
|
|
2319
|
+
const main$4 = async () => {
|
|
2309
2320
|
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
2321
|
return;
|
|
2311
2322
|
}
|
|
@@ -2315,7 +2326,7 @@ const main$3 = async () => {
|
|
|
2315
2326
|
}
|
|
2316
2327
|
listenGracefulShutdown$1();
|
|
2317
2328
|
};
|
|
2318
|
-
main$
|
|
2329
|
+
main$4();
|
|
2319
2330
|
|
|
2320
2331
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
2321
2332
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2325,7 +2336,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
|
2325
2336
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
2326
2337
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2327
2338
|
});
|
|
2328
|
-
const main$
|
|
2339
|
+
const main$3 = async () => {
|
|
2329
2340
|
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
2341
|
return;
|
|
2331
2342
|
}
|
|
@@ -2335,7 +2346,7 @@ const main$2 = async () => {
|
|
|
2335
2346
|
}
|
|
2336
2347
|
listenGracefulShutdown();
|
|
2337
2348
|
};
|
|
2338
|
-
main$
|
|
2349
|
+
main$3();
|
|
2339
2350
|
|
|
2340
2351
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2341
2352
|
const keys = Object.keys(schema);
|
|
@@ -2357,7 +2368,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2357
2368
|
}
|
|
2358
2369
|
return rows;
|
|
2359
2370
|
};
|
|
2360
|
-
const main$
|
|
2371
|
+
const main$2 = async () => {
|
|
2361
2372
|
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
2373
|
return;
|
|
2363
2374
|
}
|
|
@@ -2429,9 +2440,9 @@ const main$1 = async () => {
|
|
|
2429
2440
|
}
|
|
2430
2441
|
console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
|
|
2431
2442
|
};
|
|
2432
|
-
main$
|
|
2443
|
+
main$2();
|
|
2433
2444
|
|
|
2434
|
-
const main = async () => {
|
|
2445
|
+
const main$1 = async () => {
|
|
2435
2446
|
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
2447
|
return;
|
|
2437
2448
|
}
|
|
@@ -2476,6 +2487,83 @@ const main = async () => {
|
|
|
2476
2487
|
}
|
|
2477
2488
|
console.log(JSON.stringify(candles, null, 2));
|
|
2478
2489
|
};
|
|
2490
|
+
main$1();
|
|
2491
|
+
|
|
2492
|
+
const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
2493
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
2494
|
+
const MUSTACHE_EXT = ".mustache";
|
|
2495
|
+
async function isDirEmpty(dirPath) {
|
|
2496
|
+
try {
|
|
2497
|
+
const files = await fs$1.readdir(dirPath);
|
|
2498
|
+
return files.length === 0;
|
|
2499
|
+
}
|
|
2500
|
+
catch (error) {
|
|
2501
|
+
if (error.code === "ENOENT") {
|
|
2502
|
+
return true;
|
|
2503
|
+
}
|
|
2504
|
+
throw error;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
async function copyDir(srcDir, destDir, data) {
|
|
2508
|
+
await fs$1.mkdir(destDir, { recursive: true });
|
|
2509
|
+
const entries = await fs$1.readdir(srcDir, { withFileTypes: true });
|
|
2510
|
+
for (const entry of entries) {
|
|
2511
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
2512
|
+
if (entry.isDirectory()) {
|
|
2513
|
+
await copyDir(srcPath, path.join(destDir, entry.name), data);
|
|
2514
|
+
continue;
|
|
2515
|
+
}
|
|
2516
|
+
if (entry.name.endsWith(MUSTACHE_EXT)) {
|
|
2517
|
+
const destName = entry.name.slice(0, -MUSTACHE_EXT.length);
|
|
2518
|
+
const destPath = path.join(destDir, destName);
|
|
2519
|
+
const template = await fs$1.readFile(srcPath, "utf-8");
|
|
2520
|
+
const rendered = Mustache.render(template, data);
|
|
2521
|
+
await fs$1.writeFile(destPath, rendered, "utf-8");
|
|
2522
|
+
console.log(` -> ${destPath}`);
|
|
2523
|
+
}
|
|
2524
|
+
else {
|
|
2525
|
+
const destPath = path.join(destDir, entry.name);
|
|
2526
|
+
await fs$1.copyFile(srcPath, destPath);
|
|
2527
|
+
console.log(` -> ${destPath}`);
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
function runScript(scriptPath, cwd) {
|
|
2532
|
+
return new Promise((resolve, reject) => {
|
|
2533
|
+
const node = process.execPath;
|
|
2534
|
+
const child = child_process.spawn(node, [scriptPath], { cwd, stdio: "inherit" });
|
|
2535
|
+
child.on("close", (code) => {
|
|
2536
|
+
if (code !== 0) {
|
|
2537
|
+
reject(new Error(`Script exited with code ${code}`));
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
resolve();
|
|
2541
|
+
});
|
|
2542
|
+
child.on("error", reject);
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
const main = async () => {
|
|
2546
|
+
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
2547
|
+
return;
|
|
2548
|
+
}
|
|
2549
|
+
const { values } = getArgs();
|
|
2550
|
+
if (!values.init) {
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
const projectName = values.output || "backtest-kit-project";
|
|
2554
|
+
const projectPath = path.join(process.cwd(), projectName);
|
|
2555
|
+
const templatePath = path.join(__dirname$1, "../../template/project");
|
|
2556
|
+
const isEmpty = await isDirEmpty(projectPath);
|
|
2557
|
+
if (!isEmpty) {
|
|
2558
|
+
console.error(`Directory "${projectName}" already exists and is not empty.`);
|
|
2559
|
+
process.exit(1);
|
|
2560
|
+
}
|
|
2561
|
+
console.log(`Creating project in ${projectPath}`);
|
|
2562
|
+
await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
|
|
2563
|
+
console.log(`Fetching docs...`);
|
|
2564
|
+
await runScript(path.join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
|
|
2565
|
+
console.log(`Done! Project created at ${projectPath}`);
|
|
2566
|
+
};
|
|
2479
2567
|
main();
|
|
2480
2568
|
|
|
2481
2569
|
function setLogger(logger) {
|
package/build/index.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { Storage, Notification, Markdown, Report, Dump, Memory, StorageLive, Sto
|
|
|
4
4
|
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString, createAwaiter, 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, { basename, extname, join, resolve } from 'path';
|
|
8
|
-
import fs$1, { access, readFile, mkdir, writeFile } from 'fs/promises';
|
|
7
|
+
import path, { basename, extname, join, resolve, dirname } from 'path';
|
|
8
|
+
import fs$1, { access, readFile, mkdir, writeFile, 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 { spawn } from 'child_process';
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Fix for `Attempted to assign to readonly property (at redactToken)`
|
|
@@ -259,15 +260,15 @@ const TYPES = {
|
|
|
259
260
|
|
|
260
261
|
const entrySubject = new BehaviorSubject();
|
|
261
262
|
|
|
262
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
263
|
-
const __dirname = path.dirname(__filename);
|
|
263
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
264
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
264
265
|
let _is_launched = false;
|
|
265
266
|
class ResolveService {
|
|
266
267
|
constructor() {
|
|
267
268
|
this.loggerService = inject(TYPES.loggerService);
|
|
268
269
|
this.loaderService = inject(TYPES.loaderService);
|
|
269
|
-
this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname, '..', 'template');
|
|
270
|
-
this.DEFAULT_MODULES_DIR = path.resolve(__dirname, '..', 'modules');
|
|
270
|
+
this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$1, '..', 'template');
|
|
271
|
+
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$1, '..', 'modules');
|
|
271
272
|
this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
|
|
272
273
|
this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
|
|
273
274
|
this.getIsLaunched = () => {
|
|
@@ -429,6 +430,10 @@ const ALLOWED_EXTENSIONS = [
|
|
|
429
430
|
`.js`,
|
|
430
431
|
`.pine`,
|
|
431
432
|
];
|
|
433
|
+
const DISALLOWED_PATHS = [
|
|
434
|
+
"node_modules",
|
|
435
|
+
"@backtest-kit",
|
|
436
|
+
];
|
|
432
437
|
const getArgs = singleshot(() => {
|
|
433
438
|
const { values, positionals } = parseArgs({
|
|
434
439
|
args: process.argv,
|
|
@@ -523,6 +528,10 @@ const getArgs = singleshot(() => {
|
|
|
523
528
|
type: "boolean",
|
|
524
529
|
default: false,
|
|
525
530
|
},
|
|
531
|
+
init: {
|
|
532
|
+
type: "boolean",
|
|
533
|
+
default: false,
|
|
534
|
+
},
|
|
526
535
|
},
|
|
527
536
|
strict: false,
|
|
528
537
|
allowPositionals: true,
|
|
@@ -534,7 +543,9 @@ const getArgs = singleshot(() => {
|
|
|
534
543
|
});
|
|
535
544
|
const getPositional = singleshot(() => {
|
|
536
545
|
const { positionals = [] } = getArgs();
|
|
537
|
-
const result = positionals
|
|
546
|
+
const result = positionals
|
|
547
|
+
.filter((value) => !DISALLOWED_PATHS.some((path) => value.includes(path)))
|
|
548
|
+
.find((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
|
|
538
549
|
return result || null;
|
|
539
550
|
});
|
|
540
551
|
|
|
@@ -2191,7 +2202,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
2191
2202
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
2192
2203
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2193
2204
|
});
|
|
2194
|
-
const main$
|
|
2205
|
+
const main$7 = async () => {
|
|
2195
2206
|
if (!getEntry(import.meta.url)) {
|
|
2196
2207
|
return;
|
|
2197
2208
|
}
|
|
@@ -2202,7 +2213,7 @@ const main$6 = async () => {
|
|
|
2202
2213
|
await cli.backtestMainService.connect();
|
|
2203
2214
|
listenGracefulShutdown$4();
|
|
2204
2215
|
};
|
|
2205
|
-
main$
|
|
2216
|
+
main$7();
|
|
2206
2217
|
|
|
2207
2218
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
2208
2219
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -2223,7 +2234,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
2223
2234
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
2224
2235
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
2225
2236
|
});
|
|
2226
|
-
const main$
|
|
2237
|
+
const main$6 = async () => {
|
|
2227
2238
|
if (!getEntry(import.meta.url)) {
|
|
2228
2239
|
return;
|
|
2229
2240
|
}
|
|
@@ -2234,7 +2245,7 @@ const main$5 = async () => {
|
|
|
2234
2245
|
cli.paperMainService.connect();
|
|
2235
2246
|
listenGracefulShutdown$3();
|
|
2236
2247
|
};
|
|
2237
|
-
main$
|
|
2248
|
+
main$6();
|
|
2238
2249
|
|
|
2239
2250
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
2240
2251
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -2255,7 +2266,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
2255
2266
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
2256
2267
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
2257
2268
|
});
|
|
2258
|
-
const main$
|
|
2269
|
+
const main$5 = async () => {
|
|
2259
2270
|
if (!getEntry(import.meta.url)) {
|
|
2260
2271
|
return;
|
|
2261
2272
|
}
|
|
@@ -2266,7 +2277,7 @@ const main$4 = async () => {
|
|
|
2266
2277
|
await cli.liveMainService.connect();
|
|
2267
2278
|
listenGracefulShutdown$2();
|
|
2268
2279
|
};
|
|
2269
|
-
main$
|
|
2280
|
+
main$5();
|
|
2270
2281
|
|
|
2271
2282
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
2272
2283
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -2276,7 +2287,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
2276
2287
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
2277
2288
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
2278
2289
|
});
|
|
2279
|
-
const main$
|
|
2290
|
+
const main$4 = async () => {
|
|
2280
2291
|
if (!getEntry(import.meta.url)) {
|
|
2281
2292
|
return;
|
|
2282
2293
|
}
|
|
@@ -2286,7 +2297,7 @@ const main$3 = async () => {
|
|
|
2286
2297
|
}
|
|
2287
2298
|
listenGracefulShutdown$1();
|
|
2288
2299
|
};
|
|
2289
|
-
main$
|
|
2300
|
+
main$4();
|
|
2290
2301
|
|
|
2291
2302
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
2292
2303
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -2296,7 +2307,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
2296
2307
|
const listenGracefulShutdown = singleshot(() => {
|
|
2297
2308
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
2298
2309
|
});
|
|
2299
|
-
const main$
|
|
2310
|
+
const main$3 = async () => {
|
|
2300
2311
|
if (!getEntry(import.meta.url)) {
|
|
2301
2312
|
return;
|
|
2302
2313
|
}
|
|
@@ -2306,7 +2317,7 @@ const main$2 = async () => {
|
|
|
2306
2317
|
}
|
|
2307
2318
|
listenGracefulShutdown();
|
|
2308
2319
|
};
|
|
2309
|
-
main$
|
|
2320
|
+
main$3();
|
|
2310
2321
|
|
|
2311
2322
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
2312
2323
|
const keys = Object.keys(schema);
|
|
@@ -2328,7 +2339,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
2328
2339
|
}
|
|
2329
2340
|
return rows;
|
|
2330
2341
|
};
|
|
2331
|
-
const main$
|
|
2342
|
+
const main$2 = async () => {
|
|
2332
2343
|
if (!getEntry(import.meta.url)) {
|
|
2333
2344
|
return;
|
|
2334
2345
|
}
|
|
@@ -2400,9 +2411,9 @@ const main$1 = async () => {
|
|
|
2400
2411
|
}
|
|
2401
2412
|
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
2402
2413
|
};
|
|
2403
|
-
main$
|
|
2414
|
+
main$2();
|
|
2404
2415
|
|
|
2405
|
-
const main = async () => {
|
|
2416
|
+
const main$1 = async () => {
|
|
2406
2417
|
if (!getEntry(import.meta.url)) {
|
|
2407
2418
|
return;
|
|
2408
2419
|
}
|
|
@@ -2447,6 +2458,83 @@ const main = async () => {
|
|
|
2447
2458
|
}
|
|
2448
2459
|
console.log(JSON.stringify(candles, null, 2));
|
|
2449
2460
|
};
|
|
2461
|
+
main$1();
|
|
2462
|
+
|
|
2463
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
2464
|
+
const __dirname = dirname(__filename);
|
|
2465
|
+
const MUSTACHE_EXT = ".mustache";
|
|
2466
|
+
async function isDirEmpty(dirPath) {
|
|
2467
|
+
try {
|
|
2468
|
+
const files = await readdir(dirPath);
|
|
2469
|
+
return files.length === 0;
|
|
2470
|
+
}
|
|
2471
|
+
catch (error) {
|
|
2472
|
+
if (error.code === "ENOENT") {
|
|
2473
|
+
return true;
|
|
2474
|
+
}
|
|
2475
|
+
throw error;
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
async function copyDir(srcDir, destDir, data) {
|
|
2479
|
+
await mkdir(destDir, { recursive: true });
|
|
2480
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
2481
|
+
for (const entry of entries) {
|
|
2482
|
+
const srcPath = join(srcDir, entry.name);
|
|
2483
|
+
if (entry.isDirectory()) {
|
|
2484
|
+
await copyDir(srcPath, join(destDir, entry.name), data);
|
|
2485
|
+
continue;
|
|
2486
|
+
}
|
|
2487
|
+
if (entry.name.endsWith(MUSTACHE_EXT)) {
|
|
2488
|
+
const destName = entry.name.slice(0, -MUSTACHE_EXT.length);
|
|
2489
|
+
const destPath = join(destDir, destName);
|
|
2490
|
+
const template = await readFile(srcPath, "utf-8");
|
|
2491
|
+
const rendered = Mustache.render(template, data);
|
|
2492
|
+
await writeFile(destPath, rendered, "utf-8");
|
|
2493
|
+
console.log(` -> ${destPath}`);
|
|
2494
|
+
}
|
|
2495
|
+
else {
|
|
2496
|
+
const destPath = join(destDir, entry.name);
|
|
2497
|
+
await copyFile(srcPath, destPath);
|
|
2498
|
+
console.log(` -> ${destPath}`);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
function runScript(scriptPath, cwd) {
|
|
2503
|
+
return new Promise((resolve, reject) => {
|
|
2504
|
+
const node = process.execPath;
|
|
2505
|
+
const child = spawn(node, [scriptPath], { cwd, stdio: "inherit" });
|
|
2506
|
+
child.on("close", (code) => {
|
|
2507
|
+
if (code !== 0) {
|
|
2508
|
+
reject(new Error(`Script exited with code ${code}`));
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
resolve();
|
|
2512
|
+
});
|
|
2513
|
+
child.on("error", reject);
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2516
|
+
const main = async () => {
|
|
2517
|
+
if (!getEntry(import.meta.url)) {
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
const { values } = getArgs();
|
|
2521
|
+
if (!values.init) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const projectName = values.output || "backtest-kit-project";
|
|
2525
|
+
const projectPath = join(process.cwd(), projectName);
|
|
2526
|
+
const templatePath = join(__dirname, "../../template/project");
|
|
2527
|
+
const isEmpty = await isDirEmpty(projectPath);
|
|
2528
|
+
if (!isEmpty) {
|
|
2529
|
+
console.error(`Directory "${projectName}" already exists and is not empty.`);
|
|
2530
|
+
process.exit(1);
|
|
2531
|
+
}
|
|
2532
|
+
console.log(`Creating project in ${projectPath}`);
|
|
2533
|
+
await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
|
|
2534
|
+
console.log(`Fetching docs...`);
|
|
2535
|
+
await runScript(join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
|
|
2536
|
+
console.log(`Done! Project created at ${projectPath}`);
|
|
2537
|
+
};
|
|
2450
2538
|
main();
|
|
2451
2539
|
|
|
2452
2540
|
function setLogger(logger) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "5.11.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications — no setup code required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|