@backtest-kit/cli 9.6.0 β 9.8.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 +116 -0
- package/build/index.cjs +107 -25
- package/build/index.mjs +107 -25
- package/docker/README.md +1 -0
- package/docker/package.json +6 -6
- package/package.json +13 -13
- package/template/project/README.md +1 -0
- package/template/project/package.mustache +5 -5
- package/types.d.ts +1 -6
package/README.md
CHANGED
|
@@ -16,6 +16,50 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
|
|
|
16
16
|
|
|
17
17
|
> **New to backtest-kit?** The fastest way to get a real, production-ready setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) β a fully working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there instead of from scratch.
|
|
18
18
|
|
|
19
|
+
## ποΈ CLI Init
|
|
20
|
+
|
|
21
|
+
Minimal scaffold β all boilerplate stays inside @backtest-kit/cli:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @backtest-kit/cli --init --output backtest-kit-project
|
|
25
|
+
cd backtest-kit-project
|
|
26
|
+
npm install
|
|
27
|
+
npm start -- --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## π€ Philosophy
|
|
31
|
+
|
|
32
|
+
`@backtest-kit/cli` is designed to do **two things well** β and the same tool covers both.
|
|
33
|
+
|
|
34
|
+
**1. The lightest possible runner for a solo quant on day one.**
|
|
35
|
+
|
|
36
|
+
You write a strategy file, point the CLI at it, and you're trading. No DI container to learn, no project scaffold to fight, no infrastructure code to copy-paste. One developer, one strategy, one command:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx @backtest-kit/cli --init
|
|
40
|
+
npx @backtest-kit/cli --backtest ./content/feb_2026.strategy/index.ts
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That's the whole onboarding. The first day you have an idea, you can backtest it. The first week you have an edge, you can paper-trade it. The first month you have a P&L, you can run it live β same CLI, different flag.
|
|
44
|
+
|
|
45
|
+
**2. Built-in monorepo tooling for when the business takes off.**
|
|
46
|
+
|
|
47
|
+
The moment you start making money is the worst possible moment to rewrite your stack in another language. So the CLI is also a monorepo-grade runner from day one β even if you don't use it that way at first.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
monorepo/
|
|
51
|
+
βββ content/
|
|
52
|
+
β βββ feb_2026.strategy/
|
|
53
|
+
β βββ feb_2026.strategy.ts # strategy production code
|
|
54
|
+
β βββ feb_2026.test.ts # developer playground
|
|
55
|
+
βββ packages/
|
|
56
|
+
β βββ shared-broker/ # shared broker code
|
|
57
|
+
β βββ shared-signals/ # common indicators (RSI, MACD)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
As a result: you used to backtest your first idea is the same tool you use to run a desk of strategies in production. No rewrite, no language switch, no framework migration when the business scales β only more files in the monorepo.
|
|
62
|
+
|
|
19
63
|
## β¨ Features
|
|
20
64
|
|
|
21
65
|
- π **Zero Config**: Run `npx @backtest-kit/cli --backtest ./strategy.mjs` β no boilerplate needed
|
|
@@ -772,6 +816,78 @@ setup({
|
|
|
772
816
|
|
|
773
817
|
No changes to strategy code are needed β `setup()` wires up the adapters transparently before `backtest-kit` makes its first persistence call.
|
|
774
818
|
|
|
819
|
+
## π§© Module Loader (`config/loader.config`)
|
|
820
|
+
|
|
821
|
+
`@backtest-kit/cli` loads a `{projectRoot}/config/loader.config` file **after** `setup.config` but **before** any strategy or module code runs. Unlike `setup.config` (which is loaded for its side effects), `loader.config` exports a function that the CLI explicitly `await`s. Use it whenever you need to **wait for an async dependency** to be ready before the backtest starts.
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
monorepo/
|
|
825
|
+
βββ packages/
|
|
826
|
+
β βββ shared-broker/ # shared broker code
|
|
827
|
+
β βββ shared-signals/ # common indicators (RSI, MACD)
|
|
828
|
+
β βββ shared-db/ # mongodb wiring
|
|
829
|
+
β βββ strategy-momentum/ # strategy code
|
|
830
|
+
β βββ strategy-mean-reversion/
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
**When to use it:**
|
|
834
|
+
|
|
835
|
+
- **Wire microfrontends in a monorepo** β resolve and pre-load sibling packages, register cross-package services, or hydrate a shared DI container before strategies import from neighboring workspaces.
|
|
836
|
+
- **Wait for a database connection** β open a Mongo/Postgres/Redis connection and verify it's reachable before the first persistence call, so the backtest fails fast instead of mid-run.
|
|
837
|
+
- **Warm up caches or external APIs** β pre-fetch reference data (instruments list, calendar, fee tables) so the strategy's first tick doesn't pay the round-trip cost.
|
|
838
|
+
- **Run schema migrations** β apply any pending migrations to the persistence backend before signals start flowing.
|
|
839
|
+
|
|
840
|
+
`loader.config` supports exactly one of two export styles β **never both at once**. If both are present, the `default` export wins and the named `loader` is ignored.
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
// config/loader.config.ts β default export (preferred, ESM style)
|
|
844
|
+
export default async () => {
|
|
845
|
+
await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
|
|
846
|
+
await redis.ping();
|
|
847
|
+
};
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
```ts
|
|
851
|
+
// config/loader.config.ts β named export
|
|
852
|
+
export const loader = async () => {
|
|
853
|
+
await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
|
|
854
|
+
await redis.ping();
|
|
855
|
+
};
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Example: wait for MongoDB before running a backtest
|
|
859
|
+
|
|
860
|
+
`@backtest-kit/mongo`'s `setup()` registers the adapters synchronously but doesn't block until the connection is established. If your backtest depends on data that must be present in Mongo before the first signal fires, use `loader.config` to gate the run on a real connection:
|
|
861
|
+
|
|
862
|
+
```ts
|
|
863
|
+
// config/setup.config.ts
|
|
864
|
+
import { setup } from '@backtest-kit/mongo';
|
|
865
|
+
|
|
866
|
+
setup();
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
// config/loader.config.ts
|
|
871
|
+
import mongoose from 'mongoose';
|
|
872
|
+
|
|
873
|
+
export default async () => {
|
|
874
|
+
await mongoose.connect(process.env.CC_MONGO_CONNECTION_STRING!);
|
|
875
|
+
console.log('mongo connection verified, starting backtest');
|
|
876
|
+
};
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
### Example: stitch microfrontends in a monorepo
|
|
880
|
+
|
|
881
|
+
When `backtest-kit` strategies live in one workspace and shared services (broker adapters, signal feeds, dashboards) live in sibling workspaces, `loader.config` is the place to wire them together before the runner starts:
|
|
882
|
+
|
|
883
|
+
```ts
|
|
884
|
+
// config/loader.config.ts
|
|
885
|
+
import "@my-org/brokers";
|
|
886
|
+
import "@my-org/signals";
|
|
887
|
+
```
|
|
888
|
+
|
|
889
|
+
The `@my-org` alias should be declared in `config/alias.config`.
|
|
890
|
+
|
|
775
891
|
## π Import Aliases (`config/alias.config`)
|
|
776
892
|
|
|
777
893
|
`@backtest-kit/cli` lets you override any nodejs module import β without touching the strategy code. Drop a `config/alias.config` file in your project root and export a mapping from module name to replacement module.
|
package/build/index.cjs
CHANGED
|
@@ -910,9 +910,15 @@ class BacktestMainService {
|
|
|
910
910
|
this.loggerService.log("backtestMainService run", {
|
|
911
911
|
payload,
|
|
912
912
|
});
|
|
913
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
913
914
|
{
|
|
914
|
-
await this.configConnectionService.loadConfig("
|
|
915
|
-
|
|
915
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
916
|
+
if (typeof loader === "function") {
|
|
917
|
+
await loader();
|
|
918
|
+
}
|
|
919
|
+
if (typeof loader?.loader === "function") {
|
|
920
|
+
await loader.loader();
|
|
921
|
+
}
|
|
916
922
|
}
|
|
917
923
|
{
|
|
918
924
|
await this.configService.waitForInit();
|
|
@@ -1032,9 +1038,15 @@ class WalkerMainService {
|
|
|
1032
1038
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1033
1039
|
this.run = functoolsKit.singleshot(async (payload) => {
|
|
1034
1040
|
this.loggerService.log("walkerMainService run", { payload });
|
|
1041
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1035
1042
|
{
|
|
1036
|
-
await this.configConnectionService.loadConfig("
|
|
1037
|
-
|
|
1043
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1044
|
+
if (typeof loader === "function") {
|
|
1045
|
+
await loader();
|
|
1046
|
+
}
|
|
1047
|
+
if (typeof loader?.loader === "function") {
|
|
1048
|
+
await loader.loader();
|
|
1049
|
+
}
|
|
1038
1050
|
}
|
|
1039
1051
|
{
|
|
1040
1052
|
await this.configService.waitForInit();
|
|
@@ -1260,9 +1272,15 @@ class LiveMainService {
|
|
|
1260
1272
|
this.loggerService.log("liveMainService run", {
|
|
1261
1273
|
payload,
|
|
1262
1274
|
});
|
|
1275
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1263
1276
|
{
|
|
1264
|
-
await this.configConnectionService.loadConfig("
|
|
1265
|
-
|
|
1277
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1278
|
+
if (typeof loader === "function") {
|
|
1279
|
+
await loader();
|
|
1280
|
+
}
|
|
1281
|
+
if (typeof loader?.loader === "function") {
|
|
1282
|
+
await loader.loader();
|
|
1283
|
+
}
|
|
1266
1284
|
}
|
|
1267
1285
|
{
|
|
1268
1286
|
await this.configService.waitForInit();
|
|
@@ -1349,9 +1367,15 @@ class PaperMainService {
|
|
|
1349
1367
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1350
1368
|
this.run = functoolsKit.singleshot(async (payload) => {
|
|
1351
1369
|
this.loggerService.log("paperMainService init");
|
|
1370
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1352
1371
|
{
|
|
1353
|
-
await this.configConnectionService.loadConfig("
|
|
1354
|
-
|
|
1372
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1373
|
+
if (typeof loader === "function") {
|
|
1374
|
+
await loader();
|
|
1375
|
+
}
|
|
1376
|
+
if (typeof loader?.loader === "function") {
|
|
1377
|
+
await loader.loader();
|
|
1378
|
+
}
|
|
1355
1379
|
}
|
|
1356
1380
|
{
|
|
1357
1381
|
await this.configService.waitForInit();
|
|
@@ -2623,14 +2647,29 @@ class BabelService {
|
|
|
2623
2647
|
}
|
|
2624
2648
|
}
|
|
2625
2649
|
|
|
2626
|
-
const require$1 = Module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
2627
2650
|
const ModuleWithCache = Module;
|
|
2651
|
+
const LOOKUP_PATHS = ModuleWithCache._nodeModulePaths(process.cwd());
|
|
2652
|
+
const VIRTUAL_MAP = new Map();
|
|
2653
|
+
{
|
|
2654
|
+
const originalResolveFilename = ModuleWithCache._resolveFilename;
|
|
2655
|
+
ModuleWithCache._resolveFilename = function (request, parent, isMain, options) {
|
|
2656
|
+
const virtualKey = VIRTUAL_MAP.get(request);
|
|
2657
|
+
if (virtualKey) {
|
|
2658
|
+
return virtualKey;
|
|
2659
|
+
}
|
|
2660
|
+
return originalResolveFilename.call(this, request, parent, isMain, options);
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2628
2663
|
function overrideModule(moduleName, newExports) {
|
|
2629
2664
|
const cache = ModuleWithCache._cache;
|
|
2630
|
-
const
|
|
2665
|
+
const resolved = ModuleWithCache._findPath(moduleName, LOOKUP_PATHS, false);
|
|
2666
|
+
const key = resolved || moduleName;
|
|
2667
|
+
VIRTUAL_MAP.set(moduleName, key);
|
|
2631
2668
|
if (!cache[key]) {
|
|
2632
2669
|
cache[key] = new ModuleWithCache(key);
|
|
2633
2670
|
cache[key].loaded = true;
|
|
2671
|
+
cache[key].filename = key;
|
|
2672
|
+
cache[key].paths = [];
|
|
2634
2673
|
}
|
|
2635
2674
|
cache[key].exports = newExports;
|
|
2636
2675
|
}
|
|
@@ -2913,8 +2952,14 @@ const GET_ALIAS_EXPORTS_FN = (self) => {
|
|
|
2913
2952
|
continue;
|
|
2914
2953
|
}
|
|
2915
2954
|
const instance = self.getInstance(baseDir);
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2955
|
+
const alias = instance.import(filePath);
|
|
2956
|
+
if (!alias) {
|
|
2957
|
+
return null;
|
|
2958
|
+
}
|
|
2959
|
+
if ("default" in alias) {
|
|
2960
|
+
return alias.default;
|
|
2961
|
+
}
|
|
2962
|
+
return alias;
|
|
2918
2963
|
}
|
|
2919
2964
|
return null;
|
|
2920
2965
|
};
|
|
@@ -3013,6 +3058,10 @@ class ConfigConnectionService {
|
|
|
3013
3058
|
const config = await LOAD_CONFIG_CONFIG_FN(fileName, this);
|
|
3014
3059
|
if (!config) {
|
|
3015
3060
|
this.loadConfig.clear(fileName);
|
|
3061
|
+
return null;
|
|
3062
|
+
}
|
|
3063
|
+
if ("default" in config) {
|
|
3064
|
+
return config.default;
|
|
3016
3065
|
}
|
|
3017
3066
|
return config;
|
|
3018
3067
|
});
|
|
@@ -3171,7 +3220,7 @@ const main$g = async () => {
|
|
|
3171
3220
|
if (MODES.some((mode) => values[mode])) {
|
|
3172
3221
|
return;
|
|
3173
3222
|
}
|
|
3174
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
3223
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n`);
|
|
3175
3224
|
process.stdout.write("\n");
|
|
3176
3225
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3177
3226
|
process.stdout.write("\n");
|
|
@@ -3462,9 +3511,15 @@ const main$a = async () => {
|
|
|
3462
3511
|
}
|
|
3463
3512
|
await flush(entryPoint);
|
|
3464
3513
|
}
|
|
3514
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3465
3515
|
{
|
|
3466
|
-
await cli.configConnectionService.loadConfig("
|
|
3467
|
-
|
|
3516
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3517
|
+
if (typeof loader === "function") {
|
|
3518
|
+
await loader();
|
|
3519
|
+
}
|
|
3520
|
+
if (typeof loader?.loader === "function") {
|
|
3521
|
+
await loader.loader();
|
|
3522
|
+
}
|
|
3468
3523
|
}
|
|
3469
3524
|
{
|
|
3470
3525
|
await cli.configService.waitForInit();
|
|
@@ -3476,6 +3531,9 @@ const main$a = async () => {
|
|
|
3476
3531
|
const cwd = process.cwd();
|
|
3477
3532
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3478
3533
|
}
|
|
3534
|
+
if (entryPoints.length === 1) {
|
|
3535
|
+
process.chdir(path.dirname(path.resolve(entryPoints[0])));
|
|
3536
|
+
}
|
|
3479
3537
|
await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
|
|
3480
3538
|
listenFinish();
|
|
3481
3539
|
createGracefulShutdown(mode)();
|
|
@@ -3566,9 +3624,15 @@ const main$7 = async () => {
|
|
|
3566
3624
|
const cwd = process.cwd();
|
|
3567
3625
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3568
3626
|
}
|
|
3627
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3569
3628
|
{
|
|
3570
|
-
await cli.configConnectionService.loadConfig("
|
|
3571
|
-
|
|
3629
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3630
|
+
if (typeof loader === "function") {
|
|
3631
|
+
await loader();
|
|
3632
|
+
}
|
|
3633
|
+
if (typeof loader?.loader === "function") {
|
|
3634
|
+
await loader.loader();
|
|
3635
|
+
}
|
|
3572
3636
|
}
|
|
3573
3637
|
await cli.moduleConnectionService.loadModule("pine.module");
|
|
3574
3638
|
{
|
|
@@ -3647,9 +3711,15 @@ const main$6 = async () => {
|
|
|
3647
3711
|
console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
|
|
3648
3712
|
process.exit(1);
|
|
3649
3713
|
}
|
|
3714
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3650
3715
|
{
|
|
3651
|
-
await cli.configConnectionService.loadConfig("
|
|
3652
|
-
|
|
3716
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3717
|
+
if (typeof loader === "function") {
|
|
3718
|
+
await loader();
|
|
3719
|
+
}
|
|
3720
|
+
if (typeof loader?.loader === "function") {
|
|
3721
|
+
await loader.loader();
|
|
3722
|
+
}
|
|
3653
3723
|
}
|
|
3654
3724
|
{
|
|
3655
3725
|
await cli.configService.waitForInit();
|
|
@@ -3692,9 +3762,15 @@ const main$5 = async () => {
|
|
|
3692
3762
|
const cwd = process.cwd();
|
|
3693
3763
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3694
3764
|
}
|
|
3765
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3695
3766
|
{
|
|
3696
|
-
await cli.configConnectionService.loadConfig("
|
|
3697
|
-
|
|
3767
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3768
|
+
if (typeof loader === "function") {
|
|
3769
|
+
await loader();
|
|
3770
|
+
}
|
|
3771
|
+
if (typeof loader?.loader === "function") {
|
|
3772
|
+
await loader.loader();
|
|
3773
|
+
}
|
|
3698
3774
|
}
|
|
3699
3775
|
await cli.moduleConnectionService.loadModule("dump.module");
|
|
3700
3776
|
{
|
|
@@ -3763,9 +3839,15 @@ const main$4 = async () => {
|
|
|
3763
3839
|
const cwd = process.cwd();
|
|
3764
3840
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3765
3841
|
}
|
|
3842
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3766
3843
|
{
|
|
3767
|
-
await cli.configConnectionService.loadConfig("
|
|
3768
|
-
|
|
3844
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3845
|
+
if (typeof loader === "function") {
|
|
3846
|
+
await loader();
|
|
3847
|
+
}
|
|
3848
|
+
if (typeof loader?.loader === "function") {
|
|
3849
|
+
await loader.loader();
|
|
3850
|
+
}
|
|
3769
3851
|
}
|
|
3770
3852
|
await cli.moduleConnectionService.loadModule("pnldebug.module");
|
|
3771
3853
|
{
|
|
@@ -4221,7 +4303,7 @@ const main$1 = async () => {
|
|
|
4221
4303
|
if (!values.help) {
|
|
4222
4304
|
return;
|
|
4223
4305
|
}
|
|
4224
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
4306
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n\n`);
|
|
4225
4307
|
process.stdout.write(HELP_TEXT);
|
|
4226
4308
|
process.exit(0);
|
|
4227
4309
|
};
|
|
@@ -4235,7 +4317,7 @@ const main = async () => {
|
|
|
4235
4317
|
if (!values.version) {
|
|
4236
4318
|
return;
|
|
4237
4319
|
}
|
|
4238
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
4320
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n`);
|
|
4239
4321
|
process.exit(0);
|
|
4240
4322
|
};
|
|
4241
4323
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -885,9 +885,15 @@ class BacktestMainService {
|
|
|
885
885
|
this.loggerService.log("backtestMainService run", {
|
|
886
886
|
payload,
|
|
887
887
|
});
|
|
888
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
888
889
|
{
|
|
889
|
-
await this.configConnectionService.loadConfig("
|
|
890
|
-
|
|
890
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
891
|
+
if (typeof loader === "function") {
|
|
892
|
+
await loader();
|
|
893
|
+
}
|
|
894
|
+
if (typeof loader?.loader === "function") {
|
|
895
|
+
await loader.loader();
|
|
896
|
+
}
|
|
891
897
|
}
|
|
892
898
|
{
|
|
893
899
|
await this.configService.waitForInit();
|
|
@@ -1007,9 +1013,15 @@ class WalkerMainService {
|
|
|
1007
1013
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1008
1014
|
this.run = singleshot(async (payload) => {
|
|
1009
1015
|
this.loggerService.log("walkerMainService run", { payload });
|
|
1016
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1010
1017
|
{
|
|
1011
|
-
await this.configConnectionService.loadConfig("
|
|
1012
|
-
|
|
1018
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1019
|
+
if (typeof loader === "function") {
|
|
1020
|
+
await loader();
|
|
1021
|
+
}
|
|
1022
|
+
if (typeof loader?.loader === "function") {
|
|
1023
|
+
await loader.loader();
|
|
1024
|
+
}
|
|
1013
1025
|
}
|
|
1014
1026
|
{
|
|
1015
1027
|
await this.configService.waitForInit();
|
|
@@ -1235,9 +1247,15 @@ class LiveMainService {
|
|
|
1235
1247
|
this.loggerService.log("liveMainService run", {
|
|
1236
1248
|
payload,
|
|
1237
1249
|
});
|
|
1250
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1238
1251
|
{
|
|
1239
|
-
await this.configConnectionService.loadConfig("
|
|
1240
|
-
|
|
1252
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1253
|
+
if (typeof loader === "function") {
|
|
1254
|
+
await loader();
|
|
1255
|
+
}
|
|
1256
|
+
if (typeof loader?.loader === "function") {
|
|
1257
|
+
await loader.loader();
|
|
1258
|
+
}
|
|
1241
1259
|
}
|
|
1242
1260
|
{
|
|
1243
1261
|
await this.configService.waitForInit();
|
|
@@ -1324,9 +1342,15 @@ class PaperMainService {
|
|
|
1324
1342
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1325
1343
|
this.run = singleshot(async (payload) => {
|
|
1326
1344
|
this.loggerService.log("paperMainService init");
|
|
1345
|
+
await this.configConnectionService.loadConfig("setup.config");
|
|
1327
1346
|
{
|
|
1328
|
-
await this.configConnectionService.loadConfig("
|
|
1329
|
-
|
|
1347
|
+
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
1348
|
+
if (typeof loader === "function") {
|
|
1349
|
+
await loader();
|
|
1350
|
+
}
|
|
1351
|
+
if (typeof loader?.loader === "function") {
|
|
1352
|
+
await loader.loader();
|
|
1353
|
+
}
|
|
1330
1354
|
}
|
|
1331
1355
|
{
|
|
1332
1356
|
await this.configService.waitForInit();
|
|
@@ -2598,14 +2622,29 @@ class BabelService {
|
|
|
2598
2622
|
}
|
|
2599
2623
|
}
|
|
2600
2624
|
|
|
2601
|
-
const require = createRequire(import.meta.url);
|
|
2602
2625
|
const ModuleWithCache = Module;
|
|
2626
|
+
const LOOKUP_PATHS = ModuleWithCache._nodeModulePaths(process.cwd());
|
|
2627
|
+
const VIRTUAL_MAP = new Map();
|
|
2628
|
+
{
|
|
2629
|
+
const originalResolveFilename = ModuleWithCache._resolveFilename;
|
|
2630
|
+
ModuleWithCache._resolveFilename = function (request, parent, isMain, options) {
|
|
2631
|
+
const virtualKey = VIRTUAL_MAP.get(request);
|
|
2632
|
+
if (virtualKey) {
|
|
2633
|
+
return virtualKey;
|
|
2634
|
+
}
|
|
2635
|
+
return originalResolveFilename.call(this, request, parent, isMain, options);
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2603
2638
|
function overrideModule(moduleName, newExports) {
|
|
2604
2639
|
const cache = ModuleWithCache._cache;
|
|
2605
|
-
const
|
|
2640
|
+
const resolved = ModuleWithCache._findPath(moduleName, LOOKUP_PATHS, false);
|
|
2641
|
+
const key = resolved || moduleName;
|
|
2642
|
+
VIRTUAL_MAP.set(moduleName, key);
|
|
2606
2643
|
if (!cache[key]) {
|
|
2607
2644
|
cache[key] = new ModuleWithCache(key);
|
|
2608
2645
|
cache[key].loaded = true;
|
|
2646
|
+
cache[key].filename = key;
|
|
2647
|
+
cache[key].paths = [];
|
|
2609
2648
|
}
|
|
2610
2649
|
cache[key].exports = newExports;
|
|
2611
2650
|
}
|
|
@@ -2884,8 +2923,14 @@ const GET_ALIAS_EXPORTS_FN = (self) => {
|
|
|
2884
2923
|
continue;
|
|
2885
2924
|
}
|
|
2886
2925
|
const instance = self.getInstance(baseDir);
|
|
2887
|
-
const
|
|
2888
|
-
|
|
2926
|
+
const alias = instance.import(filePath);
|
|
2927
|
+
if (!alias) {
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
if ("default" in alias) {
|
|
2931
|
+
return alias.default;
|
|
2932
|
+
}
|
|
2933
|
+
return alias;
|
|
2889
2934
|
}
|
|
2890
2935
|
return null;
|
|
2891
2936
|
};
|
|
@@ -2984,6 +3029,10 @@ class ConfigConnectionService {
|
|
|
2984
3029
|
const config = await LOAD_CONFIG_CONFIG_FN(fileName, this);
|
|
2985
3030
|
if (!config) {
|
|
2986
3031
|
this.loadConfig.clear(fileName);
|
|
3032
|
+
return null;
|
|
3033
|
+
}
|
|
3034
|
+
if ("default" in config) {
|
|
3035
|
+
return config.default;
|
|
2987
3036
|
}
|
|
2988
3037
|
return config;
|
|
2989
3038
|
});
|
|
@@ -3142,7 +3191,7 @@ const main$g = async () => {
|
|
|
3142
3191
|
if (MODES.some((mode) => values[mode])) {
|
|
3143
3192
|
return;
|
|
3144
3193
|
}
|
|
3145
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
3194
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n`);
|
|
3146
3195
|
process.stdout.write("\n");
|
|
3147
3196
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3148
3197
|
process.stdout.write("\n");
|
|
@@ -3433,9 +3482,15 @@ const main$a = async () => {
|
|
|
3433
3482
|
}
|
|
3434
3483
|
await flush(entryPoint);
|
|
3435
3484
|
}
|
|
3485
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3436
3486
|
{
|
|
3437
|
-
await cli.configConnectionService.loadConfig("
|
|
3438
|
-
|
|
3487
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3488
|
+
if (typeof loader === "function") {
|
|
3489
|
+
await loader();
|
|
3490
|
+
}
|
|
3491
|
+
if (typeof loader?.loader === "function") {
|
|
3492
|
+
await loader.loader();
|
|
3493
|
+
}
|
|
3439
3494
|
}
|
|
3440
3495
|
{
|
|
3441
3496
|
await cli.configService.waitForInit();
|
|
@@ -3447,6 +3502,9 @@ const main$a = async () => {
|
|
|
3447
3502
|
const cwd = process.cwd();
|
|
3448
3503
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3449
3504
|
}
|
|
3505
|
+
if (entryPoints.length === 1) {
|
|
3506
|
+
process.chdir(path.dirname(path.resolve(entryPoints[0])));
|
|
3507
|
+
}
|
|
3450
3508
|
await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
|
|
3451
3509
|
listenFinish();
|
|
3452
3510
|
createGracefulShutdown(mode)();
|
|
@@ -3537,9 +3595,15 @@ const main$7 = async () => {
|
|
|
3537
3595
|
const cwd = process.cwd();
|
|
3538
3596
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3539
3597
|
}
|
|
3598
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3540
3599
|
{
|
|
3541
|
-
await cli.configConnectionService.loadConfig("
|
|
3542
|
-
|
|
3600
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3601
|
+
if (typeof loader === "function") {
|
|
3602
|
+
await loader();
|
|
3603
|
+
}
|
|
3604
|
+
if (typeof loader?.loader === "function") {
|
|
3605
|
+
await loader.loader();
|
|
3606
|
+
}
|
|
3543
3607
|
}
|
|
3544
3608
|
await cli.moduleConnectionService.loadModule("pine.module");
|
|
3545
3609
|
{
|
|
@@ -3618,9 +3682,15 @@ const main$6 = async () => {
|
|
|
3618
3682
|
console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
|
|
3619
3683
|
process.exit(1);
|
|
3620
3684
|
}
|
|
3685
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3621
3686
|
{
|
|
3622
|
-
await cli.configConnectionService.loadConfig("
|
|
3623
|
-
|
|
3687
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3688
|
+
if (typeof loader === "function") {
|
|
3689
|
+
await loader();
|
|
3690
|
+
}
|
|
3691
|
+
if (typeof loader?.loader === "function") {
|
|
3692
|
+
await loader.loader();
|
|
3693
|
+
}
|
|
3624
3694
|
}
|
|
3625
3695
|
{
|
|
3626
3696
|
await cli.configService.waitForInit();
|
|
@@ -3663,9 +3733,15 @@ const main$5 = async () => {
|
|
|
3663
3733
|
const cwd = process.cwd();
|
|
3664
3734
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3665
3735
|
}
|
|
3736
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3666
3737
|
{
|
|
3667
|
-
await cli.configConnectionService.loadConfig("
|
|
3668
|
-
|
|
3738
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3739
|
+
if (typeof loader === "function") {
|
|
3740
|
+
await loader();
|
|
3741
|
+
}
|
|
3742
|
+
if (typeof loader?.loader === "function") {
|
|
3743
|
+
await loader.loader();
|
|
3744
|
+
}
|
|
3669
3745
|
}
|
|
3670
3746
|
await cli.moduleConnectionService.loadModule("dump.module");
|
|
3671
3747
|
{
|
|
@@ -3734,9 +3810,15 @@ const main$4 = async () => {
|
|
|
3734
3810
|
const cwd = process.cwd();
|
|
3735
3811
|
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3736
3812
|
}
|
|
3813
|
+
await cli.configConnectionService.loadConfig("setup.config");
|
|
3737
3814
|
{
|
|
3738
|
-
await cli.configConnectionService.loadConfig("
|
|
3739
|
-
|
|
3815
|
+
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
3816
|
+
if (typeof loader === "function") {
|
|
3817
|
+
await loader();
|
|
3818
|
+
}
|
|
3819
|
+
if (typeof loader?.loader === "function") {
|
|
3820
|
+
await loader.loader();
|
|
3821
|
+
}
|
|
3740
3822
|
}
|
|
3741
3823
|
await cli.moduleConnectionService.loadModule("pnldebug.module");
|
|
3742
3824
|
{
|
|
@@ -4192,7 +4274,7 @@ const main$1 = async () => {
|
|
|
4192
4274
|
if (!values.help) {
|
|
4193
4275
|
return;
|
|
4194
4276
|
}
|
|
4195
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
4277
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n\n`);
|
|
4196
4278
|
process.stdout.write(HELP_TEXT);
|
|
4197
4279
|
process.exit(0);
|
|
4198
4280
|
};
|
|
@@ -4206,7 +4288,7 @@ const main = async () => {
|
|
|
4206
4288
|
if (!values.version) {
|
|
4207
4289
|
return;
|
|
4208
4290
|
}
|
|
4209
|
-
process.stdout.write(`@backtest-kit/cli ${"9.
|
|
4291
|
+
process.stdout.write(`@backtest-kit/cli ${"9.8.0"}\n`);
|
|
4210
4292
|
process.exit(0);
|
|
4211
4293
|
};
|
|
4212
4294
|
main();
|
package/docker/README.md
CHANGED
|
@@ -19,6 +19,7 @@ A self-contained Docker workspace for running [backtest-kit](https://github.com/
|
|
|
19
19
|
| Strategy | Ticker | Period | Signal source | Net PNL | Sharpe |
|
|
20
20
|
|---|---|---|---|---|---|
|
|
21
21
|
| [DOTUSDT Feb 2021 β Python EMA Crossover](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/feb_2021.strategy) | DOTUSDT | Feb 2021 | Python EMA(9)/EMA(21) crossover via WebAssembly | **+5.52%** | **0.09** |
|
|
22
|
+
| [BTCUSDT Apr 2024 β Polymarket Ξprob](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/apr_2024.strategy) | BTCUSDT | Apr 2024 | Polymarket "yes" probability shifts on crypto-prices markets | **+0.63%** | **0.055** |
|
|
22
23
|
| [BTCUSDT Oct 2021 β TensorFlow Neural Network](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/oct_2021.strategy) | BTCUSDT | Oct 2021 | TensorFlow NN predicting next candle close | **+18.26%** | **0.31** |
|
|
23
24
|
| [BTCUSDT Dec 2025 β Pine Script Range Breakout](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/dec_2025.strategy) | BTCUSDT | Dec 2025 | Pine Script BB + range detector + volume spike | **+2.40%** | **0.06** |
|
|
24
25
|
| [TRXUSDT Jan 2026 β Liquidity Harvesting](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content//jan_2026.strategy) | TRXUSDT | Jan 2026 | Telegram channel signals (inverted) | **+8.58%** | **1.14** |
|
package/docker/package.json
CHANGED
|
@@ -15,17 +15,17 @@
|
|
|
15
15
|
"@types/node": "25.6.0"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@backtest-kit/cli": "9.
|
|
19
|
-
"@backtest-kit/graph": "9.
|
|
20
|
-
"@backtest-kit/pinets": "9.
|
|
21
|
-
"@backtest-kit/signals": "9.
|
|
22
|
-
"@backtest-kit/ui": "9.
|
|
18
|
+
"@backtest-kit/cli": "9.8.0",
|
|
19
|
+
"@backtest-kit/graph": "9.8.0",
|
|
20
|
+
"@backtest-kit/pinets": "9.8.0",
|
|
21
|
+
"@backtest-kit/signals": "9.8.0",
|
|
22
|
+
"@backtest-kit/ui": "9.8.0",
|
|
23
23
|
"@tavily/core": "0.7.2",
|
|
24
24
|
"@tensorflow/tfjs": "4.22.0",
|
|
25
25
|
"@tensorflow/tfjs-backend-wasm": "4.22.0",
|
|
26
26
|
"@tensorflow/tfjs-core": "4.22.0",
|
|
27
27
|
"agent-swarm-kit": "2.6.0",
|
|
28
|
-
"backtest-kit": "9.
|
|
28
|
+
"backtest-kit": "9.8.0",
|
|
29
29
|
"dayjs": "1.11.20",
|
|
30
30
|
"functools-kit": "2.3.0",
|
|
31
31
|
"garch": "1.2.3",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.8.0",
|
|
4
4
|
"description": "Zero-boilerplate CLI runner for backtest-kit strategies. Run backtests, paper trading, and live bots with candle cache warming, web dashboard, and Telegram notifications β no setup code required.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Petr Tripolsky",
|
|
@@ -63,11 +63,11 @@
|
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@babel/plugin-transform-modules-umd": "7.27.1",
|
|
65
65
|
"@babel/standalone": "7.29.1",
|
|
66
|
-
"@backtest-kit/graph": "9.
|
|
67
|
-
"@backtest-kit/ollama": "9.
|
|
68
|
-
"@backtest-kit/pinets": "9.
|
|
69
|
-
"@backtest-kit/signals": "9.
|
|
70
|
-
"@backtest-kit/ui": "9.
|
|
66
|
+
"@backtest-kit/graph": "9.8.0",
|
|
67
|
+
"@backtest-kit/ollama": "9.8.0",
|
|
68
|
+
"@backtest-kit/pinets": "9.8.0",
|
|
69
|
+
"@backtest-kit/signals": "9.8.0",
|
|
70
|
+
"@backtest-kit/ui": "9.8.0",
|
|
71
71
|
"@rollup/plugin-replace": "6.0.3",
|
|
72
72
|
"@rollup/plugin-typescript": "11.1.6",
|
|
73
73
|
"@types/image-size": "0.7.0",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@types/mustache": "4.2.6",
|
|
76
76
|
"@types/node": "22.9.0",
|
|
77
77
|
"@types/stack-trace": "0.0.33",
|
|
78
|
-
"backtest-kit": "9.
|
|
78
|
+
"backtest-kit": "9.8.0",
|
|
79
79
|
"glob": "11.0.1",
|
|
80
80
|
"markdown-it": "14.1.1",
|
|
81
81
|
"rimraf": "6.0.1",
|
|
@@ -90,12 +90,12 @@
|
|
|
90
90
|
"peerDependencies": {
|
|
91
91
|
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
|
92
92
|
"@babel/standalone": "^7.29.1",
|
|
93
|
-
"@backtest-kit/graph": "^9.
|
|
94
|
-
"@backtest-kit/ollama": "^9.
|
|
95
|
-
"@backtest-kit/pinets": "^9.
|
|
96
|
-
"@backtest-kit/signals": "^9.
|
|
97
|
-
"@backtest-kit/ui": "^9.
|
|
98
|
-
"backtest-kit": "^9.
|
|
93
|
+
"@backtest-kit/graph": "^9.8.0",
|
|
94
|
+
"@backtest-kit/ollama": "^9.8.0",
|
|
95
|
+
"@backtest-kit/pinets": "^9.8.0",
|
|
96
|
+
"@backtest-kit/signals": "^9.8.0",
|
|
97
|
+
"@backtest-kit/ui": "^9.8.0",
|
|
98
|
+
"backtest-kit": "^9.8.0",
|
|
99
99
|
"markdown-it": "^14.1.1",
|
|
100
100
|
"typescript": "^5.0.0"
|
|
101
101
|
},
|
|
@@ -17,6 +17,7 @@ A minimal project scaffold for [backtest-kit](https://github.com/tripolskypetr/b
|
|
|
17
17
|
| Strategy | Ticker | Period | Signal source | Net PNL | Sharpe |
|
|
18
18
|
|---|---|---|---|---|---|
|
|
19
19
|
| [DOTUSDT Feb 2021 β Python EMA Crossover](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/feb_2021.strategy) | DOTUSDT | Feb 2021 | Python EMA(9)/EMA(21) crossover via WebAssembly | **+5.52%** | **0.09** |
|
|
20
|
+
| [BTCUSDT Apr 2024 β Polymarket Ξprob](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/apr_2024.strategy) | BTCUSDT | Apr 2024 | Polymarket "yes" probability shifts on crypto-prices markets | **+0.63%** | **0.055** |
|
|
20
21
|
| [BTCUSDT Oct 2021 β TensorFlow Neural Network](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/oct_2021.strategy) | BTCUSDT | Oct 2021 | TensorFlow NN predicting next candle close | **+18.26%** | **0.31** |
|
|
21
22
|
| [BTCUSDT Dec 2025 β Pine Script Range Breakout](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content/dec_2025.strategy) | BTCUSDT | Dec 2025 | Pine Script BB + range detector + volume spike | **+2.40%** | **0.06** |
|
|
22
23
|
| [TRXUSDT Jan 2026 β Liquidity Harvesting](https://github.com/tripolskypetr/backtest-kit/tree/master/example/content//jan_2026.strategy) | TRXUSDT | Jan 2026 | Telegram channel signals (inverted) | **+8.58%** | **1.14** |
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"type": "commonjs",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@backtest-kit/cli": "^9.
|
|
17
|
-
"@backtest-kit/graph": "^9.
|
|
18
|
-
"@backtest-kit/pinets": "^9.
|
|
19
|
-
"@backtest-kit/ui": "^9.
|
|
16
|
+
"@backtest-kit/cli": "^9.8.0",
|
|
17
|
+
"@backtest-kit/graph": "^9.8.0",
|
|
18
|
+
"@backtest-kit/pinets": "^9.8.0",
|
|
19
|
+
"@backtest-kit/ui": "^9.8.0",
|
|
20
20
|
"agent-swarm-kit": "^2.6.0",
|
|
21
|
-
"backtest-kit": "^9.
|
|
21
|
+
"backtest-kit": "^9.8.0",
|
|
22
22
|
"functools-kit": "^2.3.0",
|
|
23
23
|
"garch": "^1.2.3",
|
|
24
24
|
"get-moment-stamp": "^1.1.2",
|
package/types.d.ts
CHANGED
|
@@ -240,17 +240,12 @@ declare class SymbolSchemaService {
|
|
|
240
240
|
addSchema: (() => Promise<void>) & functools_kit.ISingleshotClearable<() => Promise<void>>;
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
type ModuleExports = {
|
|
244
|
-
[key: string]: any;
|
|
245
|
-
default?: any;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
243
|
declare class ConfigConnectionService {
|
|
249
244
|
readonly loggerService: LoggerService;
|
|
250
245
|
readonly resolveService: ResolveService;
|
|
251
246
|
readonly loaderService: LoaderService;
|
|
252
247
|
hasConfig: (fileName: string) => boolean;
|
|
253
|
-
loadConfig: ((fileName: string) => Promise<
|
|
248
|
+
loadConfig: ((fileName: string) => Promise<any>) & functools_kit.IClearableMemoize<string> & functools_kit.IControlMemoize<string, Promise<any>>;
|
|
254
249
|
}
|
|
255
250
|
|
|
256
251
|
declare class FrontendProviderService {
|