@backtest-kit/cli 6.7.0 → 6.7.2
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 +61 -0
- package/build/index.cjs +39 -3
- package/build/index.mjs +39 -3
- package/package.json +1 -1
- package/template/project/package.mustache +1 -1
- package/types.d.ts +11 -1
package/README.md
CHANGED
|
@@ -341,6 +341,67 @@ npm run backtest:dec
|
|
|
341
341
|
|
|
342
342
|
Each strategy run produces its own `dump/` directory, making it straightforward to compare results across time periods — both by inspection and by pointing an AI agent at a specific strategy folder.
|
|
343
343
|
|
|
344
|
+
## 🔗 Shared Import Aliases
|
|
345
|
+
|
|
346
|
+
`@backtest-kit/cli` automatically turns every **top-level folder in `cwd`** into a bare import alias available inside any strategy file. No configuration needed — just create the folder.
|
|
347
|
+
|
|
348
|
+
### How It Works
|
|
349
|
+
|
|
350
|
+
When the CLI loads a strategy file, it scans the current working directory for subdirectories and registers each one as an import alias. The alias name is the folder name. Both barrel imports and deep subpath imports are supported:
|
|
351
|
+
|
|
352
|
+
| Import | Resolves to |
|
|
353
|
+
|--------|-------------|
|
|
354
|
+
| `import { fn } from "utils"` | `<cwd>/utils/index.ts` (or `.js`, `.mjs`, `.cjs`) |
|
|
355
|
+
| `import { calcRSI } from "math/rsi"` | `<cwd>/math/rsi.ts` |
|
|
356
|
+
| `import { research } from "logic"` | `<cwd>/logic/index.ts` |
|
|
357
|
+
| `import { ResearchResponseContract } from "logic/contract/ResearchResponse.contract"` | `<cwd>/logic/contract/ResearchResponse.contract.ts` |
|
|
358
|
+
|
|
359
|
+
### Project Structure
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
my-project/
|
|
363
|
+
├── utils/ ← import { formatDate } from "utils"
|
|
364
|
+
│ └── index.ts
|
|
365
|
+
├── math/ ← import { calcRSI } from "math/rsi"
|
|
366
|
+
│ └── rsi.ts
|
|
367
|
+
├── logic/ ← import { research } from "logic"
|
|
368
|
+
│ ├── index.ts ← barrel
|
|
369
|
+
│ └── contract/
|
|
370
|
+
│ └── ResearchResponse.contract.ts ← import { ... } from "logic/contract/ResearchResponse.contract"
|
|
371
|
+
└── content/
|
|
372
|
+
├── feb_2026.strategy.ts ← uses all three aliases freely
|
|
373
|
+
└── mar_2026.strategy.ts ← same aliases, no duplication
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
This lets you extract shared utilities, math helpers, or AI agent logic (e.g. `agent-swarm-kit` workflows) into named folders and reuse them across every strategy in the project without relative path hell.
|
|
377
|
+
|
|
378
|
+
### TypeScript Support
|
|
379
|
+
|
|
380
|
+
Add a matching `paths` entry to your `tsconfig.json` so the editor resolves the aliases:
|
|
381
|
+
|
|
382
|
+
```json
|
|
383
|
+
{
|
|
384
|
+
"compilerOptions": {
|
|
385
|
+
"moduleResolution": "bundler",
|
|
386
|
+
"paths": {
|
|
387
|
+
"logic": ["./logic/index.ts"],
|
|
388
|
+
"logic/*": ["./logic/*"],
|
|
389
|
+
"math": ["./math/index.ts"],
|
|
390
|
+
"math/*": ["./math/*"],
|
|
391
|
+
"utils": ["./utils/index.ts"],
|
|
392
|
+
"utils/*": ["./utils/*"]
|
|
393
|
+
}
|
|
394
|
+
},
|
|
395
|
+
"include": [
|
|
396
|
+
"./logic",
|
|
397
|
+
"./math",
|
|
398
|
+
"./utils",
|
|
399
|
+
"./content",
|
|
400
|
+
"./modules",
|
|
401
|
+
],
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
344
405
|
## 🔔 Integrations
|
|
345
406
|
|
|
346
407
|
### Web Dashboard (`--ui`)
|
package/build/index.cjs
CHANGED
|
@@ -239,6 +239,7 @@ class ResolveService {
|
|
|
239
239
|
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$2, '..', 'modules');
|
|
240
240
|
this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
|
|
241
241
|
this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
|
|
242
|
+
this.IMPORT_PATHS_DIR = process.cwd();
|
|
242
243
|
this.getIsLaunched = () => {
|
|
243
244
|
this.loggerService.log("resolveService getIsLaunched");
|
|
244
245
|
return _is_launched;
|
|
@@ -2107,6 +2108,8 @@ class BabelService {
|
|
|
2107
2108
|
}
|
|
2108
2109
|
}
|
|
2109
2110
|
|
|
2111
|
+
const USE_ESMODULE_DEFAULT = false;
|
|
2112
|
+
const IMPORT_PATHS_EXCLUDE = new Set(["dump", "logs", "node_modules"]);
|
|
2110
2113
|
const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
2111
2114
|
const __filename = self.__filename;
|
|
2112
2115
|
const __dirname = self.__dirname;
|
|
@@ -2142,6 +2145,9 @@ const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2142
2145
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
2143
2146
|
const child = self.fork(path.dirname(resolvedPath));
|
|
2144
2147
|
const { module } = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
2148
|
+
if (!USE_ESMODULE_DEFAULT) {
|
|
2149
|
+
return module.exports;
|
|
2150
|
+
}
|
|
2145
2151
|
return "default" in module.exports
|
|
2146
2152
|
? module.exports.default
|
|
2147
2153
|
: module.exports;
|
|
@@ -2202,6 +2208,20 @@ const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2202
2208
|
}
|
|
2203
2209
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
2204
2210
|
};
|
|
2211
|
+
const READ_IMPORT_PATHS_MAP_FN = functoolsKit.singleshot((importPathsDir) => {
|
|
2212
|
+
const entries = fs.readdirSync(importPathsDir, { withFileTypes: true });
|
|
2213
|
+
const map = {};
|
|
2214
|
+
for (const entry of entries) {
|
|
2215
|
+
if (!entry.isDirectory()) {
|
|
2216
|
+
continue;
|
|
2217
|
+
}
|
|
2218
|
+
if (IMPORT_PATHS_EXCLUDE.has(entry.name)) {
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
map[entry.name] = path.join(importPathsDir, entry.name);
|
|
2222
|
+
}
|
|
2223
|
+
return map;
|
|
2224
|
+
});
|
|
2205
2225
|
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2206
2226
|
const baseRequire = self.baseRequire();
|
|
2207
2227
|
return new Proxy(baseRequire, {
|
|
@@ -2226,6 +2246,19 @@ const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
|
2226
2246
|
const child = self.fork(path.dirname(resolved));
|
|
2227
2247
|
return child.import(resolved, seen);
|
|
2228
2248
|
}
|
|
2249
|
+
const importPathsMap = READ_IMPORT_PATHS_MAP_FN(self.params.resolve.IMPORT_PATHS_DIR);
|
|
2250
|
+
if (id in importPathsMap) {
|
|
2251
|
+
const resolved = importPathsMap[id];
|
|
2252
|
+
const child = self.fork(resolved);
|
|
2253
|
+
return child.import(resolved, seen);
|
|
2254
|
+
}
|
|
2255
|
+
const importPathsKey = Object.keys(importPathsMap).find((key) => id === key || id.startsWith(`${key}/`));
|
|
2256
|
+
if (importPathsKey) {
|
|
2257
|
+
const subPath = id.slice(importPathsKey.length);
|
|
2258
|
+
const resolved = path.join(importPathsMap[importPathsKey], subPath);
|
|
2259
|
+
const child = self.fork(path.dirname(resolved));
|
|
2260
|
+
return child.import(resolved, seen);
|
|
2261
|
+
}
|
|
2229
2262
|
return baseRequire(id);
|
|
2230
2263
|
},
|
|
2231
2264
|
});
|
|
@@ -2256,6 +2289,7 @@ class ClientLoader {
|
|
|
2256
2289
|
path: basePath,
|
|
2257
2290
|
babel: this.params.babel,
|
|
2258
2291
|
logger: this.params.logger,
|
|
2292
|
+
resolve: this.params.resolve,
|
|
2259
2293
|
});
|
|
2260
2294
|
}
|
|
2261
2295
|
import(filePath, seen = new Set()) {
|
|
@@ -2300,9 +2334,11 @@ class LoaderService {
|
|
|
2300
2334
|
constructor() {
|
|
2301
2335
|
this.babelService = inject(TYPES.babelService);
|
|
2302
2336
|
this.loggerService = inject(TYPES.loggerService);
|
|
2337
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
2303
2338
|
this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) => new ClientLoader({
|
|
2304
2339
|
babel: this.babelService,
|
|
2305
2340
|
logger: this.loggerService,
|
|
2341
|
+
resolve: this.resolveService,
|
|
2306
2342
|
path: basePath,
|
|
2307
2343
|
}));
|
|
2308
2344
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
@@ -2537,7 +2573,7 @@ const main$b = async () => {
|
|
|
2537
2573
|
if (MODES.some((mode) => values[mode])) {
|
|
2538
2574
|
return;
|
|
2539
2575
|
}
|
|
2540
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
2576
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
2541
2577
|
process.stdout.write("\n");
|
|
2542
2578
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2543
2579
|
process.stdout.write("\n");
|
|
@@ -3105,7 +3141,7 @@ const main$1 = async () => {
|
|
|
3105
3141
|
if (!values.help) {
|
|
3106
3142
|
return;
|
|
3107
3143
|
}
|
|
3108
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3144
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n\n`);
|
|
3109
3145
|
process.stdout.write(HELP_TEXT);
|
|
3110
3146
|
process.exit(0);
|
|
3111
3147
|
};
|
|
@@ -3119,7 +3155,7 @@ const main = async () => {
|
|
|
3119
3155
|
if (!values.version) {
|
|
3120
3156
|
return;
|
|
3121
3157
|
}
|
|
3122
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3158
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
3123
3159
|
process.exit(0);
|
|
3124
3160
|
};
|
|
3125
3161
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -214,6 +214,7 @@ class ResolveService {
|
|
|
214
214
|
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$1, '..', 'modules');
|
|
215
215
|
this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
|
|
216
216
|
this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
|
|
217
|
+
this.IMPORT_PATHS_DIR = process.cwd();
|
|
217
218
|
this.getIsLaunched = () => {
|
|
218
219
|
this.loggerService.log("resolveService getIsLaunched");
|
|
219
220
|
return _is_launched;
|
|
@@ -2082,6 +2083,8 @@ class BabelService {
|
|
|
2082
2083
|
}
|
|
2083
2084
|
}
|
|
2084
2085
|
|
|
2086
|
+
const USE_ESMODULE_DEFAULT = false;
|
|
2087
|
+
const IMPORT_PATHS_EXCLUDE = new Set(["dump", "logs", "node_modules"]);
|
|
2085
2088
|
const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
2086
2089
|
const __filename = self.__filename;
|
|
2087
2090
|
const __dirname = self.__dirname;
|
|
@@ -2113,6 +2116,9 @@ const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2113
2116
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
2114
2117
|
const child = self.fork(path.dirname(resolvedPath));
|
|
2115
2118
|
const { module } = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
2119
|
+
if (!USE_ESMODULE_DEFAULT) {
|
|
2120
|
+
return module.exports;
|
|
2121
|
+
}
|
|
2116
2122
|
return "default" in module.exports
|
|
2117
2123
|
? module.exports.default
|
|
2118
2124
|
: module.exports;
|
|
@@ -2173,6 +2179,20 @@ const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2173
2179
|
}
|
|
2174
2180
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
2175
2181
|
};
|
|
2182
|
+
const READ_IMPORT_PATHS_MAP_FN = singleshot((importPathsDir) => {
|
|
2183
|
+
const entries = fs.readdirSync(importPathsDir, { withFileTypes: true });
|
|
2184
|
+
const map = {};
|
|
2185
|
+
for (const entry of entries) {
|
|
2186
|
+
if (!entry.isDirectory()) {
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (IMPORT_PATHS_EXCLUDE.has(entry.name)) {
|
|
2190
|
+
continue;
|
|
2191
|
+
}
|
|
2192
|
+
map[entry.name] = path.join(importPathsDir, entry.name);
|
|
2193
|
+
}
|
|
2194
|
+
return map;
|
|
2195
|
+
});
|
|
2176
2196
|
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2177
2197
|
const baseRequire = self.baseRequire();
|
|
2178
2198
|
return new Proxy(baseRequire, {
|
|
@@ -2197,6 +2217,19 @@ const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
|
2197
2217
|
const child = self.fork(path.dirname(resolved));
|
|
2198
2218
|
return child.import(resolved, seen);
|
|
2199
2219
|
}
|
|
2220
|
+
const importPathsMap = READ_IMPORT_PATHS_MAP_FN(self.params.resolve.IMPORT_PATHS_DIR);
|
|
2221
|
+
if (id in importPathsMap) {
|
|
2222
|
+
const resolved = importPathsMap[id];
|
|
2223
|
+
const child = self.fork(resolved);
|
|
2224
|
+
return child.import(resolved, seen);
|
|
2225
|
+
}
|
|
2226
|
+
const importPathsKey = Object.keys(importPathsMap).find((key) => id === key || id.startsWith(`${key}/`));
|
|
2227
|
+
if (importPathsKey) {
|
|
2228
|
+
const subPath = id.slice(importPathsKey.length);
|
|
2229
|
+
const resolved = path.join(importPathsMap[importPathsKey], subPath);
|
|
2230
|
+
const child = self.fork(path.dirname(resolved));
|
|
2231
|
+
return child.import(resolved, seen);
|
|
2232
|
+
}
|
|
2200
2233
|
return baseRequire(id);
|
|
2201
2234
|
},
|
|
2202
2235
|
});
|
|
@@ -2227,6 +2260,7 @@ class ClientLoader {
|
|
|
2227
2260
|
path: basePath,
|
|
2228
2261
|
babel: this.params.babel,
|
|
2229
2262
|
logger: this.params.logger,
|
|
2263
|
+
resolve: this.params.resolve,
|
|
2230
2264
|
});
|
|
2231
2265
|
}
|
|
2232
2266
|
import(filePath, seen = new Set()) {
|
|
@@ -2271,9 +2305,11 @@ class LoaderService {
|
|
|
2271
2305
|
constructor() {
|
|
2272
2306
|
this.babelService = inject(TYPES.babelService);
|
|
2273
2307
|
this.loggerService = inject(TYPES.loggerService);
|
|
2308
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
2274
2309
|
this.getInstance = memoize(([basePath]) => `${basePath}`, (basePath) => new ClientLoader({
|
|
2275
2310
|
babel: this.babelService,
|
|
2276
2311
|
logger: this.loggerService,
|
|
2312
|
+
resolve: this.resolveService,
|
|
2277
2313
|
path: basePath,
|
|
2278
2314
|
}));
|
|
2279
2315
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
@@ -2508,7 +2544,7 @@ const main$b = async () => {
|
|
|
2508
2544
|
if (MODES.some((mode) => values[mode])) {
|
|
2509
2545
|
return;
|
|
2510
2546
|
}
|
|
2511
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
2547
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
2512
2548
|
process.stdout.write("\n");
|
|
2513
2549
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2514
2550
|
process.stdout.write("\n");
|
|
@@ -3076,7 +3112,7 @@ const main$1 = async () => {
|
|
|
3076
3112
|
if (!values.help) {
|
|
3077
3113
|
return;
|
|
3078
3114
|
}
|
|
3079
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3115
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n\n`);
|
|
3080
3116
|
process.stdout.write(HELP_TEXT);
|
|
3081
3117
|
process.exit(0);
|
|
3082
3118
|
};
|
|
@@ -3090,7 +3126,7 @@ const main = async () => {
|
|
|
3090
3126
|
if (!values.version) {
|
|
3091
3127
|
return;
|
|
3092
3128
|
}
|
|
3093
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3129
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
3094
3130
|
process.exit(0);
|
|
3095
3131
|
};
|
|
3096
3132
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backtest-kit/cli",
|
|
3
|
-
"version": "6.7.
|
|
3
|
+
"version": "6.7.2",
|
|
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",
|
package/types.d.ts
CHANGED
|
@@ -110,18 +110,28 @@ declare class FrameSchemaService {
|
|
|
110
110
|
declare class LoaderService {
|
|
111
111
|
private readonly babelService;
|
|
112
112
|
private readonly loggerService;
|
|
113
|
+
private readonly resolveService;
|
|
113
114
|
private getInstance;
|
|
114
115
|
import: (filePath: string, basePath?: string) => any;
|
|
115
116
|
check: (filePath: string, basePath?: string) => Promise<boolean>;
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
interface IResolve {
|
|
120
|
+
DEFAULT_TEMPLATE_DIR: string;
|
|
121
|
+
DEFAULT_MODULES_DIR: string;
|
|
122
|
+
OVERRIDE_TEMPLATE_DIR: string;
|
|
123
|
+
OVERRIDE_MODULES_DIR: string;
|
|
124
|
+
IMPORT_PATHS_DIR: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare class ResolveService implements IResolve {
|
|
119
128
|
readonly loggerService: LoggerService;
|
|
120
129
|
readonly loaderService: LoaderService;
|
|
121
130
|
readonly DEFAULT_TEMPLATE_DIR: string;
|
|
122
131
|
readonly DEFAULT_MODULES_DIR: string;
|
|
123
132
|
readonly OVERRIDE_TEMPLATE_DIR: string;
|
|
124
133
|
readonly OVERRIDE_MODULES_DIR: string;
|
|
134
|
+
readonly IMPORT_PATHS_DIR: string;
|
|
125
135
|
getIsLaunched: () => boolean;
|
|
126
136
|
attachPine: (pinePath: string) => Promise<string>;
|
|
127
137
|
attachStrategy: (jsPath: string) => Promise<void>;
|