@backtest-kit/cli 6.7.1 → 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 +35 -3
- package/build/index.mjs +35 -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;
|
|
@@ -2108,6 +2109,7 @@ class BabelService {
|
|
|
2108
2109
|
}
|
|
2109
2110
|
|
|
2110
2111
|
const USE_ESMODULE_DEFAULT = false;
|
|
2112
|
+
const IMPORT_PATHS_EXCLUDE = new Set(["dump", "logs", "node_modules"]);
|
|
2111
2113
|
const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
2112
2114
|
const __filename = self.__filename;
|
|
2113
2115
|
const __dirname = self.__dirname;
|
|
@@ -2206,6 +2208,20 @@ const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2206
2208
|
}
|
|
2207
2209
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
2208
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
|
+
});
|
|
2209
2225
|
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2210
2226
|
const baseRequire = self.baseRequire();
|
|
2211
2227
|
return new Proxy(baseRequire, {
|
|
@@ -2230,6 +2246,19 @@ const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
|
2230
2246
|
const child = self.fork(path.dirname(resolved));
|
|
2231
2247
|
return child.import(resolved, seen);
|
|
2232
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
|
+
}
|
|
2233
2262
|
return baseRequire(id);
|
|
2234
2263
|
},
|
|
2235
2264
|
});
|
|
@@ -2260,6 +2289,7 @@ class ClientLoader {
|
|
|
2260
2289
|
path: basePath,
|
|
2261
2290
|
babel: this.params.babel,
|
|
2262
2291
|
logger: this.params.logger,
|
|
2292
|
+
resolve: this.params.resolve,
|
|
2263
2293
|
});
|
|
2264
2294
|
}
|
|
2265
2295
|
import(filePath, seen = new Set()) {
|
|
@@ -2304,9 +2334,11 @@ class LoaderService {
|
|
|
2304
2334
|
constructor() {
|
|
2305
2335
|
this.babelService = inject(TYPES.babelService);
|
|
2306
2336
|
this.loggerService = inject(TYPES.loggerService);
|
|
2337
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
2307
2338
|
this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) => new ClientLoader({
|
|
2308
2339
|
babel: this.babelService,
|
|
2309
2340
|
logger: this.loggerService,
|
|
2341
|
+
resolve: this.resolveService,
|
|
2310
2342
|
path: basePath,
|
|
2311
2343
|
}));
|
|
2312
2344
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
@@ -2541,7 +2573,7 @@ const main$b = async () => {
|
|
|
2541
2573
|
if (MODES.some((mode) => values[mode])) {
|
|
2542
2574
|
return;
|
|
2543
2575
|
}
|
|
2544
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
2576
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
2545
2577
|
process.stdout.write("\n");
|
|
2546
2578
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2547
2579
|
process.stdout.write("\n");
|
|
@@ -3109,7 +3141,7 @@ const main$1 = async () => {
|
|
|
3109
3141
|
if (!values.help) {
|
|
3110
3142
|
return;
|
|
3111
3143
|
}
|
|
3112
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3144
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n\n`);
|
|
3113
3145
|
process.stdout.write(HELP_TEXT);
|
|
3114
3146
|
process.exit(0);
|
|
3115
3147
|
};
|
|
@@ -3123,7 +3155,7 @@ const main = async () => {
|
|
|
3123
3155
|
if (!values.version) {
|
|
3124
3156
|
return;
|
|
3125
3157
|
}
|
|
3126
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3158
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
3127
3159
|
process.exit(0);
|
|
3128
3160
|
};
|
|
3129
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;
|
|
@@ -2083,6 +2084,7 @@ class BabelService {
|
|
|
2083
2084
|
}
|
|
2084
2085
|
|
|
2085
2086
|
const USE_ESMODULE_DEFAULT = false;
|
|
2087
|
+
const IMPORT_PATHS_EXCLUDE = new Set(["dump", "logs", "node_modules"]);
|
|
2086
2088
|
const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
2087
2089
|
const __filename = self.__filename;
|
|
2088
2090
|
const __dirname = self.__dirname;
|
|
@@ -2177,6 +2179,20 @@ const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
|
2177
2179
|
}
|
|
2178
2180
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
2179
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
|
+
});
|
|
2180
2196
|
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2181
2197
|
const baseRequire = self.baseRequire();
|
|
2182
2198
|
return new Proxy(baseRequire, {
|
|
@@ -2201,6 +2217,19 @@ const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
|
2201
2217
|
const child = self.fork(path.dirname(resolved));
|
|
2202
2218
|
return child.import(resolved, seen);
|
|
2203
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
|
+
}
|
|
2204
2233
|
return baseRequire(id);
|
|
2205
2234
|
},
|
|
2206
2235
|
});
|
|
@@ -2231,6 +2260,7 @@ class ClientLoader {
|
|
|
2231
2260
|
path: basePath,
|
|
2232
2261
|
babel: this.params.babel,
|
|
2233
2262
|
logger: this.params.logger,
|
|
2263
|
+
resolve: this.params.resolve,
|
|
2234
2264
|
});
|
|
2235
2265
|
}
|
|
2236
2266
|
import(filePath, seen = new Set()) {
|
|
@@ -2275,9 +2305,11 @@ class LoaderService {
|
|
|
2275
2305
|
constructor() {
|
|
2276
2306
|
this.babelService = inject(TYPES.babelService);
|
|
2277
2307
|
this.loggerService = inject(TYPES.loggerService);
|
|
2308
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
2278
2309
|
this.getInstance = memoize(([basePath]) => `${basePath}`, (basePath) => new ClientLoader({
|
|
2279
2310
|
babel: this.babelService,
|
|
2280
2311
|
logger: this.loggerService,
|
|
2312
|
+
resolve: this.resolveService,
|
|
2281
2313
|
path: basePath,
|
|
2282
2314
|
}));
|
|
2283
2315
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
@@ -2512,7 +2544,7 @@ const main$b = async () => {
|
|
|
2512
2544
|
if (MODES.some((mode) => values[mode])) {
|
|
2513
2545
|
return;
|
|
2514
2546
|
}
|
|
2515
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
2547
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
2516
2548
|
process.stdout.write("\n");
|
|
2517
2549
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2518
2550
|
process.stdout.write("\n");
|
|
@@ -3080,7 +3112,7 @@ const main$1 = async () => {
|
|
|
3080
3112
|
if (!values.help) {
|
|
3081
3113
|
return;
|
|
3082
3114
|
}
|
|
3083
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3115
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n\n`);
|
|
3084
3116
|
process.stdout.write(HELP_TEXT);
|
|
3085
3117
|
process.exit(0);
|
|
3086
3118
|
};
|
|
@@ -3094,7 +3126,7 @@ const main = async () => {
|
|
|
3094
3126
|
if (!values.version) {
|
|
3095
3127
|
return;
|
|
3096
3128
|
}
|
|
3097
|
-
process.stdout.write(`@backtest-kit/cli ${"6.7.
|
|
3129
|
+
process.stdout.write(`@backtest-kit/cli ${"6.7.2"}\n`);
|
|
3098
3130
|
process.exit(0);
|
|
3099
3131
|
};
|
|
3100
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>;
|