@backtest-kit/cli 8.4.3 → 8.5.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 +78 -1
- package/build/index.cjs +140 -14
- package/build/index.mjs +140 -14
- package/docker/docker-compose.yaml +1 -0
- package/docker/package.json +6 -6
- package/package.json +13 -13
- package/template/project/package.mustache +5 -5
package/README.md
CHANGED
|
@@ -277,6 +277,82 @@ npx @backtest-kit/cli --walker \
|
|
|
277
277
|
# → ./dump/feb_2026_comparison.md
|
|
278
278
|
```
|
|
279
279
|
|
|
280
|
+
## 🐙 Multiple Symbol Parallel
|
|
281
|
+
|
|
282
|
+
> **For Poweruser — skip unless needed.** The standard flow runs one symbol from `--symbol`. Use `--entry` only to fan out one strategy across many symbols at once, or to drive `*.background()` from a UI / DB / API.
|
|
283
|
+
|
|
284
|
+
With `--entry`, the CLI does only the boilerplate — `Setup`, providers (`--ui` / `--telegram`), the matching `./modules/<mode>.module`, SIGINT that stops every active run via `*.list()`, and `shutdown()` once `listenDone*` reports all your runs complete. Picking the symbol set, warming cache, and calling `*.background()` is on you.
|
|
285
|
+
|
|
286
|
+
The `--entry` flag is a modifier — combine it with exactly one of `--backtest` / `--live` / `--paper` / `--walker`. One positional: the path to your entry file.
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
npx @backtest-kit/cli --backtest --entry ./src/multi-symbol.mjs
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Example: backtest a strategy on five symbols at once
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// src/multi-symbol.mjs
|
|
296
|
+
import {
|
|
297
|
+
addExchangeSchema,
|
|
298
|
+
addFrameSchema,
|
|
299
|
+
addStrategySchema,
|
|
300
|
+
Backtest,
|
|
301
|
+
Cache,
|
|
302
|
+
} from "backtest-kit";
|
|
303
|
+
import ccxt from "ccxt";
|
|
304
|
+
|
|
305
|
+
addExchangeSchema({
|
|
306
|
+
exchangeName: "binance",
|
|
307
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
308
|
+
const exchange = new ccxt.binance();
|
|
309
|
+
const ohlcv = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
|
|
310
|
+
return ohlcv.map(([timestamp, open, high, low, close, volume]) => ({
|
|
311
|
+
timestamp, open, high, low, close, volume,
|
|
312
|
+
}));
|
|
313
|
+
},
|
|
314
|
+
formatPrice: (symbol, price) => price.toFixed(2),
|
|
315
|
+
formatQuantity: (symbol, quantity) => quantity.toFixed(8),
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
addFrameSchema({
|
|
319
|
+
frameName: "feb-2026",
|
|
320
|
+
interval: "1m",
|
|
321
|
+
startDate: new Date("2026-02-01"),
|
|
322
|
+
endDate: new Date("2026-02-28"),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
addStrategySchema({
|
|
326
|
+
strategyName: "my-strategy",
|
|
327
|
+
interval: "15m",
|
|
328
|
+
getSignal: async (symbol) => null,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Decide the symbol set yourself — from a UI, database, API, or just a list.
|
|
332
|
+
const symbols = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT"];
|
|
333
|
+
|
|
334
|
+
for (const symbol of symbols) {
|
|
335
|
+
|
|
336
|
+
//
|
|
337
|
+
// Optional
|
|
338
|
+
//
|
|
339
|
+
// await Cache.warmup(["1m", "15m", "1h"], {
|
|
340
|
+
// exchangeName: "binance",
|
|
341
|
+
// frameName: "feb-2026",
|
|
342
|
+
// symbol,
|
|
343
|
+
// });
|
|
344
|
+
//
|
|
345
|
+
|
|
346
|
+
Backtest.background(symbol, {
|
|
347
|
+
strategyName: "my-strategy",
|
|
348
|
+
exchangeName: "binance",
|
|
349
|
+
frameName: "feb-2026",
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The same shape works for `--live --entry` / `--paper --entry` (call `Live.background()` per symbol with your broker adapter)
|
|
355
|
+
|
|
280
356
|
## 🗂️ Monorepo Usage
|
|
281
357
|
|
|
282
358
|
`@backtest-kit/cli` works out of the box in a monorepo where each strategy lives in its own subdirectory. When the CLI loads your entry point file, it automatically changes the working directory to the file's location — so all relative paths (`dump/`, `modules/`, `template/`) resolve inside that strategy's folder, not the project root.
|
|
@@ -1422,7 +1498,7 @@ command:
|
|
|
1422
1498
|
Pass `MODE` and `STRATEGY_FILE` on the command line — no file edits needed:
|
|
1423
1499
|
|
|
1424
1500
|
```bash
|
|
1425
|
-
MODE=live SYMBOL=TRXUSDT
|
|
1501
|
+
MODE=live SYMBOL=TRXUSDT STRATEGY_FILE=./content/feb_2026/feb_2026.strategy.ts docker-compose up -d
|
|
1426
1502
|
```
|
|
1427
1503
|
|
|
1428
1504
|
| Variable | Required | Default | Description |
|
|
@@ -1438,6 +1514,7 @@ MODE=live SYMBOL=TRXUSDT UI=1 docker-compose up -d
|
|
|
1438
1514
|
| `VERBOSE` | no | — | Any non-empty value enables `--verbose` |
|
|
1439
1515
|
| `NO_CACHE` | no | — | Any non-empty value enables `--noCache` |
|
|
1440
1516
|
| `NO_FLUSH` | no | — | Any non-empty value enables `--noFlush` |
|
|
1517
|
+
| `ENTRY` | no | — | Any non-empty value enables multiple symbols from userspace |
|
|
1441
1518
|
|
|
1442
1519
|
## 🌍 Environment Variables
|
|
1443
1520
|
|
package/build/index.cjs
CHANGED
|
@@ -521,6 +521,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
521
521
|
type: "boolean",
|
|
522
522
|
default: false,
|
|
523
523
|
},
|
|
524
|
+
entry: {
|
|
525
|
+
type: "boolean",
|
|
526
|
+
default: false,
|
|
527
|
+
},
|
|
524
528
|
cacheInterval: {
|
|
525
529
|
type: "string",
|
|
526
530
|
default: "1m, 15m, 30m, 4h",
|
|
@@ -3019,7 +3023,7 @@ Example:
|
|
|
3019
3023
|
|
|
3020
3024
|
node ${ENTRY_PATH$1} --help
|
|
3021
3025
|
`.trimStart();
|
|
3022
|
-
const main$
|
|
3026
|
+
const main$g = async () => {
|
|
3023
3027
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3024
3028
|
return;
|
|
3025
3029
|
}
|
|
@@ -3027,14 +3031,14 @@ const main$f = async () => {
|
|
|
3027
3031
|
if (MODES.some((mode) => values[mode])) {
|
|
3028
3032
|
return;
|
|
3029
3033
|
}
|
|
3030
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
3034
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n`);
|
|
3031
3035
|
process.stdout.write("\n");
|
|
3032
3036
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3033
3037
|
process.stdout.write("\n");
|
|
3034
3038
|
process.stdout.write(HELP_TEXT$1);
|
|
3035
3039
|
process.exit(0);
|
|
3036
3040
|
};
|
|
3037
|
-
main$
|
|
3041
|
+
main$g();
|
|
3038
3042
|
|
|
3039
3043
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
3040
3044
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
@@ -3050,7 +3054,7 @@ const flush = async (entryPoint) => {
|
|
|
3050
3054
|
console.log(`Removed: ${target}`);
|
|
3051
3055
|
}
|
|
3052
3056
|
};
|
|
3053
|
-
const main$
|
|
3057
|
+
const main$f = async () => {
|
|
3054
3058
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3055
3059
|
return;
|
|
3056
3060
|
}
|
|
@@ -3067,7 +3071,7 @@ const main$e = async () => {
|
|
|
3067
3071
|
}
|
|
3068
3072
|
process.exit(0);
|
|
3069
3073
|
};
|
|
3070
|
-
main$
|
|
3074
|
+
main$f();
|
|
3071
3075
|
|
|
3072
3076
|
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
3073
3077
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3089,7 +3093,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
3089
3093
|
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
3090
3094
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3091
3095
|
});
|
|
3092
|
-
const main$
|
|
3096
|
+
const main$e = async () => {
|
|
3093
3097
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3094
3098
|
return;
|
|
3095
3099
|
}
|
|
@@ -3097,6 +3101,9 @@ const main$d = async () => {
|
|
|
3097
3101
|
if (!values.backtest) {
|
|
3098
3102
|
return;
|
|
3099
3103
|
}
|
|
3104
|
+
if (values.entry) {
|
|
3105
|
+
return;
|
|
3106
|
+
}
|
|
3100
3107
|
if (!values.noFlush) {
|
|
3101
3108
|
const [entryPoint = null] = getPositionals();
|
|
3102
3109
|
entryPoint && await flush(entryPoint);
|
|
@@ -3104,7 +3111,7 @@ const main$d = async () => {
|
|
|
3104
3111
|
await cli.backtestMainService.connect();
|
|
3105
3112
|
listenGracefulShutdown$5();
|
|
3106
3113
|
};
|
|
3107
|
-
main$
|
|
3114
|
+
main$e();
|
|
3108
3115
|
|
|
3109
3116
|
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
3110
3117
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3122,7 +3129,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
3122
3129
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
3123
3130
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3124
3131
|
});
|
|
3125
|
-
const main$
|
|
3132
|
+
const main$d = async () => {
|
|
3126
3133
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3127
3134
|
return;
|
|
3128
3135
|
}
|
|
@@ -3130,6 +3137,9 @@ const main$c = async () => {
|
|
|
3130
3137
|
if (!values.walker) {
|
|
3131
3138
|
return;
|
|
3132
3139
|
}
|
|
3140
|
+
if (values.entry) {
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3133
3143
|
if (!values.noFlush) {
|
|
3134
3144
|
for (const entryPoint of getPositionals()) {
|
|
3135
3145
|
await flush(entryPoint);
|
|
@@ -3138,7 +3148,7 @@ const main$c = async () => {
|
|
|
3138
3148
|
listenGracefulShutdown$4();
|
|
3139
3149
|
await cli.walkerMainService.connect();
|
|
3140
3150
|
};
|
|
3141
|
-
main$
|
|
3151
|
+
main$d();
|
|
3142
3152
|
|
|
3143
3153
|
const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
3144
3154
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3159,7 +3169,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
3159
3169
|
const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
|
|
3160
3170
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3161
3171
|
});
|
|
3162
|
-
const main$
|
|
3172
|
+
const main$c = async () => {
|
|
3163
3173
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3164
3174
|
return;
|
|
3165
3175
|
}
|
|
@@ -3167,10 +3177,13 @@ const main$b = async () => {
|
|
|
3167
3177
|
if (!values.paper) {
|
|
3168
3178
|
return;
|
|
3169
3179
|
}
|
|
3180
|
+
if (values.entry) {
|
|
3181
|
+
return;
|
|
3182
|
+
}
|
|
3170
3183
|
cli.paperMainService.connect();
|
|
3171
3184
|
listenGracefulShutdown$3();
|
|
3172
3185
|
};
|
|
3173
|
-
main$
|
|
3186
|
+
main$c();
|
|
3174
3187
|
|
|
3175
3188
|
const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
3176
3189
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -3191,7 +3204,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
3191
3204
|
const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
|
|
3192
3205
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3193
3206
|
});
|
|
3194
|
-
const main$
|
|
3207
|
+
const main$b = async () => {
|
|
3195
3208
|
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3196
3209
|
return;
|
|
3197
3210
|
}
|
|
@@ -3199,9 +3212,122 @@ const main$a = async () => {
|
|
|
3199
3212
|
if (!values.live) {
|
|
3200
3213
|
return;
|
|
3201
3214
|
}
|
|
3215
|
+
if (values.entry) {
|
|
3216
|
+
return;
|
|
3217
|
+
}
|
|
3202
3218
|
await cli.liveMainService.connect();
|
|
3203
3219
|
listenGracefulShutdown$2();
|
|
3204
3220
|
};
|
|
3221
|
+
main$b();
|
|
3222
|
+
|
|
3223
|
+
const MODE_MODULE = {
|
|
3224
|
+
backtest: "./backtest.module",
|
|
3225
|
+
live: "./live.module",
|
|
3226
|
+
paper: "./paper.module",
|
|
3227
|
+
walker: "./walker.module",
|
|
3228
|
+
};
|
|
3229
|
+
const resolveMode = (values) => {
|
|
3230
|
+
const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
|
|
3231
|
+
if (enabled.length !== 1) {
|
|
3232
|
+
return null;
|
|
3233
|
+
}
|
|
3234
|
+
return enabled[0];
|
|
3235
|
+
};
|
|
3236
|
+
const stopBacktestList = async () => {
|
|
3237
|
+
for (const item of await BacktestKit.Backtest.list()) {
|
|
3238
|
+
if (item.status === "fulfilled") {
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
BacktestKit.Backtest.stop(item.symbol, {
|
|
3242
|
+
exchangeName: item.exchangeName,
|
|
3243
|
+
strategyName: item.strategyName,
|
|
3244
|
+
frameName: item.frameName,
|
|
3245
|
+
});
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
const stopLiveList = async () => {
|
|
3249
|
+
for (const item of await BacktestKit.Live.list()) {
|
|
3250
|
+
if (item.status === "fulfilled") {
|
|
3251
|
+
continue;
|
|
3252
|
+
}
|
|
3253
|
+
BacktestKit.Live.stop(item.symbol, {
|
|
3254
|
+
exchangeName: item.exchangeName,
|
|
3255
|
+
strategyName: item.strategyName,
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
};
|
|
3259
|
+
const stopWalkerList = async () => {
|
|
3260
|
+
for (const item of await BacktestKit.Walker.list()) {
|
|
3261
|
+
if (item.status === "fulfilled") {
|
|
3262
|
+
continue;
|
|
3263
|
+
}
|
|
3264
|
+
BacktestKit.Walker.stop(item.symbol, { walkerName: item.walkerName });
|
|
3265
|
+
}
|
|
3266
|
+
};
|
|
3267
|
+
const MODE_STOP = {
|
|
3268
|
+
backtest: stopBacktestList,
|
|
3269
|
+
live: stopLiveList,
|
|
3270
|
+
paper: stopLiveList,
|
|
3271
|
+
walker: stopWalkerList,
|
|
3272
|
+
};
|
|
3273
|
+
const listenFinish = functoolsKit.singleshot(() => {
|
|
3274
|
+
let disposeRef;
|
|
3275
|
+
const unBacktest = BacktestKit.listenDoneBacktest(() => {
|
|
3276
|
+
console.log("Backtest trading finished");
|
|
3277
|
+
disposeRef && disposeRef();
|
|
3278
|
+
});
|
|
3279
|
+
const unLive = BacktestKit.listenDoneLive(() => {
|
|
3280
|
+
console.log("Live trading finished");
|
|
3281
|
+
disposeRef && disposeRef();
|
|
3282
|
+
});
|
|
3283
|
+
const unWalker = BacktestKit.listenDoneWalker(() => {
|
|
3284
|
+
console.log("Walker comparison finished");
|
|
3285
|
+
disposeRef && disposeRef();
|
|
3286
|
+
});
|
|
3287
|
+
disposeRef = functoolsKit.compose(() => unBacktest(), () => unLive(), () => unWalker());
|
|
3288
|
+
BacktestKit.shutdown();
|
|
3289
|
+
});
|
|
3290
|
+
const createGracefulShutdown = (mode) => {
|
|
3291
|
+
const stop = MODE_STOP[mode];
|
|
3292
|
+
const handler = functoolsKit.singleshot(async () => {
|
|
3293
|
+
process.off("SIGINT", handler);
|
|
3294
|
+
notifyShutdown();
|
|
3295
|
+
await stop();
|
|
3296
|
+
});
|
|
3297
|
+
return functoolsKit.singleshot(() => {
|
|
3298
|
+
process.on("SIGINT", handler);
|
|
3299
|
+
});
|
|
3300
|
+
};
|
|
3301
|
+
const main$a = async () => {
|
|
3302
|
+
if (!getEntry((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))) {
|
|
3303
|
+
return;
|
|
3304
|
+
}
|
|
3305
|
+
const { values } = getArgs();
|
|
3306
|
+
if (!values.entry) {
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
const mode = resolveMode(values);
|
|
3310
|
+
if (!mode) {
|
|
3311
|
+
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
|
|
3312
|
+
process.exit(1);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
const [entryPoint = null] = getPositionals();
|
|
3316
|
+
if (!entryPoint) {
|
|
3317
|
+
throw new Error("Entry point is required");
|
|
3318
|
+
}
|
|
3319
|
+
if (!values.noFlush) {
|
|
3320
|
+
await flush(entryPoint);
|
|
3321
|
+
}
|
|
3322
|
+
await cli.configService.waitForInit();
|
|
3323
|
+
Setup.enable();
|
|
3324
|
+
cli.frontendProviderService.connect();
|
|
3325
|
+
cli.telegramProviderService.connect();
|
|
3326
|
+
await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
|
|
3327
|
+
listenFinish();
|
|
3328
|
+
createGracefulShutdown(mode)();
|
|
3329
|
+
await cli.resolveService.attachJavascript(entryPoint);
|
|
3330
|
+
};
|
|
3205
3331
|
main$a();
|
|
3206
3332
|
|
|
3207
3333
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
@@ -3908,7 +4034,7 @@ const main$1 = async () => {
|
|
|
3908
4034
|
if (!values.help) {
|
|
3909
4035
|
return;
|
|
3910
4036
|
}
|
|
3911
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
4037
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n\n`);
|
|
3912
4038
|
process.stdout.write(HELP_TEXT);
|
|
3913
4039
|
process.exit(0);
|
|
3914
4040
|
};
|
|
@@ -3922,7 +4048,7 @@ const main = async () => {
|
|
|
3922
4048
|
if (!values.version) {
|
|
3923
4049
|
return;
|
|
3924
4050
|
}
|
|
3925
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
4051
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n`);
|
|
3926
4052
|
process.exit(0);
|
|
3927
4053
|
};
|
|
3928
4054
|
main();
|
package/build/index.mjs
CHANGED
|
@@ -496,6 +496,10 @@ const getArgs = singleshot(() => {
|
|
|
496
496
|
type: "boolean",
|
|
497
497
|
default: false,
|
|
498
498
|
},
|
|
499
|
+
entry: {
|
|
500
|
+
type: "boolean",
|
|
501
|
+
default: false,
|
|
502
|
+
},
|
|
499
503
|
cacheInterval: {
|
|
500
504
|
type: "string",
|
|
501
505
|
default: "1m, 15m, 30m, 4h",
|
|
@@ -2990,7 +2994,7 @@ Example:
|
|
|
2990
2994
|
|
|
2991
2995
|
node ${ENTRY_PATH$1} --help
|
|
2992
2996
|
`.trimStart();
|
|
2993
|
-
const main$
|
|
2997
|
+
const main$g = async () => {
|
|
2994
2998
|
if (!getEntry(import.meta.url)) {
|
|
2995
2999
|
return;
|
|
2996
3000
|
}
|
|
@@ -2998,14 +3002,14 @@ const main$f = async () => {
|
|
|
2998
3002
|
if (MODES.some((mode) => values[mode])) {
|
|
2999
3003
|
return;
|
|
3000
3004
|
}
|
|
3001
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
3005
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n`);
|
|
3002
3006
|
process.stdout.write("\n");
|
|
3003
3007
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3004
3008
|
process.stdout.write("\n");
|
|
3005
3009
|
process.stdout.write(HELP_TEXT$1);
|
|
3006
3010
|
process.exit(0);
|
|
3007
3011
|
};
|
|
3008
|
-
main$
|
|
3012
|
+
main$g();
|
|
3009
3013
|
|
|
3010
3014
|
const notifyShutdown = singleshot(async () => {
|
|
3011
3015
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
@@ -3021,7 +3025,7 @@ const flush = async (entryPoint) => {
|
|
|
3021
3025
|
console.log(`Removed: ${target}`);
|
|
3022
3026
|
}
|
|
3023
3027
|
};
|
|
3024
|
-
const main$
|
|
3028
|
+
const main$f = async () => {
|
|
3025
3029
|
if (!getEntry(import.meta.url)) {
|
|
3026
3030
|
return;
|
|
3027
3031
|
}
|
|
@@ -3038,7 +3042,7 @@ const main$e = async () => {
|
|
|
3038
3042
|
}
|
|
3039
3043
|
process.exit(0);
|
|
3040
3044
|
};
|
|
3041
|
-
main$
|
|
3045
|
+
main$f();
|
|
3042
3046
|
|
|
3043
3047
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
3044
3048
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3060,7 +3064,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3060
3064
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3061
3065
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3062
3066
|
});
|
|
3063
|
-
const main$
|
|
3067
|
+
const main$e = async () => {
|
|
3064
3068
|
if (!getEntry(import.meta.url)) {
|
|
3065
3069
|
return;
|
|
3066
3070
|
}
|
|
@@ -3068,6 +3072,9 @@ const main$d = async () => {
|
|
|
3068
3072
|
if (!values.backtest) {
|
|
3069
3073
|
return;
|
|
3070
3074
|
}
|
|
3075
|
+
if (values.entry) {
|
|
3076
|
+
return;
|
|
3077
|
+
}
|
|
3071
3078
|
if (!values.noFlush) {
|
|
3072
3079
|
const [entryPoint = null] = getPositionals();
|
|
3073
3080
|
entryPoint && await flush(entryPoint);
|
|
@@ -3075,7 +3082,7 @@ const main$d = async () => {
|
|
|
3075
3082
|
await cli.backtestMainService.connect();
|
|
3076
3083
|
listenGracefulShutdown$5();
|
|
3077
3084
|
};
|
|
3078
|
-
main$
|
|
3085
|
+
main$e();
|
|
3079
3086
|
|
|
3080
3087
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3081
3088
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3093,7 +3100,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3093
3100
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3094
3101
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3095
3102
|
});
|
|
3096
|
-
const main$
|
|
3103
|
+
const main$d = async () => {
|
|
3097
3104
|
if (!getEntry(import.meta.url)) {
|
|
3098
3105
|
return;
|
|
3099
3106
|
}
|
|
@@ -3101,6 +3108,9 @@ const main$c = async () => {
|
|
|
3101
3108
|
if (!values.walker) {
|
|
3102
3109
|
return;
|
|
3103
3110
|
}
|
|
3111
|
+
if (values.entry) {
|
|
3112
|
+
return;
|
|
3113
|
+
}
|
|
3104
3114
|
if (!values.noFlush) {
|
|
3105
3115
|
for (const entryPoint of getPositionals()) {
|
|
3106
3116
|
await flush(entryPoint);
|
|
@@ -3109,7 +3119,7 @@ const main$c = async () => {
|
|
|
3109
3119
|
listenGracefulShutdown$4();
|
|
3110
3120
|
await cli.walkerMainService.connect();
|
|
3111
3121
|
};
|
|
3112
|
-
main$
|
|
3122
|
+
main$d();
|
|
3113
3123
|
|
|
3114
3124
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3115
3125
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3130,7 +3140,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3130
3140
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3131
3141
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3132
3142
|
});
|
|
3133
|
-
const main$
|
|
3143
|
+
const main$c = async () => {
|
|
3134
3144
|
if (!getEntry(import.meta.url)) {
|
|
3135
3145
|
return;
|
|
3136
3146
|
}
|
|
@@ -3138,10 +3148,13 @@ const main$b = async () => {
|
|
|
3138
3148
|
if (!values.paper) {
|
|
3139
3149
|
return;
|
|
3140
3150
|
}
|
|
3151
|
+
if (values.entry) {
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3141
3154
|
cli.paperMainService.connect();
|
|
3142
3155
|
listenGracefulShutdown$3();
|
|
3143
3156
|
};
|
|
3144
|
-
main$
|
|
3157
|
+
main$c();
|
|
3145
3158
|
|
|
3146
3159
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
3147
3160
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -3162,7 +3175,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3162
3175
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3163
3176
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3164
3177
|
});
|
|
3165
|
-
const main$
|
|
3178
|
+
const main$b = async () => {
|
|
3166
3179
|
if (!getEntry(import.meta.url)) {
|
|
3167
3180
|
return;
|
|
3168
3181
|
}
|
|
@@ -3170,9 +3183,122 @@ const main$a = async () => {
|
|
|
3170
3183
|
if (!values.live) {
|
|
3171
3184
|
return;
|
|
3172
3185
|
}
|
|
3186
|
+
if (values.entry) {
|
|
3187
|
+
return;
|
|
3188
|
+
}
|
|
3173
3189
|
await cli.liveMainService.connect();
|
|
3174
3190
|
listenGracefulShutdown$2();
|
|
3175
3191
|
};
|
|
3192
|
+
main$b();
|
|
3193
|
+
|
|
3194
|
+
const MODE_MODULE = {
|
|
3195
|
+
backtest: "./backtest.module",
|
|
3196
|
+
live: "./live.module",
|
|
3197
|
+
paper: "./paper.module",
|
|
3198
|
+
walker: "./walker.module",
|
|
3199
|
+
};
|
|
3200
|
+
const resolveMode = (values) => {
|
|
3201
|
+
const enabled = ["backtest", "live", "paper", "walker"].filter((mode) => Boolean(values[mode]));
|
|
3202
|
+
if (enabled.length !== 1) {
|
|
3203
|
+
return null;
|
|
3204
|
+
}
|
|
3205
|
+
return enabled[0];
|
|
3206
|
+
};
|
|
3207
|
+
const stopBacktestList = async () => {
|
|
3208
|
+
for (const item of await Backtest.list()) {
|
|
3209
|
+
if (item.status === "fulfilled") {
|
|
3210
|
+
continue;
|
|
3211
|
+
}
|
|
3212
|
+
Backtest.stop(item.symbol, {
|
|
3213
|
+
exchangeName: item.exchangeName,
|
|
3214
|
+
strategyName: item.strategyName,
|
|
3215
|
+
frameName: item.frameName,
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
const stopLiveList = async () => {
|
|
3220
|
+
for (const item of await Live.list()) {
|
|
3221
|
+
if (item.status === "fulfilled") {
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
Live.stop(item.symbol, {
|
|
3225
|
+
exchangeName: item.exchangeName,
|
|
3226
|
+
strategyName: item.strategyName,
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
const stopWalkerList = async () => {
|
|
3231
|
+
for (const item of await Walker.list()) {
|
|
3232
|
+
if (item.status === "fulfilled") {
|
|
3233
|
+
continue;
|
|
3234
|
+
}
|
|
3235
|
+
Walker.stop(item.symbol, { walkerName: item.walkerName });
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
3238
|
+
const MODE_STOP = {
|
|
3239
|
+
backtest: stopBacktestList,
|
|
3240
|
+
live: stopLiveList,
|
|
3241
|
+
paper: stopLiveList,
|
|
3242
|
+
walker: stopWalkerList,
|
|
3243
|
+
};
|
|
3244
|
+
const listenFinish = singleshot(() => {
|
|
3245
|
+
let disposeRef;
|
|
3246
|
+
const unBacktest = listenDoneBacktest(() => {
|
|
3247
|
+
console.log("Backtest trading finished");
|
|
3248
|
+
disposeRef && disposeRef();
|
|
3249
|
+
});
|
|
3250
|
+
const unLive = listenDoneLive(() => {
|
|
3251
|
+
console.log("Live trading finished");
|
|
3252
|
+
disposeRef && disposeRef();
|
|
3253
|
+
});
|
|
3254
|
+
const unWalker = listenDoneWalker(() => {
|
|
3255
|
+
console.log("Walker comparison finished");
|
|
3256
|
+
disposeRef && disposeRef();
|
|
3257
|
+
});
|
|
3258
|
+
disposeRef = compose(() => unBacktest(), () => unLive(), () => unWalker());
|
|
3259
|
+
shutdown();
|
|
3260
|
+
});
|
|
3261
|
+
const createGracefulShutdown = (mode) => {
|
|
3262
|
+
const stop = MODE_STOP[mode];
|
|
3263
|
+
const handler = singleshot(async () => {
|
|
3264
|
+
process.off("SIGINT", handler);
|
|
3265
|
+
notifyShutdown();
|
|
3266
|
+
await stop();
|
|
3267
|
+
});
|
|
3268
|
+
return singleshot(() => {
|
|
3269
|
+
process.on("SIGINT", handler);
|
|
3270
|
+
});
|
|
3271
|
+
};
|
|
3272
|
+
const main$a = async () => {
|
|
3273
|
+
if (!getEntry(import.meta.url)) {
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
const { values } = getArgs();
|
|
3277
|
+
if (!values.entry) {
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
const mode = resolveMode(values);
|
|
3281
|
+
if (!mode) {
|
|
3282
|
+
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
|
|
3283
|
+
process.exit(1);
|
|
3284
|
+
return;
|
|
3285
|
+
}
|
|
3286
|
+
const [entryPoint = null] = getPositionals();
|
|
3287
|
+
if (!entryPoint) {
|
|
3288
|
+
throw new Error("Entry point is required");
|
|
3289
|
+
}
|
|
3290
|
+
if (!values.noFlush) {
|
|
3291
|
+
await flush(entryPoint);
|
|
3292
|
+
}
|
|
3293
|
+
await cli.configService.waitForInit();
|
|
3294
|
+
Setup.enable();
|
|
3295
|
+
cli.frontendProviderService.connect();
|
|
3296
|
+
cli.telegramProviderService.connect();
|
|
3297
|
+
await cli.moduleConnectionService.loadModule(MODE_MODULE[mode]);
|
|
3298
|
+
listenFinish();
|
|
3299
|
+
createGracefulShutdown(mode)();
|
|
3300
|
+
await cli.resolveService.attachJavascript(entryPoint);
|
|
3301
|
+
};
|
|
3176
3302
|
main$a();
|
|
3177
3303
|
|
|
3178
3304
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
@@ -3879,7 +4005,7 @@ const main$1 = async () => {
|
|
|
3879
4005
|
if (!values.help) {
|
|
3880
4006
|
return;
|
|
3881
4007
|
}
|
|
3882
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
4008
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n\n`);
|
|
3883
4009
|
process.stdout.write(HELP_TEXT);
|
|
3884
4010
|
process.exit(0);
|
|
3885
4011
|
};
|
|
@@ -3893,7 +4019,7 @@ const main = async () => {
|
|
|
3893
4019
|
if (!values.version) {
|
|
3894
4020
|
return;
|
|
3895
4021
|
}
|
|
3896
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
4022
|
+
process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n`);
|
|
3897
4023
|
process.exit(0);
|
|
3898
4024
|
};
|
|
3899
4025
|
main();
|
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": "8.
|
|
19
|
-
"@backtest-kit/graph": "8.
|
|
20
|
-
"@backtest-kit/pinets": "8.
|
|
21
|
-
"@backtest-kit/signals": "8.
|
|
22
|
-
"@backtest-kit/ui": "8.
|
|
18
|
+
"@backtest-kit/cli": "8.5.0",
|
|
19
|
+
"@backtest-kit/graph": "8.5.0",
|
|
20
|
+
"@backtest-kit/pinets": "8.5.0",
|
|
21
|
+
"@backtest-kit/signals": "8.5.0",
|
|
22
|
+
"@backtest-kit/ui": "8.5.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": "8.
|
|
28
|
+
"backtest-kit": "8.5.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": "8.
|
|
3
|
+
"version": "8.5.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": "8.
|
|
67
|
-
"@backtest-kit/ollama": "8.
|
|
68
|
-
"@backtest-kit/pinets": "8.
|
|
69
|
-
"@backtest-kit/signals": "8.
|
|
70
|
-
"@backtest-kit/ui": "8.
|
|
66
|
+
"@backtest-kit/graph": "8.5.0",
|
|
67
|
+
"@backtest-kit/ollama": "8.5.0",
|
|
68
|
+
"@backtest-kit/pinets": "8.5.0",
|
|
69
|
+
"@backtest-kit/signals": "8.5.0",
|
|
70
|
+
"@backtest-kit/ui": "8.5.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": "8.
|
|
78
|
+
"backtest-kit": "8.5.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": "^8.
|
|
94
|
-
"@backtest-kit/ollama": "^8.
|
|
95
|
-
"@backtest-kit/pinets": "^8.
|
|
96
|
-
"@backtest-kit/signals": "^8.
|
|
97
|
-
"@backtest-kit/ui": "^8.
|
|
98
|
-
"backtest-kit": "^8.
|
|
93
|
+
"@backtest-kit/graph": "^8.5.0",
|
|
94
|
+
"@backtest-kit/ollama": "^8.5.0",
|
|
95
|
+
"@backtest-kit/pinets": "^8.5.0",
|
|
96
|
+
"@backtest-kit/signals": "^8.5.0",
|
|
97
|
+
"@backtest-kit/ui": "^8.5.0",
|
|
98
|
+
"backtest-kit": "^8.5.0",
|
|
99
99
|
"markdown-it": "^14.1.1",
|
|
100
100
|
"typescript": "^5.0.0"
|
|
101
101
|
},
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"type": "commonjs",
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@backtest-kit/cli": "^8.
|
|
17
|
-
"@backtest-kit/graph": "^8.
|
|
18
|
-
"@backtest-kit/pinets": "^8.
|
|
19
|
-
"@backtest-kit/ui": "^8.
|
|
16
|
+
"@backtest-kit/cli": "^8.5.0",
|
|
17
|
+
"@backtest-kit/graph": "^8.5.0",
|
|
18
|
+
"@backtest-kit/pinets": "^8.5.0",
|
|
19
|
+
"@backtest-kit/ui": "^8.5.0",
|
|
20
20
|
"agent-swarm-kit": "^2.6.0",
|
|
21
|
-
"backtest-kit": "^8.
|
|
21
|
+
"backtest-kit": "^8.5.0",
|
|
22
22
|
"functools-kit": "^2.3.0",
|
|
23
23
|
"garch": "^1.2.3",
|
|
24
24
|
"get-moment-stamp": "^1.1.2",
|