@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 +104 -1
- package/build/index.cjs +156 -45
- package/build/index.mjs +156 -45
- package/docker/.env.example +2 -0
- package/docker/content/feb_2026/feb_2026.strategy.ts +11 -0
- package/docker/content/feb_2026/modules/backtest.module.ts +83 -0
- package/docker/docker-compose.yaml +24 -0
- package/docker/package.json +38 -0
- package/docker/tsconfig.json +36 -0
- package/package.json +15 -14
- package/template/project/package.mustache +5 -5
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`
|
|
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$
|
|
270
|
-
const __dirname$
|
|
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$
|
|
277
|
-
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$
|
|
278
|
-
this.DEFAULT_CONFIG_DIR = path.resolve(__dirname$
|
|
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$
|
|
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.
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
3346
|
+
main$7();
|
|
3335
3347
|
|
|
3336
|
-
const main$
|
|
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$
|
|
3383
|
+
main$6();
|
|
3372
3384
|
|
|
3373
|
-
const main$
|
|
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$
|
|
3446
|
+
main$5();
|
|
3435
3447
|
|
|
3436
|
-
const main$
|
|
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$
|
|
3542
|
+
main$4();
|
|
3531
3543
|
|
|
3532
|
-
const __filename$
|
|
3533
|
-
const __dirname$
|
|
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$
|
|
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$
|
|
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
|
|
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.
|
|
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.
|
|
3921
|
+
process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n`);
|
|
3811
3922
|
process.exit(0);
|
|
3812
3923
|
};
|
|
3813
3924
|
main();
|