@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/build/index.mjs
CHANGED
|
@@ -241,16 +241,16 @@ const TYPES = {
|
|
|
241
241
|
|
|
242
242
|
const entrySubject = new BehaviorSubject();
|
|
243
243
|
|
|
244
|
-
const __filename$
|
|
245
|
-
const __dirname$
|
|
244
|
+
const __filename$2 = fileURLToPath(import.meta.url);
|
|
245
|
+
const __dirname$2 = path.dirname(__filename$2);
|
|
246
246
|
let _is_launched = false;
|
|
247
247
|
class ResolveService {
|
|
248
248
|
constructor() {
|
|
249
249
|
this.loggerService = inject(TYPES.loggerService);
|
|
250
250
|
this.loaderService = inject(TYPES.loaderService);
|
|
251
|
-
this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$
|
|
252
|
-
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$
|
|
253
|
-
this.DEFAULT_CONFIG_DIR = path.resolve(__dirname$
|
|
251
|
+
this.DEFAULT_TEMPLATE_DIR = path.resolve(__dirname$2, '..', 'template');
|
|
252
|
+
this.DEFAULT_MODULES_DIR = path.resolve(__dirname$2, '..', 'modules');
|
|
253
|
+
this.DEFAULT_CONFIG_DIR = path.resolve(__dirname$2, '..', 'config');
|
|
254
254
|
this.OVERRIDE_TEMPLATE_DIR = path.resolve(process.cwd(), 'template');
|
|
255
255
|
this.OVERRIDE_MODULES_DIR = path.resolve(process.cwd(), 'modules');
|
|
256
256
|
this.OVERRIDE_CONFIG_DIR = path.resolve(process.cwd(), 'config');
|
|
@@ -561,10 +561,22 @@ const getArgs = singleshot(() => {
|
|
|
561
561
|
type: "string",
|
|
562
562
|
default: "",
|
|
563
563
|
},
|
|
564
|
+
brokerdebug: {
|
|
565
|
+
type: "boolean",
|
|
566
|
+
default: false,
|
|
567
|
+
},
|
|
568
|
+
commit: {
|
|
569
|
+
type: "string",
|
|
570
|
+
default: "",
|
|
571
|
+
},
|
|
564
572
|
init: {
|
|
565
573
|
type: "boolean",
|
|
566
574
|
default: false,
|
|
567
575
|
},
|
|
576
|
+
docker: {
|
|
577
|
+
type: "boolean",
|
|
578
|
+
default: false,
|
|
579
|
+
},
|
|
568
580
|
help: {
|
|
569
581
|
type: "boolean",
|
|
570
582
|
default: false,
|
|
@@ -2970,14 +2982,14 @@ const cli = {
|
|
|
2970
2982
|
};
|
|
2971
2983
|
init();
|
|
2972
2984
|
|
|
2973
|
-
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "flush", "init", "help", "version"];
|
|
2985
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
2974
2986
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2975
2987
|
const HELP_TEXT$1 = `
|
|
2976
2988
|
Example:
|
|
2977
2989
|
|
|
2978
2990
|
node ${ENTRY_PATH$1} --help
|
|
2979
2991
|
`.trimStart();
|
|
2980
|
-
const main$
|
|
2992
|
+
const main$f = async () => {
|
|
2981
2993
|
if (!getEntry(import.meta.url)) {
|
|
2982
2994
|
return;
|
|
2983
2995
|
}
|
|
@@ -2985,14 +2997,14 @@ const main$e = async () => {
|
|
|
2985
2997
|
if (MODES.some((mode) => values[mode])) {
|
|
2986
2998
|
return;
|
|
2987
2999
|
}
|
|
2988
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
3000
|
+
process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n`);
|
|
2989
3001
|
process.stdout.write("\n");
|
|
2990
3002
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2991
3003
|
process.stdout.write("\n");
|
|
2992
3004
|
process.stdout.write(HELP_TEXT$1);
|
|
2993
3005
|
process.exit(0);
|
|
2994
3006
|
};
|
|
2995
|
-
main$
|
|
3007
|
+
main$f();
|
|
2996
3008
|
|
|
2997
3009
|
const notifyShutdown = singleshot(async () => {
|
|
2998
3010
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
@@ -3008,7 +3020,7 @@ const flush = async (entryPoint) => {
|
|
|
3008
3020
|
console.log(`Removed: ${target}`);
|
|
3009
3021
|
}
|
|
3010
3022
|
};
|
|
3011
|
-
const main$
|
|
3023
|
+
const main$e = async () => {
|
|
3012
3024
|
if (!getEntry(import.meta.url)) {
|
|
3013
3025
|
return;
|
|
3014
3026
|
}
|
|
@@ -3025,7 +3037,7 @@ const main$d = async () => {
|
|
|
3025
3037
|
}
|
|
3026
3038
|
process.exit(0);
|
|
3027
3039
|
};
|
|
3028
|
-
main$
|
|
3040
|
+
main$e();
|
|
3029
3041
|
|
|
3030
3042
|
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
3031
3043
|
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
@@ -3047,7 +3059,7 @@ const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
|
3047
3059
|
const listenGracefulShutdown$5 = singleshot(() => {
|
|
3048
3060
|
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
3049
3061
|
});
|
|
3050
|
-
const main$
|
|
3062
|
+
const main$d = async () => {
|
|
3051
3063
|
if (!getEntry(import.meta.url)) {
|
|
3052
3064
|
return;
|
|
3053
3065
|
}
|
|
@@ -3062,7 +3074,7 @@ const main$c = async () => {
|
|
|
3062
3074
|
await cli.backtestMainService.connect();
|
|
3063
3075
|
listenGracefulShutdown$5();
|
|
3064
3076
|
};
|
|
3065
|
-
main$
|
|
3077
|
+
main$d();
|
|
3066
3078
|
|
|
3067
3079
|
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
3068
3080
|
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
@@ -3080,7 +3092,7 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
3080
3092
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
3081
3093
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
3082
3094
|
});
|
|
3083
|
-
const main$
|
|
3095
|
+
const main$c = async () => {
|
|
3084
3096
|
if (!getEntry(import.meta.url)) {
|
|
3085
3097
|
return;
|
|
3086
3098
|
}
|
|
@@ -3096,7 +3108,7 @@ const main$b = async () => {
|
|
|
3096
3108
|
listenGracefulShutdown$4();
|
|
3097
3109
|
await cli.walkerMainService.connect();
|
|
3098
3110
|
};
|
|
3099
|
-
main$
|
|
3111
|
+
main$c();
|
|
3100
3112
|
|
|
3101
3113
|
const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
3102
3114
|
process.off("SIGINT", BEFORE_EXIT_FN$3);
|
|
@@ -3117,7 +3129,7 @@ const BEFORE_EXIT_FN$3 = singleshot(async () => {
|
|
|
3117
3129
|
const listenGracefulShutdown$3 = singleshot(() => {
|
|
3118
3130
|
process.on("SIGINT", BEFORE_EXIT_FN$3);
|
|
3119
3131
|
});
|
|
3120
|
-
const main$
|
|
3132
|
+
const main$b = async () => {
|
|
3121
3133
|
if (!getEntry(import.meta.url)) {
|
|
3122
3134
|
return;
|
|
3123
3135
|
}
|
|
@@ -3128,7 +3140,7 @@ const main$a = async () => {
|
|
|
3128
3140
|
cli.paperMainService.connect();
|
|
3129
3141
|
listenGracefulShutdown$3();
|
|
3130
3142
|
};
|
|
3131
|
-
main$
|
|
3143
|
+
main$b();
|
|
3132
3144
|
|
|
3133
3145
|
const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
3134
3146
|
process.off("SIGINT", BEFORE_EXIT_FN$2);
|
|
@@ -3149,7 +3161,7 @@ const BEFORE_EXIT_FN$2 = singleshot(async () => {
|
|
|
3149
3161
|
const listenGracefulShutdown$2 = singleshot(() => {
|
|
3150
3162
|
process.on("SIGINT", BEFORE_EXIT_FN$2);
|
|
3151
3163
|
});
|
|
3152
|
-
const main$
|
|
3164
|
+
const main$a = async () => {
|
|
3153
3165
|
if (!getEntry(import.meta.url)) {
|
|
3154
3166
|
return;
|
|
3155
3167
|
}
|
|
@@ -3160,7 +3172,7 @@ const main$9 = async () => {
|
|
|
3160
3172
|
await cli.liveMainService.connect();
|
|
3161
3173
|
listenGracefulShutdown$2();
|
|
3162
3174
|
};
|
|
3163
|
-
main$
|
|
3175
|
+
main$a();
|
|
3164
3176
|
|
|
3165
3177
|
const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
3166
3178
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
@@ -3170,7 +3182,7 @@ const BEFORE_EXIT_FN$1 = singleshot(async () => {
|
|
|
3170
3182
|
const listenGracefulShutdown$1 = singleshot(() => {
|
|
3171
3183
|
process.on("SIGINT", BEFORE_EXIT_FN$1);
|
|
3172
3184
|
});
|
|
3173
|
-
const main$
|
|
3185
|
+
const main$9 = async () => {
|
|
3174
3186
|
if (!getEntry(import.meta.url)) {
|
|
3175
3187
|
return;
|
|
3176
3188
|
}
|
|
@@ -3180,7 +3192,7 @@ const main$8 = async () => {
|
|
|
3180
3192
|
}
|
|
3181
3193
|
listenGracefulShutdown$1();
|
|
3182
3194
|
};
|
|
3183
|
-
main$
|
|
3195
|
+
main$9();
|
|
3184
3196
|
|
|
3185
3197
|
const BEFORE_EXIT_FN = singleshot(async () => {
|
|
3186
3198
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
@@ -3190,7 +3202,7 @@ const BEFORE_EXIT_FN = singleshot(async () => {
|
|
|
3190
3202
|
const listenGracefulShutdown = singleshot(() => {
|
|
3191
3203
|
process.on("SIGINT", BEFORE_EXIT_FN);
|
|
3192
3204
|
});
|
|
3193
|
-
const main$
|
|
3205
|
+
const main$8 = async () => {
|
|
3194
3206
|
if (!getEntry(import.meta.url)) {
|
|
3195
3207
|
return;
|
|
3196
3208
|
}
|
|
@@ -3200,7 +3212,7 @@ const main$7 = async () => {
|
|
|
3200
3212
|
}
|
|
3201
3213
|
listenGracefulShutdown();
|
|
3202
3214
|
};
|
|
3203
|
-
main$
|
|
3215
|
+
main$8();
|
|
3204
3216
|
|
|
3205
3217
|
const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
3206
3218
|
const keys = Object.keys(schema);
|
|
@@ -3222,7 +3234,7 @@ const EXTRACT_ROWS_FN = (plots, schema) => {
|
|
|
3222
3234
|
}
|
|
3223
3235
|
return rows;
|
|
3224
3236
|
};
|
|
3225
|
-
const main$
|
|
3237
|
+
const main$7 = async () => {
|
|
3226
3238
|
if (!getEntry(import.meta.url)) {
|
|
3227
3239
|
return;
|
|
3228
3240
|
}
|
|
@@ -3302,9 +3314,9 @@ const main$6 = async () => {
|
|
|
3302
3314
|
console.log(await toMarkdown(signalId, plots, signalSchema));
|
|
3303
3315
|
process.exit(0);
|
|
3304
3316
|
};
|
|
3305
|
-
main$
|
|
3317
|
+
main$7();
|
|
3306
3318
|
|
|
3307
|
-
const main$
|
|
3319
|
+
const main$6 = async () => {
|
|
3308
3320
|
if (!getEntry(import.meta.url)) {
|
|
3309
3321
|
return;
|
|
3310
3322
|
}
|
|
@@ -3339,9 +3351,9 @@ const main$5 = async () => {
|
|
|
3339
3351
|
};
|
|
3340
3352
|
process.on("SIGINT", beforeExit);
|
|
3341
3353
|
};
|
|
3342
|
-
main$
|
|
3354
|
+
main$6();
|
|
3343
3355
|
|
|
3344
|
-
const main$
|
|
3356
|
+
const main$5 = async () => {
|
|
3345
3357
|
if (!getEntry(import.meta.url)) {
|
|
3346
3358
|
return;
|
|
3347
3359
|
}
|
|
@@ -3402,9 +3414,9 @@ const main$4 = async () => {
|
|
|
3402
3414
|
console.log(JSON.stringify(candles, null, 2));
|
|
3403
3415
|
process.exit(0);
|
|
3404
3416
|
};
|
|
3405
|
-
main$
|
|
3417
|
+
main$5();
|
|
3406
3418
|
|
|
3407
|
-
const main$
|
|
3419
|
+
const main$4 = async () => {
|
|
3408
3420
|
if (!getEntry(import.meta.url)) {
|
|
3409
3421
|
return;
|
|
3410
3422
|
}
|
|
@@ -3498,16 +3510,16 @@ const main$3 = async () => {
|
|
|
3498
3510
|
}
|
|
3499
3511
|
process.exit(0);
|
|
3500
3512
|
};
|
|
3501
|
-
main$
|
|
3513
|
+
main$4();
|
|
3502
3514
|
|
|
3503
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
3504
|
-
const __dirname = dirname(__filename);
|
|
3515
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
3516
|
+
const __dirname$1 = dirname(__filename$1);
|
|
3505
3517
|
const MUSTACHE_EXT = ".mustache";
|
|
3506
3518
|
const MUSTACHE_RENAME = {
|
|
3507
3519
|
"gitignore": ".gitignore",
|
|
3508
3520
|
"package": "package.json",
|
|
3509
3521
|
};
|
|
3510
|
-
async function isDirEmpty(dirPath) {
|
|
3522
|
+
async function isDirEmpty$1(dirPath) {
|
|
3511
3523
|
try {
|
|
3512
3524
|
const files = await readdir(dirPath);
|
|
3513
3525
|
return files.length === 0;
|
|
@@ -3519,13 +3531,13 @@ async function isDirEmpty(dirPath) {
|
|
|
3519
3531
|
throw error;
|
|
3520
3532
|
}
|
|
3521
3533
|
}
|
|
3522
|
-
async function copyDir(srcDir, destDir, data) {
|
|
3534
|
+
async function copyDir$1(srcDir, destDir, data) {
|
|
3523
3535
|
await mkdir(destDir, { recursive: true });
|
|
3524
3536
|
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
3525
3537
|
for (const entry of entries) {
|
|
3526
3538
|
const srcPath = join(srcDir, entry.name);
|
|
3527
3539
|
if (entry.isDirectory()) {
|
|
3528
|
-
await copyDir(srcPath, join(destDir, entry.name), data);
|
|
3540
|
+
await copyDir$1(srcPath, join(destDir, entry.name), data);
|
|
3529
3541
|
continue;
|
|
3530
3542
|
}
|
|
3531
3543
|
if (entry.name === ".gitkeep") {
|
|
@@ -3561,7 +3573,7 @@ function runScript(scriptPath, cwd) {
|
|
|
3561
3573
|
child.on("error", reject);
|
|
3562
3574
|
});
|
|
3563
3575
|
}
|
|
3564
|
-
function runNpmInstall(cwd) {
|
|
3576
|
+
function runNpmInstall$1(cwd) {
|
|
3565
3577
|
return new Promise((resolve, reject) => {
|
|
3566
3578
|
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3567
3579
|
const child = spawn(npm, ["install"], { cwd, stdio: "inherit", shell: true });
|
|
@@ -3575,7 +3587,7 @@ function runNpmInstall(cwd) {
|
|
|
3575
3587
|
child.on("error", reject);
|
|
3576
3588
|
});
|
|
3577
3589
|
}
|
|
3578
|
-
const main$
|
|
3590
|
+
const main$3 = async () => {
|
|
3579
3591
|
if (!getEntry(import.meta.url)) {
|
|
3580
3592
|
return;
|
|
3581
3593
|
}
|
|
@@ -3585,21 +3597,95 @@ const main$2 = async () => {
|
|
|
3585
3597
|
}
|
|
3586
3598
|
const projectName = values.output || "backtest-kit-project";
|
|
3587
3599
|
const projectPath = join(process.cwd(), projectName);
|
|
3588
|
-
const templatePath = join(__dirname, "../template/project");
|
|
3589
|
-
const isEmpty = await isDirEmpty(projectPath);
|
|
3600
|
+
const templatePath = join(__dirname$1, "../template/project");
|
|
3601
|
+
const isEmpty = await isDirEmpty$1(projectPath);
|
|
3590
3602
|
if (!isEmpty) {
|
|
3591
3603
|
console.error(`Directory "${projectName}" already exists and is not empty.`);
|
|
3592
3604
|
process.exit(1);
|
|
3593
3605
|
}
|
|
3594
3606
|
console.log(`Creating project in ${projectPath}`);
|
|
3595
|
-
await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
|
|
3607
|
+
await copyDir$1(templatePath, projectPath, { PROJECT_NAME: projectName });
|
|
3596
3608
|
console.log(`Fetching docs...`);
|
|
3597
3609
|
await runScript(join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
|
|
3598
3610
|
console.log(`Installing dependencies...`);
|
|
3599
|
-
await runNpmInstall(projectPath);
|
|
3611
|
+
await runNpmInstall$1(projectPath);
|
|
3600
3612
|
console.log(`Done! Project created at ${projectPath}`);
|
|
3601
3613
|
process.exit(0);
|
|
3602
3614
|
};
|
|
3615
|
+
main$3();
|
|
3616
|
+
|
|
3617
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
3618
|
+
const __dirname = dirname(__filename);
|
|
3619
|
+
async function isDirEmpty(dirPath) {
|
|
3620
|
+
try {
|
|
3621
|
+
const files = await readdir(dirPath);
|
|
3622
|
+
return files.length === 0;
|
|
3623
|
+
}
|
|
3624
|
+
catch (error) {
|
|
3625
|
+
if (error.code === "ENOENT") {
|
|
3626
|
+
return true;
|
|
3627
|
+
}
|
|
3628
|
+
throw error;
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
async function copyDir(srcDir, destDir) {
|
|
3632
|
+
await mkdir(destDir, { recursive: true });
|
|
3633
|
+
const entries = await readdir(srcDir, { withFileTypes: true });
|
|
3634
|
+
for (const entry of entries) {
|
|
3635
|
+
const srcPath = join(srcDir, entry.name);
|
|
3636
|
+
if (entry.isDirectory()) {
|
|
3637
|
+
await copyDir(srcPath, join(destDir, entry.name));
|
|
3638
|
+
continue;
|
|
3639
|
+
}
|
|
3640
|
+
if (entry.name === ".gitkeep") {
|
|
3641
|
+
continue;
|
|
3642
|
+
}
|
|
3643
|
+
const destPath = join(destDir, entry.name);
|
|
3644
|
+
await copyFile(srcPath, destPath);
|
|
3645
|
+
console.log(` -> ${destPath}`);
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
function runNpmInstall(cwd) {
|
|
3649
|
+
return new Promise((resolve, reject) => {
|
|
3650
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3651
|
+
const child = spawn(npm, ["install"], { cwd, stdio: "inherit", shell: true });
|
|
3652
|
+
child.on("close", (code) => {
|
|
3653
|
+
if (code !== 0) {
|
|
3654
|
+
reject(new Error(`npm install exited with code ${code}`));
|
|
3655
|
+
return;
|
|
3656
|
+
}
|
|
3657
|
+
resolve();
|
|
3658
|
+
});
|
|
3659
|
+
child.on("error", reject);
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
const main$2 = async () => {
|
|
3663
|
+
if (!getEntry(import.meta.url)) {
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const { values } = getArgs();
|
|
3667
|
+
if (!values.docker) {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const projectName = values.output || "backtest-kit-docker";
|
|
3671
|
+
const projectPath = join(process.cwd(), projectName);
|
|
3672
|
+
const templatePath = join(__dirname, "../docker");
|
|
3673
|
+
const isEmpty = await isDirEmpty(projectPath);
|
|
3674
|
+
if (!isEmpty) {
|
|
3675
|
+
console.error(`Directory "${projectName}" already exists and is not empty.`);
|
|
3676
|
+
process.exit(1);
|
|
3677
|
+
}
|
|
3678
|
+
console.log(`Creating Docker workspace in ${projectPath}`);
|
|
3679
|
+
await copyDir(templatePath, projectPath);
|
|
3680
|
+
console.log(`Installing dependencies...`);
|
|
3681
|
+
await runNpmInstall(projectPath);
|
|
3682
|
+
console.log(`Done! Docker workspace created at ${projectPath}`);
|
|
3683
|
+
console.log(`Next steps:`);
|
|
3684
|
+
console.log(` cd ${projectName}`);
|
|
3685
|
+
console.log(` docker compose up -d`);
|
|
3686
|
+
console.log(` docker compose logs -f`);
|
|
3687
|
+
process.exit(0);
|
|
3688
|
+
};
|
|
3603
3689
|
main$2();
|
|
3604
3690
|
|
|
3605
3691
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
@@ -3617,8 +3703,10 @@ Modes:
|
|
|
3617
3703
|
--editor Open the Pine Script visual editor in the browser
|
|
3618
3704
|
--dump Fetch and save raw OHLCV candles
|
|
3619
3705
|
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
3706
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
3620
3707
|
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
3621
3708
|
--init Scaffold a new project in the current directory
|
|
3709
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
3622
3710
|
--help Print this help message
|
|
3623
3711
|
|
|
3624
3712
|
Backtest flags:
|
|
@@ -3704,6 +3792,17 @@ PnL debug flags (--pnldebug):
|
|
|
3704
3792
|
|
|
3705
3793
|
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
3706
3794
|
|
|
3795
|
+
Broker debug flags (--brokerdebug):
|
|
3796
|
+
|
|
3797
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
3798
|
+
--exchange <string> Exchange name (default: first registered)
|
|
3799
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
3800
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
3801
|
+
(default: signal-open)
|
|
3802
|
+
|
|
3803
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
3804
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
3805
|
+
|
|
3707
3806
|
Flush flags (--flush):
|
|
3708
3807
|
|
|
3709
3808
|
One or more positional entry points. For each entry point the following
|
|
@@ -3717,6 +3816,14 @@ Init flags (--init):
|
|
|
3717
3816
|
|
|
3718
3817
|
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
3719
3818
|
|
|
3819
|
+
Docker flags (--docker):
|
|
3820
|
+
|
|
3821
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
3822
|
+
|
|
3823
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
3824
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
3825
|
+
docker compose up to start the container.
|
|
3826
|
+
|
|
3720
3827
|
Module hooks (loaded automatically by each mode):
|
|
3721
3828
|
|
|
3722
3829
|
modules/backtest.module --backtest Broker adapter for backtest
|
|
@@ -3726,7 +3833,8 @@ Module hooks (loaded automatically by each mode):
|
|
|
3726
3833
|
modules/pine.module --pine Exchange schema for PineScript runs
|
|
3727
3834
|
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
3728
3835
|
modules/dump.module --dump Exchange schema for candle dumps
|
|
3729
|
-
modules/pnldebug.module --pnldebug
|
|
3836
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
3837
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
3730
3838
|
|
|
3731
3839
|
--flush has no associated module. It only removes dump subdirectories.
|
|
3732
3840
|
|
|
@@ -3752,9 +3860,12 @@ Examples:
|
|
|
3752
3860
|
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
3753
3861
|
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
3754
3862
|
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
3863
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
3864
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
3755
3865
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
3756
3866
|
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
3757
3867
|
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
3868
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
3758
3869
|
`.trimStart();
|
|
3759
3870
|
const main$1 = async () => {
|
|
3760
3871
|
if (!getEntry(import.meta.url)) {
|
|
@@ -3764,7 +3875,7 @@ const main$1 = async () => {
|
|
|
3764
3875
|
if (!values.help) {
|
|
3765
3876
|
return;
|
|
3766
3877
|
}
|
|
3767
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
3878
|
+
process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n\n`);
|
|
3768
3879
|
process.stdout.write(HELP_TEXT);
|
|
3769
3880
|
process.exit(0);
|
|
3770
3881
|
};
|
|
@@ -3778,7 +3889,7 @@ const main = async () => {
|
|
|
3778
3889
|
if (!values.version) {
|
|
3779
3890
|
return;
|
|
3780
3891
|
}
|
|
3781
|
-
process.stdout.write(`@backtest-kit/cli ${"8.
|
|
3892
|
+
process.stdout.write(`@backtest-kit/cli ${"8.3.0"}\n`);
|
|
3782
3893
|
process.exit(0);
|
|
3783
3894
|
};
|
|
3784
3895
|
main();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { addExchangeSchema, addFrameSchema, roundTicks } from "backtest-kit";
|
|
2
|
+
import { singleshot } from "functools-kit";
|
|
3
|
+
import ccxt from "ccxt";
|
|
4
|
+
|
|
5
|
+
const getExchange = singleshot(async () => {
|
|
6
|
+
const exchange = new ccxt.binance({
|
|
7
|
+
options: {
|
|
8
|
+
defaultType: "spot",
|
|
9
|
+
adjustForTimeDifference: true,
|
|
10
|
+
recvWindow: 60000,
|
|
11
|
+
},
|
|
12
|
+
enableRateLimit: true,
|
|
13
|
+
});
|
|
14
|
+
await exchange.loadMarkets();
|
|
15
|
+
return exchange;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
addExchangeSchema({
|
|
19
|
+
exchangeName: "ccxt-exchange",
|
|
20
|
+
getCandles: async (symbol, interval, since, limit) => {
|
|
21
|
+
const exchange = await getExchange();
|
|
22
|
+
const candles = await exchange.fetchOHLCV(
|
|
23
|
+
symbol,
|
|
24
|
+
interval,
|
|
25
|
+
since.getTime(),
|
|
26
|
+
limit,
|
|
27
|
+
);
|
|
28
|
+
return candles.map(([timestamp, open, high, low, close, volume]) => ({
|
|
29
|
+
timestamp,
|
|
30
|
+
open,
|
|
31
|
+
high,
|
|
32
|
+
low,
|
|
33
|
+
close,
|
|
34
|
+
volume,
|
|
35
|
+
}));
|
|
36
|
+
},
|
|
37
|
+
getOrderBook: async (symbol, depth, _from, _to, backtest) => {
|
|
38
|
+
if (backtest) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
"Order book fetching is not supported in backtest mode for the default exchange schema. Please implement it according to your needs.",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const exchange = await getExchange();
|
|
44
|
+
const bookData = await exchange.fetchOrderBook(symbol, depth);
|
|
45
|
+
return {
|
|
46
|
+
symbol,
|
|
47
|
+
asks: bookData.asks.map(([price, quantity]) => ({
|
|
48
|
+
price: String(price),
|
|
49
|
+
quantity: String(quantity),
|
|
50
|
+
})),
|
|
51
|
+
bids: bookData.bids.map(([price, quantity]) => ({
|
|
52
|
+
price: String(price),
|
|
53
|
+
quantity: String(quantity),
|
|
54
|
+
})),
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
formatPrice: async (symbol, price) => {
|
|
58
|
+
const exchange = await getExchange();
|
|
59
|
+
const market = exchange.market(symbol);
|
|
60
|
+
const tickSize = market.limits?.price?.min || market.precision?.price;
|
|
61
|
+
if (tickSize !== undefined) {
|
|
62
|
+
return roundTicks(price, tickSize);
|
|
63
|
+
}
|
|
64
|
+
return exchange.priceToPrecision(symbol, price);
|
|
65
|
+
},
|
|
66
|
+
formatQuantity: async (symbol, quantity) => {
|
|
67
|
+
const exchange = await getExchange();
|
|
68
|
+
const market = exchange.market(symbol);
|
|
69
|
+
const stepSize = market.limits?.amount?.min || market.precision?.amount;
|
|
70
|
+
if (stepSize !== undefined) {
|
|
71
|
+
return roundTicks(quantity, stepSize);
|
|
72
|
+
}
|
|
73
|
+
return exchange.amountToPrecision(symbol, quantity);
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
addFrameSchema({
|
|
78
|
+
frameName: "feb_2026_frame",
|
|
79
|
+
interval: "1m",
|
|
80
|
+
startDate: new Date("2026-02-01T00:00:00Z"),
|
|
81
|
+
endDate: new Date("2026-02-28T23:59:59Z"),
|
|
82
|
+
note: "February 2026",
|
|
83
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
version: '3.8'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
backtest:
|
|
5
|
+
image: tripolskypetr/backtest-kit
|
|
6
|
+
network_mode: host
|
|
7
|
+
extra_hosts:
|
|
8
|
+
- "host.docker.internal:host-gateway"
|
|
9
|
+
container_name: backtest
|
|
10
|
+
ports:
|
|
11
|
+
- "60050:60050"
|
|
12
|
+
restart: unless-stopped
|
|
13
|
+
volumes:
|
|
14
|
+
- ./:/workspace
|
|
15
|
+
working_dir: /workspace
|
|
16
|
+
command:
|
|
17
|
+
- --backtest
|
|
18
|
+
- --symbol
|
|
19
|
+
- BTCUSDT
|
|
20
|
+
- --strategy
|
|
21
|
+
- feb_2026_strategy
|
|
22
|
+
- --exchange
|
|
23
|
+
- ccxt-exchange
|
|
24
|
+
- ./content/feb_2026/feb_2026.strategy.ts
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "example",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node ./node_modules/@backtest-kit/cli/build/index.mjs",
|
|
8
|
+
"start:debug": "node --inspect-brk ./node_modules/@backtest-kit/cli/build/index.mjs"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"type": "commonjs",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "25.6.0"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@backtest-kit/cli": "8.3.0",
|
|
19
|
+
"@backtest-kit/graph": "8.3.0",
|
|
20
|
+
"@backtest-kit/pinets": "8.3.0",
|
|
21
|
+
"@backtest-kit/signals": "8.3.0",
|
|
22
|
+
"@backtest-kit/ui": "8.3.0",
|
|
23
|
+
"@tavily/core": "0.7.2",
|
|
24
|
+
"@tensorflow/tfjs": "4.22.0",
|
|
25
|
+
"@tensorflow/tfjs-backend-wasm": "4.22.0",
|
|
26
|
+
"@tensorflow/tfjs-core": "4.22.0",
|
|
27
|
+
"agent-swarm-kit": "2.6.0",
|
|
28
|
+
"backtest-kit": "8.3.0",
|
|
29
|
+
"dayjs": "1.11.20",
|
|
30
|
+
"functools-kit": "2.3.0",
|
|
31
|
+
"garch": "1.2.3",
|
|
32
|
+
"get-moment-stamp": "1.1.2",
|
|
33
|
+
"jsonrepair": "3.12.0",
|
|
34
|
+
"ollama": "0.6.3",
|
|
35
|
+
"slugify": "1.6.9",
|
|
36
|
+
"volume-anomaly": "1.2.3"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"ignoreDeprecations": "6.0",
|
|
4
|
+
"lib": [
|
|
5
|
+
"esnext",
|
|
6
|
+
"dom"
|
|
7
|
+
],
|
|
8
|
+
"types": [
|
|
9
|
+
"node"
|
|
10
|
+
],
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"target": "ES2020",
|
|
13
|
+
"module": "ESNext",
|
|
14
|
+
"moduleResolution": "bundler",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"strict": false,
|
|
17
|
+
"downlevelIteration": true,
|
|
18
|
+
"noImplicitAny": false,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUnusedLocals": false,
|
|
22
|
+
"noUnusedParameters": false,
|
|
23
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
24
|
+
"paths": {
|
|
25
|
+
"logic": ["./logic/index.ts"],
|
|
26
|
+
"logic/*": ["./logic/*"],
|
|
27
|
+
"utils": ["./utils/index.ts"],
|
|
28
|
+
"utils/*": ["./utils/*"]
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"include": [
|
|
32
|
+
"./logic",
|
|
33
|
+
"./content",
|
|
34
|
+
"./modules",
|
|
35
|
+
],
|
|
36
|
+
}
|