@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 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 UI=1 docker-compose up -d
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$f = async () => {
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.4.3"}\n`);
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$f();
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$e = async () => {
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$e();
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$d = async () => {
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$d();
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$c = async () => {
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$c();
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$b = async () => {
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$b();
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$a = async () => {
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.4.3"}\n\n`);
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.4.3"}\n`);
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$f = async () => {
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.4.3"}\n`);
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$f();
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$e = async () => {
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$e();
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$d = async () => {
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$d();
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$c = async () => {
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$c();
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$b = async () => {
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$b();
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$a = async () => {
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.4.3"}\n\n`);
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.4.3"}\n`);
4022
+ process.stdout.write(`@backtest-kit/cli ${"8.5.0"}\n`);
3897
4023
  process.exit(0);
3898
4024
  };
3899
4025
  main();
@@ -27,6 +27,7 @@ services:
27
27
  - VERBOSE
28
28
  - NO_CACHE
29
29
  - NO_FLUSH
30
+ - ENTRY
30
31
  healthcheck:
31
32
  test: ["CMD", "curl", "-f", "http://localhost:60050/api/v1/health/health_check"]
32
33
  interval: 30s
@@ -15,17 +15,17 @@
15
15
  "@types/node": "25.6.0"
16
16
  },
17
17
  "dependencies": {
18
- "@backtest-kit/cli": "8.4.3",
19
- "@backtest-kit/graph": "8.4.0",
20
- "@backtest-kit/pinets": "8.4.0",
21
- "@backtest-kit/signals": "8.4.0",
22
- "@backtest-kit/ui": "8.4.0",
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.4.0",
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.4.3",
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.4.0",
67
- "@backtest-kit/ollama": "8.4.0",
68
- "@backtest-kit/pinets": "8.4.0",
69
- "@backtest-kit/signals": "8.4.0",
70
- "@backtest-kit/ui": "8.4.0",
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.4.0",
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.4.0",
94
- "@backtest-kit/ollama": "^8.4.0",
95
- "@backtest-kit/pinets": "^8.4.0",
96
- "@backtest-kit/signals": "^8.4.0",
97
- "@backtest-kit/ui": "^8.4.0",
98
- "backtest-kit": "^8.4.0",
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.4.3",
17
- "@backtest-kit/graph": "^8.4.0",
18
- "@backtest-kit/pinets": "^8.4.0",
19
- "@backtest-kit/ui": "^8.4.0",
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.4.0",
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",