@backtest-kit/cli 12.4.0 → 12.6.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 +1888 -1854
- package/build/index.cjs +413 -227
- package/build/index.mjs +413 -227
- package/config/notification.config.mjs +13 -13
- package/config/symbol.config.mjs +460 -460
- package/docker/.env.example +2 -2
- package/docker/content/feb_2026/feb_2026.strategy.ts +11 -11
- package/docker/content/feb_2026/modules/backtest.module.ts +83 -83
- package/docker/docker-compose.yaml +46 -46
- package/docker/package.json +39 -38
- package/docker/tsconfig.json +36 -36
- package/package.json +126 -126
- package/template/average-buy.mustache +22 -22
- package/template/breakeven.mustache +21 -21
- package/template/cancel-scheduled.mustache +14 -14
- package/template/cancelled.mustache +21 -21
- package/template/close-pending.mustache +16 -16
- package/template/closed.mustache +23 -23
- package/template/opened.mustache +22 -22
- package/template/partial-loss.mustache +22 -22
- package/template/partial-profit.mustache +22 -22
- package/template/project/config/symbol.config.ts +460 -460
- package/template/project/package.mustache +28 -28
- package/template/risk.mustache +19 -19
- package/template/scheduled.mustache +22 -22
- package/template/signal-close.mustache +22 -22
- package/template/signal-info.mustache +20 -20
- package/template/signal-open.mustache +22 -22
- package/template/source/CLAUDE.md +160 -160
- package/template/trailing-stop.mustache +21 -21
- package/template/trailing-take.mustache +21 -21
package/build/index.mjs
CHANGED
|
@@ -73,11 +73,11 @@ const getEntrySubject = singleshot(() => {
|
|
|
73
73
|
return new BehaviorSubject();
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
const main$
|
|
76
|
+
const main$i = () => {
|
|
77
77
|
const entrySubject = getEntrySubject();
|
|
78
78
|
entrySubject.subscribe((path) => console.log("Running", path));
|
|
79
79
|
};
|
|
80
|
-
main$
|
|
80
|
+
main$i();
|
|
81
81
|
|
|
82
82
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
83
83
|
function dumpStackTrace() {
|
|
@@ -638,6 +638,10 @@ const getArgs = singleshot(() => {
|
|
|
638
638
|
type: "boolean",
|
|
639
639
|
default: false,
|
|
640
640
|
},
|
|
641
|
+
main: {
|
|
642
|
+
type: "boolean",
|
|
643
|
+
default: false,
|
|
644
|
+
},
|
|
641
645
|
cacheInterval: {
|
|
642
646
|
type: "string",
|
|
643
647
|
default: "1m, 15m, 30m, 4h",
|
|
@@ -1586,6 +1590,8 @@ const getEnv = singleshot(() => {
|
|
|
1586
1590
|
};
|
|
1587
1591
|
});
|
|
1588
1592
|
|
|
1593
|
+
const READY_SUBJECT = new BehaviorSubject();
|
|
1594
|
+
const MAX_WAIT_TIME = 5000;
|
|
1589
1595
|
const GET_SYMBOL_EXPORTS_FN = async (self) => {
|
|
1590
1596
|
const exports = await self.configConnectionService.loadConfig("symbol.config");
|
|
1591
1597
|
if (!exports) {
|
|
@@ -1630,6 +1636,17 @@ const MAP_SYMBOL_CONFIG_FN = (config) => {
|
|
|
1630
1636
|
symbolList.sort(({ priority: a_p, index: a_x }, { priority: b_p, index: b_x }) => b_p - a_p || a_x - b_x);
|
|
1631
1637
|
return symbolList;
|
|
1632
1638
|
};
|
|
1639
|
+
const CREATE_LISTEN_FN = (self) => async () => {
|
|
1640
|
+
const promise = READY_SUBJECT.toPromise();
|
|
1641
|
+
self.enable();
|
|
1642
|
+
if (READY_SUBJECT.data) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
await Promise.race([
|
|
1646
|
+
promise,
|
|
1647
|
+
sleep(MAX_WAIT_TIME),
|
|
1648
|
+
]);
|
|
1649
|
+
};
|
|
1633
1650
|
class FrontendProviderService {
|
|
1634
1651
|
constructor() {
|
|
1635
1652
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -1647,7 +1664,7 @@ class FrontendProviderService {
|
|
|
1647
1664
|
lib.symbolConnectionService.getSymbolList.setValue(Promise.resolve(symbolList));
|
|
1648
1665
|
}
|
|
1649
1666
|
}
|
|
1650
|
-
unServer = serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR);
|
|
1667
|
+
unServer = serve(CC_WWWROOT_HOST, CC_WWWROOT_PORT, this.resolveService.PROJECT_ROOT_DIR, () => READY_SUBJECT.next(true));
|
|
1651
1668
|
};
|
|
1652
1669
|
init();
|
|
1653
1670
|
return () => {
|
|
@@ -1667,7 +1684,7 @@ class FrontendProviderService {
|
|
|
1667
1684
|
if (!getArgs().values.ui) {
|
|
1668
1685
|
return;
|
|
1669
1686
|
}
|
|
1670
|
-
return getEntrySubject().subscribe(this
|
|
1687
|
+
return getEntrySubject().subscribe(CREATE_LISTEN_FN(this));
|
|
1671
1688
|
});
|
|
1672
1689
|
}
|
|
1673
1690
|
}
|
|
@@ -3329,14 +3346,14 @@ const cli = {
|
|
|
3329
3346
|
};
|
|
3330
3347
|
init();
|
|
3331
3348
|
|
|
3332
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3349
|
+
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3333
3350
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3334
|
-
const HELP_TEXT$1 = `
|
|
3335
|
-
Example:
|
|
3336
|
-
|
|
3337
|
-
node ${ENTRY_PATH$1} --help
|
|
3351
|
+
const HELP_TEXT$1 = `
|
|
3352
|
+
Example:
|
|
3353
|
+
|
|
3354
|
+
node ${ENTRY_PATH$1} --help
|
|
3338
3355
|
`.trimStart();
|
|
3339
|
-
const main$
|
|
3356
|
+
const main$h = async () => {
|
|
3340
3357
|
if (!getEntry(import.meta.url)) {
|
|
3341
3358
|
return;
|
|
3342
3359
|
}
|
|
@@ -3344,14 +3361,14 @@ const main$g = async () => {
|
|
|
3344
3361
|
if (MODES.some((mode) => values[mode])) {
|
|
3345
3362
|
return;
|
|
3346
3363
|
}
|
|
3347
|
-
process.stdout.write(`@backtest-kit/cli ${"12.
|
|
3364
|
+
process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
|
|
3348
3365
|
process.stdout.write("\n");
|
|
3349
3366
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3350
3367
|
process.stdout.write("\n");
|
|
3351
3368
|
process.stdout.write(HELP_TEXT$1);
|
|
3352
3369
|
process.exit(0);
|
|
3353
3370
|
};
|
|
3354
|
-
main$
|
|
3371
|
+
main$h();
|
|
3355
3372
|
|
|
3356
3373
|
const notifyShutdown = singleshot(async () => {
|
|
3357
3374
|
console.log("Graceful shutdown initiated.");
|
|
@@ -3367,7 +3384,7 @@ const flush = async (entryPoint) => {
|
|
|
3367
3384
|
console.log(`Removed: ${target}`);
|
|
3368
3385
|
}
|
|
3369
3386
|
};
|
|
3370
|
-
const main$
|
|
3387
|
+
const main$g = async () => {
|
|
3371
3388
|
if (!getEntry(import.meta.url)) {
|
|
3372
3389
|
return;
|
|
3373
3390
|
}
|
|
@@ -3384,10 +3401,10 @@ const main$f = async () => {
|
|
|
3384
3401
|
}
|
|
3385
3402
|
process.exit(0);
|
|
3386
3403
|
};
|
|
3387
|
-
main$
|
|
3404
|
+
main$g();
|
|
3388
3405
|
|
|
3389
|
-
const BEFORE_EXIT_FN$
|
|
3390
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
3406
|
+
const BEFORE_EXIT_FN$6 = singleshot(async () => {
|
|
3407
|
+
process.off("SIGINT", BEFORE_EXIT_FN$6);
|
|
3391
3408
|
const [running = null] = await Backtest.list();
|
|
3392
3409
|
if (!running) {
|
|
3393
3410
|
return;
|
|
@@ -3404,10 +3421,10 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3404
3421
|
frameName,
|
|
3405
3422
|
});
|
|
3406
3423
|
});
|
|
3407
|
-
const listenGracefulShutdown$
|
|
3408
|
-
process.on("SIGINT", BEFORE_EXIT_FN$
|
|
3424
|
+
const listenGracefulShutdown$6 = singleshot(() => {
|
|
3425
|
+
process.on("SIGINT", BEFORE_EXIT_FN$6);
|
|
3409
3426
|
});
|
|
3410
|
-
const main$
|
|
3427
|
+
const main$f = async () => {
|
|
3411
3428
|
if (!getEntry(import.meta.url)) {
|
|
3412
3429
|
return;
|
|
3413
3430
|
}
|
|
@@ -3423,12 +3440,12 @@ const main$e = async () => {
|
|
|
3423
3440
|
entryPoint && await flush(entryPoint);
|
|
3424
3441
|
}
|
|
3425
3442
|
await cli.backtestMainService.connect();
|
|
3426
|
-
listenGracefulShutdown$
|
|
3443
|
+
listenGracefulShutdown$6();
|
|
3427
3444
|
};
|
|
3428
|
-
main$
|
|
3445
|
+
main$f();
|
|
3429
3446
|
|
|
3430
|
-
const BEFORE_EXIT_FN$
|
|
3431
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
3447
|
+
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
3448
|
+
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
3432
3449
|
const [running = null] = await Walker.list();
|
|
3433
3450
|
if (!running) {
|
|
3434
3451
|
return;
|
|
@@ -3441,10 +3458,10 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3441
3458
|
}
|
|
3442
3459
|
Walker.stop(symbol, { walkerName });
|
|
3443
3460
|
});
|
|
3444
|
-
const listenGracefulShutdown$
|
|
3445
|
-
process.on("SIGINT", BEFORE_EXIT_FN$
|
|
3461
|
+
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3462
|
+
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3446
3463
|
});
|
|
3447
|
-
const main$
|
|
3464
|
+
const main$e = async () => {
|
|
3448
3465
|
if (!getEntry(import.meta.url)) {
|
|
3449
3466
|
return;
|
|
3450
3467
|
}
|
|
@@ -3460,13 +3477,13 @@ const main$d = async () => {
|
|
|
3460
3477
|
await flush(entryPoint);
|
|
3461
3478
|
}
|
|
3462
3479
|
}
|
|
3463
|
-
listenGracefulShutdown$
|
|
3480
|
+
listenGracefulShutdown$5();
|
|
3464
3481
|
await cli.walkerMainService.connect();
|
|
3465
3482
|
};
|
|
3466
|
-
main$
|
|
3483
|
+
main$e();
|
|
3467
3484
|
|
|
3468
|
-
const BEFORE_EXIT_FN$
|
|
3469
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
3485
|
+
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3486
|
+
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
3470
3487
|
const [running = null] = await Live.list();
|
|
3471
3488
|
if (!running) {
|
|
3472
3489
|
return;
|
|
@@ -3482,10 +3499,10 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3482
3499
|
strategyName,
|
|
3483
3500
|
});
|
|
3484
3501
|
});
|
|
3485
|
-
const listenGracefulShutdown$
|
|
3486
|
-
process.on("SIGINT", BEFORE_EXIT_FN$
|
|
3502
|
+
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3503
|
+
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3487
3504
|
});
|
|
3488
|
-
const main$
|
|
3505
|
+
const main$d = async () => {
|
|
3489
3506
|
if (!getEntry(import.meta.url)) {
|
|
3490
3507
|
return;
|
|
3491
3508
|
}
|
|
@@ -3497,12 +3514,12 @@ const main$c = async () => {
|
|
|
3497
3514
|
return;
|
|
3498
3515
|
}
|
|
3499
3516
|
cli.paperMainService.connect();
|
|
3500
|
-
listenGracefulShutdown$
|
|
3517
|
+
listenGracefulShutdown$4();
|
|
3501
3518
|
};
|
|
3502
|
-
main$
|
|
3519
|
+
main$d();
|
|
3503
3520
|
|
|
3504
|
-
const BEFORE_EXIT_FN$
|
|
3505
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
3521
|
+
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3522
|
+
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
3506
3523
|
const [running = null] = await Live.list();
|
|
3507
3524
|
if (!running) {
|
|
3508
3525
|
return;
|
|
@@ -3518,6 +3535,84 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3518
3535
|
strategyName,
|
|
3519
3536
|
});
|
|
3520
3537
|
});
|
|
3538
|
+
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3539
|
+
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3540
|
+
});
|
|
3541
|
+
const main$c = async () => {
|
|
3542
|
+
if (!getEntry(import.meta.url)) {
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
const { values } = getArgs();
|
|
3546
|
+
if (!values.live) {
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
if (values.entry) {
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
await cli.liveMainService.connect();
|
|
3553
|
+
listenGracefulShutdown$3();
|
|
3554
|
+
};
|
|
3555
|
+
main$c();
|
|
3556
|
+
|
|
3557
|
+
const stopBacktestList$1 = async () => {
|
|
3558
|
+
for (const item of await Backtest.list()) {
|
|
3559
|
+
if (item.status === "fulfilled") {
|
|
3560
|
+
continue;
|
|
3561
|
+
}
|
|
3562
|
+
Backtest.stop(item.symbol, {
|
|
3563
|
+
exchangeName: item.exchangeName,
|
|
3564
|
+
strategyName: item.strategyName,
|
|
3565
|
+
frameName: item.frameName,
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
3569
|
+
const stopLiveList$1 = async () => {
|
|
3570
|
+
for (const item of await Live.list()) {
|
|
3571
|
+
if (item.status === "fulfilled") {
|
|
3572
|
+
continue;
|
|
3573
|
+
}
|
|
3574
|
+
Live.stop(item.symbol, {
|
|
3575
|
+
exchangeName: item.exchangeName,
|
|
3576
|
+
strategyName: item.strategyName,
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3579
|
+
};
|
|
3580
|
+
const stopWalkerList$1 = async () => {
|
|
3581
|
+
for (const item of await Walker.list()) {
|
|
3582
|
+
if (item.status === "fulfilled") {
|
|
3583
|
+
continue;
|
|
3584
|
+
}
|
|
3585
|
+
Walker.stop(item.symbol, { walkerName: item.walkerName });
|
|
3586
|
+
}
|
|
3587
|
+
};
|
|
3588
|
+
const stopMain$1 = async () => {
|
|
3589
|
+
await stopBacktestList$1();
|
|
3590
|
+
await stopLiveList$1();
|
|
3591
|
+
await stopWalkerList$1();
|
|
3592
|
+
};
|
|
3593
|
+
const listenFinish$1 = singleshot(() => {
|
|
3594
|
+
let disposeRef;
|
|
3595
|
+
const unBacktest = listenDoneBacktest(() => {
|
|
3596
|
+
console.log("Backtest trading finished");
|
|
3597
|
+
disposeRef && disposeRef();
|
|
3598
|
+
});
|
|
3599
|
+
const unLive = listenDoneLive(() => {
|
|
3600
|
+
console.log("Live trading finished");
|
|
3601
|
+
disposeRef && disposeRef();
|
|
3602
|
+
});
|
|
3603
|
+
const unWalker = listenDoneWalker(() => {
|
|
3604
|
+
console.log("Walker comparison finished");
|
|
3605
|
+
disposeRef && disposeRef();
|
|
3606
|
+
});
|
|
3607
|
+
disposeRef = compose(() => unBacktest(), () => unLive(), () => unWalker());
|
|
3608
|
+
shutdown();
|
|
3609
|
+
});
|
|
3610
|
+
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
3611
|
+
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
3612
|
+
notifyShutdown();
|
|
3613
|
+
notifyKill();
|
|
3614
|
+
await stopMain$1();
|
|
3615
|
+
});
|
|
3521
3616
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3522
3617
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3523
3618
|
});
|
|
@@ -3526,14 +3621,56 @@ const main$b = async () => {
|
|
|
3526
3621
|
return;
|
|
3527
3622
|
}
|
|
3528
3623
|
const { values } = getArgs();
|
|
3529
|
-
if (!values.
|
|
3624
|
+
if (!values.main) {
|
|
3530
3625
|
return;
|
|
3531
3626
|
}
|
|
3532
3627
|
if (values.entry) {
|
|
3533
3628
|
return;
|
|
3534
3629
|
}
|
|
3535
|
-
|
|
3630
|
+
const [entryPoint = null] = getPositionals();
|
|
3631
|
+
if (!entryPoint) {
|
|
3632
|
+
throw new Error("Entry point is required");
|
|
3633
|
+
}
|
|
3634
|
+
{
|
|
3635
|
+
const cwd = process.cwd();
|
|
3636
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3637
|
+
}
|
|
3638
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3639
|
+
{
|
|
3640
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3641
|
+
try {
|
|
3642
|
+
if (typeof loader === "function") {
|
|
3643
|
+
await loader();
|
|
3644
|
+
}
|
|
3645
|
+
if (typeof loader?.loader === "function") {
|
|
3646
|
+
await loader.loader();
|
|
3647
|
+
}
|
|
3648
|
+
}
|
|
3649
|
+
catch (error) {
|
|
3650
|
+
console.error("Module loader failed", error);
|
|
3651
|
+
kill(-1);
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
{
|
|
3656
|
+
await cli.configService.waitForInit();
|
|
3657
|
+
Setup.enable();
|
|
3658
|
+
}
|
|
3659
|
+
const cwd = process.cwd();
|
|
3660
|
+
{
|
|
3661
|
+
const absolutePath = path.resolve(entryPoint);
|
|
3662
|
+
const moduleRoot = path.dirname(absolutePath);
|
|
3663
|
+
process.chdir(moduleRoot);
|
|
3664
|
+
cwd !== moduleRoot && Setup.update();
|
|
3665
|
+
dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
|
|
3666
|
+
}
|
|
3667
|
+
if (!values.noFlush) {
|
|
3668
|
+
await flush(path.resolve(cwd, entryPoint));
|
|
3669
|
+
}
|
|
3670
|
+
await cli.moduleConnectionService.loadModule("main.module");
|
|
3671
|
+
listenFinish$1();
|
|
3536
3672
|
listenGracefulShutdown$2();
|
|
3673
|
+
await cli.resolveService.attachEntry(path.resolve(cwd, entryPoint));
|
|
3537
3674
|
};
|
|
3538
3675
|
main$b();
|
|
3539
3676
|
|
|
@@ -3542,13 +3679,29 @@ const MODE_MODULE = {
|
|
|
3542
3679
|
live: "live.module",
|
|
3543
3680
|
paper: "paper.module",
|
|
3544
3681
|
walker: "walker.module",
|
|
3682
|
+
main: "main.module",
|
|
3545
3683
|
};
|
|
3684
|
+
const MODE_LIST = ["backtest", "live", "paper", "walker", "main"];
|
|
3546
3685
|
const resolveMode = (values) => {
|
|
3547
|
-
|
|
3548
|
-
if (enabled.length !== 1) {
|
|
3686
|
+
if (MODE_LIST.filter((mode) => values[mode]).length > 1) {
|
|
3549
3687
|
return null;
|
|
3550
3688
|
}
|
|
3551
|
-
|
|
3689
|
+
if (values.main) {
|
|
3690
|
+
return "main";
|
|
3691
|
+
}
|
|
3692
|
+
if (values.backtest) {
|
|
3693
|
+
return "backtest";
|
|
3694
|
+
}
|
|
3695
|
+
if (values.live) {
|
|
3696
|
+
return "live";
|
|
3697
|
+
}
|
|
3698
|
+
if (values.paper) {
|
|
3699
|
+
return "paper";
|
|
3700
|
+
}
|
|
3701
|
+
if (values.walker) {
|
|
3702
|
+
return "walker";
|
|
3703
|
+
}
|
|
3704
|
+
return null;
|
|
3552
3705
|
};
|
|
3553
3706
|
const stopBacktestList = async () => {
|
|
3554
3707
|
for (const item of await Backtest.list()) {
|
|
@@ -3581,11 +3734,17 @@ const stopWalkerList = async () => {
|
|
|
3581
3734
|
Walker.stop(item.symbol, { walkerName: item.walkerName });
|
|
3582
3735
|
}
|
|
3583
3736
|
};
|
|
3737
|
+
const stopMain = async () => {
|
|
3738
|
+
await stopBacktestList();
|
|
3739
|
+
await stopLiveList();
|
|
3740
|
+
await stopWalkerList();
|
|
3741
|
+
};
|
|
3584
3742
|
const MODE_STOP = {
|
|
3585
3743
|
backtest: stopBacktestList,
|
|
3586
3744
|
live: stopLiveList,
|
|
3587
3745
|
paper: stopLiveList,
|
|
3588
3746
|
walker: stopWalkerList,
|
|
3747
|
+
main: stopMain,
|
|
3589
3748
|
};
|
|
3590
3749
|
const listenFinish = singleshot(() => {
|
|
3591
3750
|
let disposeRef;
|
|
@@ -3626,12 +3785,12 @@ const main$a = async () => {
|
|
|
3626
3785
|
}
|
|
3627
3786
|
const mode = resolveMode(values);
|
|
3628
3787
|
if (!mode) {
|
|
3629
|
-
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
|
|
3788
|
+
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker, --main");
|
|
3630
3789
|
kill(1);
|
|
3631
3790
|
return;
|
|
3632
3791
|
}
|
|
3633
3792
|
const entryPoints = getPositionals();
|
|
3634
|
-
if (!entryPoints.length) {
|
|
3793
|
+
if (mode !== "main" && !entryPoints.length) {
|
|
3635
3794
|
throw new Error("At least one entry point is required");
|
|
3636
3795
|
}
|
|
3637
3796
|
{
|
|
@@ -3678,12 +3837,18 @@ const main$a = async () => {
|
|
|
3678
3837
|
}
|
|
3679
3838
|
await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
|
|
3680
3839
|
listenFinish();
|
|
3681
|
-
|
|
3840
|
+
{
|
|
3841
|
+
const listenShutdown = createGracefulShutdown(mode);
|
|
3842
|
+
listenShutdown();
|
|
3843
|
+
}
|
|
3682
3844
|
let absolutePath;
|
|
3683
3845
|
for (const entryPoint of entryPoints) {
|
|
3684
3846
|
absolutePath = path.resolve(cwd, entryPoint);
|
|
3685
3847
|
await cli.resolveService.attachEntry(absolutePath);
|
|
3686
3848
|
}
|
|
3849
|
+
if (mode === "main") {
|
|
3850
|
+
return;
|
|
3851
|
+
}
|
|
3687
3852
|
await getEntrySubject().next(absolutePath);
|
|
3688
3853
|
};
|
|
3689
3854
|
main$a();
|
|
@@ -4295,183 +4460,204 @@ const main$2 = async () => {
|
|
|
4295
4460
|
main$2();
|
|
4296
4461
|
|
|
4297
4462
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
4298
|
-
const HELP_TEXT = `
|
|
4299
|
-
Usage:
|
|
4300
|
-
node index.mjs --<mode> [flags] [entry-point]
|
|
4301
|
-
|
|
4302
|
-
Modes:
|
|
4303
|
-
|
|
4304
|
-
--backtest <entry> Run strategy against historical candle data
|
|
4305
|
-
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4306
|
-
--paper <entry> Paper trading (live prices, no real orders)
|
|
4307
|
-
--live <entry> Live trading with real orders
|
|
4308
|
-
--
|
|
4309
|
-
--
|
|
4310
|
-
--
|
|
4311
|
-
--
|
|
4312
|
-
--
|
|
4313
|
-
--
|
|
4314
|
-
--
|
|
4315
|
-
--
|
|
4316
|
-
--
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
--
|
|
4322
|
-
--
|
|
4323
|
-
--
|
|
4324
|
-
--
|
|
4325
|
-
--
|
|
4326
|
-
--
|
|
4327
|
-
--
|
|
4328
|
-
--
|
|
4329
|
-
--
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
--
|
|
4335
|
-
--
|
|
4336
|
-
--
|
|
4337
|
-
--
|
|
4338
|
-
--
|
|
4339
|
-
--
|
|
4340
|
-
--
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
--
|
|
4353
|
-
--
|
|
4354
|
-
--
|
|
4355
|
-
--
|
|
4356
|
-
--
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
--
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
--
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
--
|
|
4380
|
-
--
|
|
4381
|
-
--
|
|
4382
|
-
--
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
--
|
|
4395
|
-
--
|
|
4396
|
-
--
|
|
4397
|
-
--
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4463
|
+
const HELP_TEXT = `
|
|
4464
|
+
Usage:
|
|
4465
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
4466
|
+
|
|
4467
|
+
Modes:
|
|
4468
|
+
|
|
4469
|
+
--backtest <entry> Run strategy against historical candle data
|
|
4470
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4471
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
4472
|
+
--live <entry> Live trading with real orders
|
|
4473
|
+
--main <entry> Run an entry point with prepared environment, no trading harness
|
|
4474
|
+
--pine <entry> Execute a local .pine indicator file
|
|
4475
|
+
--editor Open the Pine Script visual editor in the browser
|
|
4476
|
+
--dump Fetch and save raw OHLCV candles
|
|
4477
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4478
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4479
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4480
|
+
--init Scaffold a new project in the current directory
|
|
4481
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4482
|
+
--help Print this help message
|
|
4483
|
+
|
|
4484
|
+
Backtest flags:
|
|
4485
|
+
|
|
4486
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4487
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4488
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4489
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4490
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4491
|
+
--noCache Skip candle cache warming before the run
|
|
4492
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4493
|
+
--verbose Log every candle fetch to stdout
|
|
4494
|
+
--ui Start web dashboard at http://localhost:60050
|
|
4495
|
+
--telegram Send trade notifications to Telegram
|
|
4496
|
+
|
|
4497
|
+
Walker flags (--walker):
|
|
4498
|
+
|
|
4499
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4500
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4501
|
+
--noCache Skip candle cache warming before the run
|
|
4502
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4503
|
+
--verbose Log every candle fetch to stdout
|
|
4504
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4505
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
4506
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4507
|
+
|
|
4508
|
+
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
4509
|
+
changing process.cwd() — .env is read from the working directory only.
|
|
4510
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4511
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4512
|
+
|
|
4513
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4514
|
+
|
|
4515
|
+
Paper / Live flags:
|
|
4516
|
+
|
|
4517
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4518
|
+
--strategy <string> Strategy name (default: first registered)
|
|
4519
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4520
|
+
--verbose Log every candle fetch to stdout
|
|
4521
|
+
--ui Start web dashboard
|
|
4522
|
+
--telegram Send Telegram notifications
|
|
4523
|
+
|
|
4524
|
+
Main flags (--main):
|
|
4525
|
+
|
|
4526
|
+
--noFlush Skip removing report/log/markdown/agent folders before the run
|
|
4527
|
+
|
|
4528
|
+
Prepares the runtime environment (loads .env, setup.config, loader.config and
|
|
4529
|
+
modules/main.module) and runs the single positional entry point — but does NOT
|
|
4530
|
+
start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
|
|
4531
|
+
Backtest/Live/Walker.background; the entry point decides what to run.
|
|
4532
|
+
|
|
4533
|
+
Exactly one positional entry point is required. process.cwd() is changed to the
|
|
4534
|
+
entry point directory and its local .env is loaded.
|
|
4535
|
+
|
|
4536
|
+
Backtest, Live and Walker runs started from userspace are still tracked: the run
|
|
4537
|
+
finishes automatically when one of them completes, Ctrl+C stops any active run,
|
|
4538
|
+
and a second Ctrl+C force-quits.
|
|
4539
|
+
|
|
4540
|
+
Module file ./modules/main.module is loaded automatically if it exists.
|
|
4541
|
+
|
|
4542
|
+
PineScript flags (--pine):
|
|
4543
|
+
|
|
4544
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4545
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4546
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
4547
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4548
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4549
|
+
--output <string> Output file base name without extension
|
|
4550
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4551
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4552
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4553
|
+
|
|
4554
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
4555
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4556
|
+
|
|
4557
|
+
Candle dump flags (--dump):
|
|
4558
|
+
|
|
4559
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4560
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4561
|
+
--limit <string> Number of candles (default: 250)
|
|
4562
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4563
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4564
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4565
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4566
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4567
|
+
|
|
4568
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4569
|
+
|
|
4570
|
+
PnL debug flags (--pnldebug):
|
|
4571
|
+
|
|
4572
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4573
|
+
--priceopen <number> Entry price (required)
|
|
4574
|
+
--direction <string> Position direction: long or short (default: long)
|
|
4575
|
+
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4576
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4577
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4578
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4579
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4580
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4581
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4582
|
+
|
|
4583
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4584
|
+
|
|
4585
|
+
Broker debug flags (--brokerdebug):
|
|
4586
|
+
|
|
4587
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4588
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4589
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4590
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4591
|
+
(default: signal-open)
|
|
4592
|
+
|
|
4593
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4594
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
4595
|
+
|
|
4596
|
+
Flush flags (--flush):
|
|
4597
|
+
|
|
4598
|
+
One or more positional entry points. For each entry point the following
|
|
4599
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
4600
|
+
|
|
4601
|
+
report log markdown agent
|
|
4602
|
+
|
|
4603
|
+
Init flags (--init):
|
|
4604
|
+
|
|
4605
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
4606
|
+
|
|
4607
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4608
|
+
|
|
4609
|
+
Docker flags (--docker):
|
|
4610
|
+
|
|
4611
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4612
|
+
|
|
4613
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4614
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4615
|
+
docker compose up to start the container.
|
|
4616
|
+
|
|
4617
|
+
Module hooks (loaded automatically by each mode):
|
|
4618
|
+
|
|
4619
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
4620
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
4621
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
4622
|
+
modules/live.module --live Broker adapter for live trading
|
|
4623
|
+
modules/main.module --main Environment setup for a custom entry point
|
|
4624
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4625
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4626
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
4627
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4628
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4629
|
+
|
|
4630
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
4631
|
+
|
|
4632
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4633
|
+
|
|
4634
|
+
Environment variables:
|
|
4635
|
+
|
|
4636
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4637
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4638
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4639
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4640
|
+
|
|
4641
|
+
Examples:
|
|
4642
|
+
|
|
4643
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4644
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4645
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4646
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4647
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4648
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4649
|
+
node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
|
|
4650
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4651
|
+
node ${ENTRY_PATH} --editor
|
|
4652
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4653
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4654
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4655
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4656
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4657
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4658
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4659
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4660
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4475
4661
|
`.trimStart();
|
|
4476
4662
|
const main$1 = async () => {
|
|
4477
4663
|
if (!getEntry(import.meta.url)) {
|
|
@@ -4481,7 +4667,7 @@ const main$1 = async () => {
|
|
|
4481
4667
|
if (!values.help) {
|
|
4482
4668
|
return;
|
|
4483
4669
|
}
|
|
4484
|
-
process.stdout.write(`@backtest-kit/cli ${"12.
|
|
4670
|
+
process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n\n`);
|
|
4485
4671
|
process.stdout.write(HELP_TEXT);
|
|
4486
4672
|
process.exit(0);
|
|
4487
4673
|
};
|
|
@@ -4495,7 +4681,7 @@ const main = async () => {
|
|
|
4495
4681
|
if (!values.version) {
|
|
4496
4682
|
return;
|
|
4497
4683
|
}
|
|
4498
|
-
process.stdout.write(`@backtest-kit/cli ${"12.
|
|
4684
|
+
process.stdout.write(`@backtest-kit/cli ${"12.6.0"}\n`);
|
|
4499
4685
|
process.exit(0);
|
|
4500
4686
|
};
|
|
4501
4687
|
main();
|
|
@@ -4517,17 +4703,17 @@ async function run(mode, args) {
|
|
|
4517
4703
|
}
|
|
4518
4704
|
if (mode === "backtest") {
|
|
4519
4705
|
await cli.backtestMainService.run(args);
|
|
4520
|
-
listenGracefulShutdown$
|
|
4706
|
+
listenGracefulShutdown$6();
|
|
4521
4707
|
return;
|
|
4522
4708
|
}
|
|
4523
4709
|
if (mode === "paper") {
|
|
4524
4710
|
await cli.paperMainService.run(args);
|
|
4525
|
-
listenGracefulShutdown$
|
|
4711
|
+
listenGracefulShutdown$4();
|
|
4526
4712
|
return;
|
|
4527
4713
|
}
|
|
4528
4714
|
if (mode === "live") {
|
|
4529
4715
|
await cli.liveMainService.run(args);
|
|
4530
|
-
listenGracefulShutdown$
|
|
4716
|
+
listenGracefulShutdown$3();
|
|
4531
4717
|
return;
|
|
4532
4718
|
}
|
|
4533
4719
|
throw new Error(`Invalid mode: ${mode}`);
|