@backtest-kit/cli 8.1.0 → 8.3.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
@@ -45,6 +45,7 @@ Point the CLI at your strategy file, choose a mode, and it handles exchange conn
45
45
  | **Pine Editor** | `--editor` | Open the visual Pine Script editor in the browser |
46
46
  | **Candle Dump** | `--dump` | Fetch and save raw OHLCV candles to a file |
47
47
  | **PnL Debug** | `--pnldebug` | Simulate per-minute PnL for a given entry price and direction |
48
+ | **Broker Debug** | `--brokerdebug` | Fire a single broker commit against the live broker adapter |
48
49
  | **Flush** | `--flush` | Delete report/log/markdown/agent folders from strategy dump dir |
49
50
  | **Init Project** | `--init` | Scaffold a new backtest-kit project |
50
51
 
@@ -155,6 +156,8 @@ npm start -- --symbol BTCUSDT --ui
155
156
  | `--exchange` | string | Exchange name (default: first registered) |
156
157
  | `--frame` | string | Backtest frame name (default: first registered) |
157
158
  | `--cacheInterval` | string | Intervals to pre-cache before backtest (default: `"1m, 15m, 30m, 4h"`) |
159
+ | `--brokerdebug` | boolean | Fire a single broker commit against the live broker adapter (default: `false`) |
160
+ | `--commit` | string | Commit type for `--brokerdebug` (default: `"signal-open"`) |
158
161
 
159
162
  **Positional argument (required):** path to your strategy entry point file (set once in `package.json` scripts).
160
163
 
@@ -567,7 +570,8 @@ The CLI supports **mode-specific module files** that are loaded as side-effect i
567
570
  | `--live` | `./modules/live.module.mjs` | `Live.background()` |
568
571
  | `--paper` | `./modules/paper.module.mjs` | `Live.background()` (paper) |
569
572
  | `--backtest` | `./modules/backtest.module.mjs` | `Backtest.background()` |
570
- | `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
573
+ | `--walker` | `./modules/walker.module.mjs` | `Walker.background()` |
574
+ | `--brokerdebug` | `./modules/brokerdebug.module.mjs` | broker commit test |
571
575
 
572
576
  > File is resolved relative to `cwd` (the strategy directory). All of `.mjs`, `.cjs`, `.ts` extensions are tried automatically. Missing module is a soft warning — not an error.
573
577
 
@@ -1170,6 +1174,63 @@ Symbol: BTCUSDT | Direction: short | PriceOpen: 64069.50 | From: 2025-02-25T00:0
1170
1174
  120 | 2025-02-25T02:00:00.000Z | 63200.00 | +1.36% | +1.36% | -0.06%
1171
1175
  ```
1172
1176
 
1177
+ ## 🐛 Broker Debug (`--brokerdebug`)
1178
+
1179
+ `@backtest-kit/cli` can fire a single broker commit against your live broker adapter without running a full strategy — useful for verifying that your `brokerdebug.module` correctly wires up exchange calls.
1180
+
1181
+ ### CLI Flags
1182
+
1183
+ | Flag | Type | Description |
1184
+ |------|------|-------------|
1185
+ | `--brokerdebug` | boolean | Enable broker debug mode |
1186
+ | `--commit` | string | Commit type to fire (default: `"signal-open"`) |
1187
+ | `--symbol` | string | Trading pair (default: `"BTCUSDT"`) |
1188
+ | `--exchange` | string | Exchange name (default: first registered) |
1189
+
1190
+ **Available `--commit` values:**
1191
+
1192
+ | Value | Broker hook |
1193
+ |-------|-------------|
1194
+ | `signal-open` | `onSignalOpenCommit` |
1195
+ | `signal-close` | `onSignalCloseCommit` |
1196
+ | `partial-profit` | `onPartialProfitCommit` |
1197
+ | `partial-loss` | `onPartialLossCommit` |
1198
+ | `average-buy` | `onAverageBuyCommit` |
1199
+ | `trailing-stop` | `onTrailingStopCommit` |
1200
+ | `trailing-take` | `onTrailingTakeCommit` |
1201
+ | `breakeven` | `onBreakevenCommit` |
1202
+
1203
+ ### How It Works
1204
+
1205
+ The CLI loads `./modules/brokerdebug.module`, fetches the last candle for `--symbol` / `--timeframe`, derives synthetic payload values from `currentPrice` (TP = +2%, SL = -2%), and calls the selected broker hook once. Exits with code `0` on success.
1206
+
1207
+ ### Broker via `brokerdebug.module`
1208
+
1209
+ Create a `modules/brokerdebug.module.ts` file and register your broker adapter:
1210
+
1211
+ ```typescript
1212
+ // modules/brokerdebug.module.ts
1213
+ import { Broker } from 'backtest-kit';
1214
+ import { myExchange } from './exchange.mjs';
1215
+
1216
+ class MyBroker {
1217
+ async onSignalOpenCommit({ symbol, priceOpen, position }) {
1218
+ await myExchange.openPosition(symbol, position, priceOpen);
1219
+ }
1220
+ // ... other hooks
1221
+ }
1222
+
1223
+ Broker.useBrokerAdapter(MyBroker);
1224
+ Broker.enable();
1225
+ ```
1226
+
1227
+ ### Usage
1228
+
1229
+ ```bash
1230
+ npx @backtest-kit/cli --brokerdebug --commit signal-open --symbol BTCUSDT
1231
+ npx @backtest-kit/cli --brokerdebug --commit partial-profit --symbol ETHUSDT --timeframe 1h
1232
+ ```
1233
+
1173
1234
  ## 🗑️ Flushing Strategy Output (`--flush`)
1174
1235
 
1175
1236
  `@backtest-kit/cli` can delete generated output folders from one or more strategy dump directories without touching cached candle data.
@@ -1308,6 +1369,48 @@ Or via the pre-configured npm script:
1308
1369
  npm run sync:lib
1309
1370
  ```
1310
1371
 
1372
+ ## 🐳 Running in Docker (`--docker`)
1373
+
1374
+ CLI can scaffold a ready-to-use Docker workspace: self-contained directory with `docker-compose.yaml` and a strategy entry point.
1375
+
1376
+ ### CLI Flags
1377
+
1378
+ | Flag | Type | Description |
1379
+ |------|------|-------------|
1380
+ | `--docker` | boolean | Scaffold a Docker workspace |
1381
+ | `--output` | string | Target directory name (default: `backtest-kit-docker`) |
1382
+
1383
+ ### Usage
1384
+
1385
+ ```bash
1386
+ npx @backtest-kit/cli --docker
1387
+ ```
1388
+
1389
+ Creates `./backtest-kit-docker/` in the current working directory.
1390
+
1391
+ Override the directory name with `--output`:
1392
+
1393
+ ```bash
1394
+ npx @backtest-kit/cli --docker --output my-docker-workspace
1395
+ ```
1396
+
1397
+ The target directory must not exist or must be empty — the command aborts if it contains any files.
1398
+
1399
+ ### Generated Workspace Structure
1400
+
1401
+ ```
1402
+ backtest-kit-docker/
1403
+ ├── docker-compose.yaml # ready-to-run service definition
1404
+ ├── .env.example # environment variable reference
1405
+ ├── package.json # dependencies for editing strategies locally
1406
+ ├── tsconfig.json # TypeScript config for content/
1407
+ └── content/
1408
+ └── feb_2026/
1409
+ ├── feb_2026.strategy.ts # example strategy entry point
1410
+ └── modules/
1411
+ └── backtest.module.ts # CCXT Binance exchange + frame schema
1412
+ ```
1413
+
1311
1414
  ## 🌍 Environment Variables
1312
1415
 
1313
1416
  Create a `.env` file in your project root:
package/build/index.cjs CHANGED
@@ -266,16 +266,16 @@ const TYPES = {
266
266
 
267
267
  const entrySubject = new functoolsKit.BehaviorSubject();
268
268
 
269
- const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
270
- const __dirname$2 = path.dirname(__filename$2);
269
+ const __filename$3 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
270
+ const __dirname$3 = path.dirname(__filename$3);
271
271
  let _is_launched = false;
272
272
  class ResolveService {
273
273
  constructor() {
274
274
  this.loggerService = inject(TYPES.loggerService);
275
275
  this.loaderService = inject(TYPES.loaderService);
276
- this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$2, '..', 'template');
277
- this.DEFAULT_MODULES_DIR = path.resolve(__dirname$2, '..', 'modules');
278
- this.DEFAULT_CONFIG_DIR = path.resolve(__dirname$2, '..', 'config');
276
+ this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$3, '..', 'template');
277
+ this.DEFAULT_MODULES_DIR = path.resolve(__dirname$3, '..', 'modules');
278
+ this.DEFAULT_CONFIG_DIR = path.resolve(__dirname$3, '..', 'config');
279
279
  this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
280
280
  this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
281
281
  this.OVERRIDE_CONFIG_DIR = path.resolve(process.cwd(), 'config');
@@ -586,10 +586,22 @@ const getArgs = functoolsKit.singleshot(() => {
586
586
  type: "string",
587
587
  default: "",
588
588
  },
589
+ brokerdebug: {
590
+ type: "boolean",
591
+ default: false,
592
+ },
593
+ commit: {
594
+ type: "string",
595
+ default: "",
596
+ },
589
597
  init: {
590
598
  type: "boolean",
591
599
  default: false,
592
600
  },
601
+ docker: {
602
+ type: "boolean",
603
+ default: false,
604
+ },
593
605
  help: {
594
606
  type: "boolean",
595
607
  default: false,
@@ -2999,14 +3011,14 @@ const cli = {
2999
3011
  };
3000
3012
  init();
3001
3013
 
3002
- const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "flush", "init", "help", "version"];
3014
+ const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
3003
3015
  const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
3004
3016
  const HELP_TEXT$1 = `
3005
3017
  Example:
3006
3018
 
3007
3019
  node ${ENTRY_PATH$1} --help
3008
3020
  `.trimStart();
3009
- const main$e = async () => {
3021
+ const main$f = async () => {
3010
3022
  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)))) {
3011
3023
  return;
3012
3024
  }
@@ -3014,14 +3026,14 @@ const main$e = async () => {
3014
3026
  if (MODES.some((mode) => values[mode])) {
3015
3027
  return;
3016
3028
  }
3017
- process.stdout.write(`@backtest-kit/cli ${"8.1.0"}\n`);
3029
+ process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n`);
3018
3030
  process.stdout.write("\n");
3019
3031
  process.stdout.write(`Run with --help to see available commands.\n`);
3020
3032
  process.stdout.write("\n");
3021
3033
  process.stdout.write(HELP_TEXT$1);
3022
3034
  process.exit(0);
3023
3035
  };
3024
- main$e();
3036
+ main$f();
3025
3037
 
3026
3038
  const notifyShutdown = functoolsKit.singleshot(async () => {
3027
3039
  console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
@@ -3037,7 +3049,7 @@ const flush = async (entryPoint) => {
3037
3049
  console.log(`Removed: ${target}`);
3038
3050
  }
3039
3051
  };
3040
- const main$d = async () => {
3052
+ const main$e = async () => {
3041
3053
  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)))) {
3042
3054
  return;
3043
3055
  }
@@ -3054,7 +3066,7 @@ const main$d = async () => {
3054
3066
  }
3055
3067
  process.exit(0);
3056
3068
  };
3057
- main$d();
3069
+ main$e();
3058
3070
 
3059
3071
  const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3060
3072
  process.off("SIGINT", BEFORE_EXIT_FN$5);
@@ -3076,7 +3088,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
3076
3088
  const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
3077
3089
  process.on("SIGINT", BEFORE_EXIT_FN$5);
3078
3090
  });
3079
- const main$c = async () => {
3091
+ const main$d = async () => {
3080
3092
  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)))) {
3081
3093
  return;
3082
3094
  }
@@ -3091,7 +3103,7 @@ const main$c = async () => {
3091
3103
  await cli.backtestMainService.connect();
3092
3104
  listenGracefulShutdown$5();
3093
3105
  };
3094
- main$c();
3106
+ main$d();
3095
3107
 
3096
3108
  const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3097
3109
  process.off("SIGINT", BEFORE_EXIT_FN$4);
@@ -3109,7 +3121,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
3109
3121
  const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
3110
3122
  process.on("SIGINT", BEFORE_EXIT_FN$4);
3111
3123
  });
3112
- const main$b = async () => {
3124
+ const main$c = async () => {
3113
3125
  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)))) {
3114
3126
  return;
3115
3127
  }
@@ -3125,7 +3137,7 @@ const main$b = async () => {
3125
3137
  listenGracefulShutdown$4();
3126
3138
  await cli.walkerMainService.connect();
3127
3139
  };
3128
- main$b();
3140
+ main$c();
3129
3141
 
3130
3142
  const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3131
3143
  process.off("SIGINT", BEFORE_EXIT_FN$3);
@@ -3146,7 +3158,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
3146
3158
  const listenGracefulShutdown$3 = functoolsKit.singleshot(() => {
3147
3159
  process.on("SIGINT", BEFORE_EXIT_FN$3);
3148
3160
  });
3149
- const main$a = async () => {
3161
+ const main$b = async () => {
3150
3162
  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)))) {
3151
3163
  return;
3152
3164
  }
@@ -3157,7 +3169,7 @@ const main$a = async () => {
3157
3169
  cli.paperMainService.connect();
3158
3170
  listenGracefulShutdown$3();
3159
3171
  };
3160
- main$a();
3172
+ main$b();
3161
3173
 
3162
3174
  const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3163
3175
  process.off("SIGINT", BEFORE_EXIT_FN$2);
@@ -3178,7 +3190,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
3178
3190
  const listenGracefulShutdown$2 = functoolsKit.singleshot(() => {
3179
3191
  process.on("SIGINT", BEFORE_EXIT_FN$2);
3180
3192
  });
3181
- const main$9 = async () => {
3193
+ const main$a = async () => {
3182
3194
  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)))) {
3183
3195
  return;
3184
3196
  }
@@ -3189,7 +3201,7 @@ const main$9 = async () => {
3189
3201
  await cli.liveMainService.connect();
3190
3202
  listenGracefulShutdown$2();
3191
3203
  };
3192
- main$9();
3204
+ main$a();
3193
3205
 
3194
3206
  const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
3195
3207
  process.off("SIGINT", BEFORE_EXIT_FN$1);
@@ -3199,7 +3211,7 @@ const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
3199
3211
  const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
3200
3212
  process.on("SIGINT", BEFORE_EXIT_FN$1);
3201
3213
  });
3202
- const main$8 = async () => {
3214
+ const main$9 = async () => {
3203
3215
  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)))) {
3204
3216
  return;
3205
3217
  }
@@ -3209,7 +3221,7 @@ const main$8 = async () => {
3209
3221
  }
3210
3222
  listenGracefulShutdown$1();
3211
3223
  };
3212
- main$8();
3224
+ main$9();
3213
3225
 
3214
3226
  const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
3215
3227
  process.off("SIGINT", BEFORE_EXIT_FN);
@@ -3219,7 +3231,7 @@ const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
3219
3231
  const listenGracefulShutdown = functoolsKit.singleshot(() => {
3220
3232
  process.on("SIGINT", BEFORE_EXIT_FN);
3221
3233
  });
3222
- const main$7 = async () => {
3234
+ const main$8 = async () => {
3223
3235
  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)))) {
3224
3236
  return;
3225
3237
  }
@@ -3229,7 +3241,7 @@ const main$7 = async () => {
3229
3241
  }
3230
3242
  listenGracefulShutdown();
3231
3243
  };
3232
- main$7();
3244
+ main$8();
3233
3245
 
3234
3246
  const EXTRACT_ROWS_FN = (plots, schema) => {
3235
3247
  const keys = Object.keys(schema);
@@ -3251,7 +3263,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
3251
3263
  }
3252
3264
  return rows;
3253
3265
  };
3254
- const main$6 = async () => {
3266
+ const main$7 = async () => {
3255
3267
  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)))) {
3256
3268
  return;
3257
3269
  }
@@ -3331,9 +3343,9 @@ const main$6 = async () => {
3331
3343
  console.log(await BacktestKitPinets.toMarkdown(signalId, plots, signalSchema));
3332
3344
  process.exit(0);
3333
3345
  };
3334
- main$6();
3346
+ main$7();
3335
3347
 
3336
- const main$5 = async () => {
3348
+ const main$6 = async () => {
3337
3349
  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)))) {
3338
3350
  return;
3339
3351
  }
@@ -3368,9 +3380,9 @@ const main$5 = async () => {
3368
3380
  };
3369
3381
  process.on("SIGINT", beforeExit);
3370
3382
  };
3371
- main$5();
3383
+ main$6();
3372
3384
 
3373
- const main$4 = async () => {
3385
+ const main$5 = async () => {
3374
3386
  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)))) {
3375
3387
  return;
3376
3388
  }
@@ -3431,9 +3443,9 @@ const main$4 = async () => {
3431
3443
  console.log(JSON.stringify(candles, null, 2));
3432
3444
  process.exit(0);
3433
3445
  };
3434
- main$4();
3446
+ main$5();
3435
3447
 
3436
- const main$3 = async () => {
3448
+ const main$4 = async () => {
3437
3449
  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)))) {
3438
3450
  return;
3439
3451
  }
@@ -3527,16 +3539,16 @@ const main$3 = async () => {
3527
3539
  }
3528
3540
  process.exit(0);
3529
3541
  };
3530
- main$3();
3542
+ main$4();
3531
3543
 
3532
- const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
3533
- const __dirname$1 = path.dirname(__filename$1);
3544
+ const __filename$2 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
3545
+ const __dirname$2 = path.dirname(__filename$2);
3534
3546
  const MUSTACHE_EXT = ".mustache";
3535
3547
  const MUSTACHE_RENAME = {
3536
3548
  "gitignore": ".gitignore",
3537
3549
  "package": "package.json",
3538
3550
  };
3539
- async function isDirEmpty(dirPath) {
3551
+ async function isDirEmpty$1(dirPath) {
3540
3552
  try {
3541
3553
  const files = await fs$1.readdir(dirPath);
3542
3554
  return files.length === 0;
@@ -3548,13 +3560,13 @@ async function isDirEmpty(dirPath) {
3548
3560
  throw error;
3549
3561
  }
3550
3562
  }
3551
- async function copyDir(srcDir, destDir, data) {
3563
+ async function copyDir$1(srcDir, destDir, data) {
3552
3564
  await fs$1.mkdir(destDir, { recursive: true });
3553
3565
  const entries = await fs$1.readdir(srcDir, { withFileTypes: true });
3554
3566
  for (const entry of entries) {
3555
3567
  const srcPath = path.join(srcDir, entry.name);
3556
3568
  if (entry.isDirectory()) {
3557
- await copyDir(srcPath, path.join(destDir, entry.name), data);
3569
+ await copyDir$1(srcPath, path.join(destDir, entry.name), data);
3558
3570
  continue;
3559
3571
  }
3560
3572
  if (entry.name === ".gitkeep") {
@@ -3590,7 +3602,7 @@ function runScript(scriptPath, cwd) {
3590
3602
  child.on("error", reject);
3591
3603
  });
3592
3604
  }
3593
- function runNpmInstall(cwd) {
3605
+ function runNpmInstall$1(cwd) {
3594
3606
  return new Promise((resolve, reject) => {
3595
3607
  const npm = process.platform === "win32" ? "npm.cmd" : "npm";
3596
3608
  const child = child_process.spawn(npm, ["install"], { cwd, stdio: "inherit", shell: true });
@@ -3604,7 +3616,7 @@ function runNpmInstall(cwd) {
3604
3616
  child.on("error", reject);
3605
3617
  });
3606
3618
  }
3607
- const main$2 = async () => {
3619
+ const main$3 = async () => {
3608
3620
  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)))) {
3609
3621
  return;
3610
3622
  }
@@ -3614,21 +3626,95 @@ const main$2 = async () => {
3614
3626
  }
3615
3627
  const projectName = values.output || "backtest-kit-project";
3616
3628
  const projectPath = path.join(process.cwd(), projectName);
3617
- const templatePath = path.join(__dirname$1, "../template/project");
3618
- const isEmpty = await isDirEmpty(projectPath);
3629
+ const templatePath = path.join(__dirname$2, "../template/project");
3630
+ const isEmpty = await isDirEmpty$1(projectPath);
3619
3631
  if (!isEmpty) {
3620
3632
  console.error(`Directory "${projectName}" already exists and is not empty.`);
3621
3633
  process.exit(1);
3622
3634
  }
3623
3635
  console.log(`Creating project in ${projectPath}`);
3624
- await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
3636
+ await copyDir$1(templatePath, projectPath, { PROJECT_NAME: projectName });
3625
3637
  console.log(`Fetching docs...`);
3626
3638
  await runScript(path.join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
3627
3639
  console.log(`Installing dependencies...`);
3628
- await runNpmInstall(projectPath);
3640
+ await runNpmInstall$1(projectPath);
3629
3641
  console.log(`Done! Project created at ${projectPath}`);
3630
3642
  process.exit(0);
3631
3643
  };
3644
+ main$3();
3645
+
3646
+ const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
3647
+ const __dirname$1 = path.dirname(__filename$1);
3648
+ async function isDirEmpty(dirPath) {
3649
+ try {
3650
+ const files = await fs$1.readdir(dirPath);
3651
+ return files.length === 0;
3652
+ }
3653
+ catch (error) {
3654
+ if (error.code === "ENOENT") {
3655
+ return true;
3656
+ }
3657
+ throw error;
3658
+ }
3659
+ }
3660
+ async function copyDir(srcDir, destDir) {
3661
+ await fs$1.mkdir(destDir, { recursive: true });
3662
+ const entries = await fs$1.readdir(srcDir, { withFileTypes: true });
3663
+ for (const entry of entries) {
3664
+ const srcPath = path.join(srcDir, entry.name);
3665
+ if (entry.isDirectory()) {
3666
+ await copyDir(srcPath, path.join(destDir, entry.name));
3667
+ continue;
3668
+ }
3669
+ if (entry.name === ".gitkeep") {
3670
+ continue;
3671
+ }
3672
+ const destPath = path.join(destDir, entry.name);
3673
+ await fs$1.copyFile(srcPath, destPath);
3674
+ console.log(` -> ${destPath}`);
3675
+ }
3676
+ }
3677
+ function runNpmInstall(cwd) {
3678
+ return new Promise((resolve, reject) => {
3679
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
3680
+ const child = child_process.spawn(npm, ["install"], { cwd, stdio: "inherit", shell: true });
3681
+ child.on("close", (code) => {
3682
+ if (code !== 0) {
3683
+ reject(new Error(`npm install exited with code ${code}`));
3684
+ return;
3685
+ }
3686
+ resolve();
3687
+ });
3688
+ child.on("error", reject);
3689
+ });
3690
+ }
3691
+ const main$2 = async () => {
3692
+ 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)))) {
3693
+ return;
3694
+ }
3695
+ const { values } = getArgs();
3696
+ if (!values.docker) {
3697
+ return;
3698
+ }
3699
+ const projectName = values.output || "backtest-kit-docker";
3700
+ const projectPath = path.join(process.cwd(), projectName);
3701
+ const templatePath = path.join(__dirname$1, "../docker");
3702
+ const isEmpty = await isDirEmpty(projectPath);
3703
+ if (!isEmpty) {
3704
+ console.error(`Directory "${projectName}" already exists and is not empty.`);
3705
+ process.exit(1);
3706
+ }
3707
+ console.log(`Creating Docker workspace in ${projectPath}`);
3708
+ await copyDir(templatePath, projectPath);
3709
+ console.log(`Installing dependencies...`);
3710
+ await runNpmInstall(projectPath);
3711
+ console.log(`Done! Docker workspace created at ${projectPath}`);
3712
+ console.log(`Next steps:`);
3713
+ console.log(` cd ${projectName}`);
3714
+ console.log(` docker compose up -d`);
3715
+ console.log(` docker compose logs -f`);
3716
+ process.exit(0);
3717
+ };
3632
3718
  main$2();
3633
3719
 
3634
3720
  const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
@@ -3646,8 +3732,10 @@ Modes:
3646
3732
  --editor Open the Pine Script visual editor in the browser
3647
3733
  --dump Fetch and save raw OHLCV candles
3648
3734
  --pnldebug Simulate PnL per minute for a given entry price and direction
3735
+ --brokerdebug Fire a single broker commit against the live broker adapter
3649
3736
  --flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
3650
3737
  --init Scaffold a new project in the current directory
3738
+ --docker Scaffold a Docker workspace for running strategies in a container
3651
3739
  --help Print this help message
3652
3740
 
3653
3741
  Backtest flags:
@@ -3733,6 +3821,17 @@ PnL debug flags (--pnldebug):
3733
3821
 
3734
3822
  Module file ./modules/pnldebug.module is loaded automatically if it exists.
3735
3823
 
3824
+ Broker debug flags (--brokerdebug):
3825
+
3826
+ --symbol <string> Trading pair (default: BTCUSDT)
3827
+ --exchange <string> Exchange name (default: first registered)
3828
+ --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
3829
+ partial-loss, average-buy, trailing-stop, trailing-take, breakeven
3830
+ (default: signal-open)
3831
+
3832
+ Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
3833
+ the selected broker commit with synthetic payload values derived from current price.
3834
+
3736
3835
  Flush flags (--flush):
3737
3836
 
3738
3837
  One or more positional entry points. For each entry point the following
@@ -3746,6 +3845,14 @@ Init flags (--init):
3746
3845
 
3747
3846
  Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
3748
3847
 
3848
+ Docker flags (--docker):
3849
+
3850
+ --output <string> Target directory name (default: backtest-kit-docker)
3851
+
3852
+ Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
3853
+ tsconfig.json, and a sample strategy under content/. Run npm install then
3854
+ docker compose up to start the container.
3855
+
3749
3856
  Module hooks (loaded automatically by each mode):
3750
3857
 
3751
3858
  modules/backtest.module --backtest Broker adapter for backtest
@@ -3755,7 +3862,8 @@ Module hooks (loaded automatically by each mode):
3755
3862
  modules/pine.module --pine Exchange schema for PineScript runs
3756
3863
  modules/editor.module --editor Exchange schema for the visual Pine editor
3757
3864
  modules/dump.module --dump Exchange schema for candle dumps
3758
- modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
3865
+ modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
3866
+ modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
3759
3867
 
3760
3868
  --flush has no associated module. It only removes dump subdirectories.
3761
3869
 
@@ -3781,9 +3889,12 @@ Examples:
3781
3889
  node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
3782
3890
  node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
3783
3891
  node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
3892
+ node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
3893
+ node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
3784
3894
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
3785
3895
  node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
3786
3896
  node ${ENTRY_PATH} --init --output my-trading-bot
3897
+ node ${ENTRY_PATH} --docker --output my-docker-workspace
3787
3898
  `.trimStart();
3788
3899
  const main$1 = async () => {
3789
3900
  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)))) {
@@ -3793,7 +3904,7 @@ const main$1 = async () => {
3793
3904
  if (!values.help) {
3794
3905
  return;
3795
3906
  }
3796
- process.stdout.write(`@backtest-kit/cli ${"8.1.0"}\n\n`);
3907
+ process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n\n`);
3797
3908
  process.stdout.write(HELP_TEXT);
3798
3909
  process.exit(0);
3799
3910
  };
@@ -3807,7 +3918,7 @@ const main = async () => {
3807
3918
  if (!values.version) {
3808
3919
  return;
3809
3920
  }
3810
- process.stdout.write(`@backtest-kit/cli ${"8.1.0"}\n`);
3921
+ process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n`);
3811
3922
  process.exit(0);
3812
3923
  };
3813
3924
  main();