@backtest-kit/cli 9.8.3 → 10.1.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 +1854 -1854
- package/build/index.cjs +386 -238
- package/build/index.mjs +388 -240
- package/config/notification.config.mjs +13 -13
- package/config/symbol.config.mjs +460 -460
- package/docker/.env.example +2 -2
- package/docker/content/feb_2026/feb_2026.strategy.ts +11 -11
- package/docker/content/feb_2026/modules/backtest.module.ts +83 -83
- package/docker/docker-compose.yaml +46 -46
- package/docker/package.json +38 -38
- package/docker/tsconfig.json +36 -36
- package/package.json +126 -126
- package/template/average-buy.mustache +22 -22
- package/template/breakeven.mustache +21 -21
- package/template/cancel-scheduled.mustache +14 -14
- package/template/cancelled.mustache +21 -21
- package/template/close-pending.mustache +16 -16
- package/template/closed.mustache +23 -23
- package/template/opened.mustache +22 -22
- package/template/partial-loss.mustache +22 -22
- package/template/partial-profit.mustache +22 -22
- package/template/project/config/symbol.config.ts +460 -460
- package/template/project/package.mustache +28 -28
- package/template/risk.mustache +19 -19
- package/template/scheduled.mustache +22 -22
- package/template/signal-close.mustache +22 -22
- package/template/signal-info.mustache +20 -20
- package/template/signal-open.mustache +22 -22
- package/template/source/CLAUDE.md +160 -160
- package/template/trailing-stop.mustache +21 -21
- package/template/trailing-take.mustache +21 -21
- package/types.d.ts +0 -1
package/build/index.cjs
CHANGED
|
@@ -12,6 +12,7 @@ var diKit = require('di-kit');
|
|
|
12
12
|
var url = require('url');
|
|
13
13
|
var ccxt = require('ccxt');
|
|
14
14
|
var util = require('util');
|
|
15
|
+
var child_process = require('child_process');
|
|
15
16
|
var BacktestKitUi = require('@backtest-kit/ui');
|
|
16
17
|
var QuickChart = require('quickchart-js');
|
|
17
18
|
var telegraf = require('telegraf');
|
|
@@ -30,7 +31,6 @@ var BacktestKitOllama = require('@backtest-kit/ollama');
|
|
|
30
31
|
var BacktestKitPinets = require('@backtest-kit/pinets');
|
|
31
32
|
var BacktestKitSignals = require('@backtest-kit/signals');
|
|
32
33
|
var open = require('open');
|
|
33
|
-
var child_process = require('child_process');
|
|
34
34
|
|
|
35
35
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
36
36
|
function _interopNamespaceDefault(e) {
|
|
@@ -279,6 +279,9 @@ class SetupUtils {
|
|
|
279
279
|
const config = cli.configService.getNotificationConfig();
|
|
280
280
|
BacktestKit.Notification.enable(config);
|
|
281
281
|
}
|
|
282
|
+
{
|
|
283
|
+
BacktestKit.Cron.enable();
|
|
284
|
+
}
|
|
282
285
|
{
|
|
283
286
|
BacktestKit.Recent.enable();
|
|
284
287
|
BacktestKit.Storage.enable();
|
|
@@ -300,6 +303,9 @@ class SetupUtils {
|
|
|
300
303
|
return;
|
|
301
304
|
}
|
|
302
305
|
this.enable.clear();
|
|
306
|
+
{
|
|
307
|
+
BacktestKit.Cron.disable();
|
|
308
|
+
}
|
|
303
309
|
{
|
|
304
310
|
BacktestKit.Recent.disable();
|
|
305
311
|
BacktestKit.Storage.disable();
|
|
@@ -856,6 +862,129 @@ const notifyVerbose = functoolsKit.singleshot(() => {
|
|
|
856
862
|
|
|
857
863
|
const entrySubject = new functoolsKit.BehaviorSubject();
|
|
858
864
|
|
|
865
|
+
function killPid(pid, signal) {
|
|
866
|
+
try {
|
|
867
|
+
process.kill(pid, signal);
|
|
868
|
+
}
|
|
869
|
+
catch (err) {
|
|
870
|
+
if (err.code !== "ESRCH")
|
|
871
|
+
throw err;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function killAll(tree, signal, callback) {
|
|
875
|
+
const killed = {};
|
|
876
|
+
try {
|
|
877
|
+
for (const pid of Object.keys(tree).map(Number)) {
|
|
878
|
+
for (const child of tree[pid]) {
|
|
879
|
+
if (!killed[child]) {
|
|
880
|
+
killPid(child, signal);
|
|
881
|
+
killed[child] = true;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (!killed[pid]) {
|
|
885
|
+
killPid(pid, signal);
|
|
886
|
+
killed[pid] = true;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
catch (err) {
|
|
891
|
+
if (callback)
|
|
892
|
+
return callback(err);
|
|
893
|
+
throw err;
|
|
894
|
+
}
|
|
895
|
+
callback?.();
|
|
896
|
+
}
|
|
897
|
+
function buildProcessTree(parentPid, tree, pidsToProcess, spawnList, cb) {
|
|
898
|
+
const ps = spawnList(parentPid);
|
|
899
|
+
let allData = "";
|
|
900
|
+
ps.stdout.on("data", (data) => {
|
|
901
|
+
allData += data.toString("ascii");
|
|
902
|
+
});
|
|
903
|
+
ps.on("close", (code) => {
|
|
904
|
+
delete pidsToProcess[parentPid];
|
|
905
|
+
if (code !== 0) {
|
|
906
|
+
if (Object.keys(pidsToProcess).length === 0)
|
|
907
|
+
cb();
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
const matches = allData.match(/\d+/g);
|
|
911
|
+
if (matches) {
|
|
912
|
+
for (const pidStr of matches) {
|
|
913
|
+
const pid = parseInt(pidStr, 10);
|
|
914
|
+
tree[parentPid].push(pid);
|
|
915
|
+
tree[pid] = [];
|
|
916
|
+
pidsToProcess[pid] = 1;
|
|
917
|
+
buildProcessTree(pid, tree, pidsToProcess, spawnList, cb);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
function treeKill(pid, signalOrCallback, callback) {
|
|
923
|
+
let signal;
|
|
924
|
+
if (typeof signalOrCallback === "function") {
|
|
925
|
+
callback = signalOrCallback;
|
|
926
|
+
signal = undefined;
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
signal = signalOrCallback;
|
|
930
|
+
}
|
|
931
|
+
pid = parseInt(pid, 10);
|
|
932
|
+
if (Number.isNaN(pid)) {
|
|
933
|
+
const err = new Error("pid must be a number");
|
|
934
|
+
if (callback)
|
|
935
|
+
return callback(err);
|
|
936
|
+
throw err;
|
|
937
|
+
}
|
|
938
|
+
const tree = { [pid]: [] };
|
|
939
|
+
const pidsToProcess = { [pid]: 1 };
|
|
940
|
+
switch (process.platform) {
|
|
941
|
+
case "win32":
|
|
942
|
+
child_process.exec(`taskkill /pid ${pid} /T /F`, (err) => callback?.(err ?? undefined));
|
|
943
|
+
break;
|
|
944
|
+
case "darwin":
|
|
945
|
+
buildProcessTree(pid, tree, pidsToProcess, (parentPid) => child_process.spawn("pgrep", ["-P", String(parentPid)]), () => killAll(tree, signal, callback));
|
|
946
|
+
break;
|
|
947
|
+
default: // Linux
|
|
948
|
+
buildProcessTree(pid, tree, pidsToProcess, (parentPid) => child_process.spawn("ps", ["-o", "pid", "--no-headers", "--ppid", String(parentPid)]), () => killAll(tree, signal, callback));
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
const DRAIN_MAX_AWAIT = 250;
|
|
954
|
+
const drainStream = (stream) => new Promise((resolve) => {
|
|
955
|
+
if (stream.writableLength === 0) {
|
|
956
|
+
stream.write("", () => resolve());
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
stream.once("drain", () => {
|
|
960
|
+
stream.write("", () => resolve());
|
|
961
|
+
});
|
|
962
|
+
});
|
|
963
|
+
const flushStream = (stream) => {
|
|
964
|
+
const handle = stream._handle;
|
|
965
|
+
if (handle && typeof handle.setBlocking === "function") {
|
|
966
|
+
handle.setBlocking(true);
|
|
967
|
+
return new Promise((resolve) => stream.write("", () => resolve()));
|
|
968
|
+
}
|
|
969
|
+
return drainStream(stream);
|
|
970
|
+
};
|
|
971
|
+
const kill = functoolsKit.singleshot(async (code = -1) => {
|
|
972
|
+
await Promise.race([
|
|
973
|
+
Promise.all([
|
|
974
|
+
flushStream(process.stdout),
|
|
975
|
+
flushStream(process.stderr)
|
|
976
|
+
]),
|
|
977
|
+
functoolsKit.sleep(DRAIN_MAX_AWAIT),
|
|
978
|
+
]);
|
|
979
|
+
treeKill(process.pid, "SIGKILL", () => {
|
|
980
|
+
process.exit(code);
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
const notifyKill = functoolsKit.singleshot(() => {
|
|
984
|
+
console.log("Press Ctrl+C again to force quit.");
|
|
985
|
+
process.on("SIGINT", () => kill());
|
|
986
|
+
});
|
|
987
|
+
|
|
859
988
|
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
860
989
|
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
861
990
|
const { values } = getArgs();
|
|
@@ -883,6 +1012,10 @@ class BacktestMainService {
|
|
|
883
1012
|
this.loggerService.log("backtestMainService run", {
|
|
884
1013
|
payload,
|
|
885
1014
|
});
|
|
1015
|
+
{
|
|
1016
|
+
const cwd = process.cwd();
|
|
1017
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1018
|
+
}
|
|
886
1019
|
await this.configConnectionService.loadConfig("setup.config");
|
|
887
1020
|
{
|
|
888
1021
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -896,7 +1029,8 @@ class BacktestMainService {
|
|
|
896
1029
|
}
|
|
897
1030
|
catch (error) {
|
|
898
1031
|
console.error("Module loader failed", error);
|
|
899
|
-
|
|
1032
|
+
kill(-1);
|
|
1033
|
+
return;
|
|
900
1034
|
}
|
|
901
1035
|
}
|
|
902
1036
|
{
|
|
@@ -907,10 +1041,6 @@ class BacktestMainService {
|
|
|
907
1041
|
this.frontendProviderService.connect();
|
|
908
1042
|
this.telegramProviderService.connect();
|
|
909
1043
|
}
|
|
910
|
-
{
|
|
911
|
-
const cwd = process.cwd();
|
|
912
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
913
|
-
}
|
|
914
1044
|
let absolutePath;
|
|
915
1045
|
{
|
|
916
1046
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -1019,6 +1149,10 @@ class WalkerMainService {
|
|
|
1019
1149
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1020
1150
|
this.run = functoolsKit.singleshot(async (payload) => {
|
|
1021
1151
|
this.loggerService.log("walkerMainService run", { payload });
|
|
1152
|
+
{
|
|
1153
|
+
const cwd = process.cwd();
|
|
1154
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1155
|
+
}
|
|
1022
1156
|
await this.configConnectionService.loadConfig("setup.config");
|
|
1023
1157
|
{
|
|
1024
1158
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1032,7 +1166,8 @@ class WalkerMainService {
|
|
|
1032
1166
|
}
|
|
1033
1167
|
catch (error) {
|
|
1034
1168
|
console.error("Module loader failed", error);
|
|
1035
|
-
|
|
1169
|
+
kill(-1);
|
|
1170
|
+
return;
|
|
1036
1171
|
}
|
|
1037
1172
|
}
|
|
1038
1173
|
{
|
|
@@ -1068,10 +1203,6 @@ class WalkerMainService {
|
|
|
1068
1203
|
BacktestKit.Cache.resetCounter();
|
|
1069
1204
|
BacktestKit.Interval.resetCounter();
|
|
1070
1205
|
}
|
|
1071
|
-
{
|
|
1072
|
-
const cwd = process.cwd();
|
|
1073
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1074
|
-
}
|
|
1075
1206
|
await this.moduleConnectionService.loadModule("walker.module");
|
|
1076
1207
|
{
|
|
1077
1208
|
this.exchangeSchemaService.addSchema();
|
|
@@ -1200,7 +1331,7 @@ class WalkerMainService {
|
|
|
1200
1331
|
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
1201
1332
|
await fs$1.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
1202
1333
|
console.log(`Saved: ${filePath}`);
|
|
1203
|
-
|
|
1334
|
+
kill(0);
|
|
1204
1335
|
return;
|
|
1205
1336
|
}
|
|
1206
1337
|
if (payload.markdown) {
|
|
@@ -1209,12 +1340,13 @@ class WalkerMainService {
|
|
|
1209
1340
|
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
1210
1341
|
await fs$1.writeFile(filePath, report, "utf-8");
|
|
1211
1342
|
console.log(`Saved: ${filePath}`);
|
|
1212
|
-
|
|
1343
|
+
kill(0);
|
|
1213
1344
|
return;
|
|
1214
1345
|
}
|
|
1215
1346
|
const report = await BacktestKit.Walker.getReport(symbol, { walkerName: WalkerName$1.DefaultWalker });
|
|
1216
1347
|
console.log(report);
|
|
1217
|
-
|
|
1348
|
+
kill(0);
|
|
1349
|
+
return;
|
|
1218
1350
|
});
|
|
1219
1351
|
this.connect = functoolsKit.singleshot(async () => {
|
|
1220
1352
|
this.loggerService.log("walkerMainService connect");
|
|
@@ -1259,6 +1391,10 @@ class LiveMainService {
|
|
|
1259
1391
|
this.loggerService.log("liveMainService run", {
|
|
1260
1392
|
payload,
|
|
1261
1393
|
});
|
|
1394
|
+
{
|
|
1395
|
+
const cwd = process.cwd();
|
|
1396
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1397
|
+
}
|
|
1262
1398
|
await this.configConnectionService.loadConfig("setup.config");
|
|
1263
1399
|
{
|
|
1264
1400
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1272,7 +1408,8 @@ class LiveMainService {
|
|
|
1272
1408
|
}
|
|
1273
1409
|
catch (error) {
|
|
1274
1410
|
console.error("Module loader failed", error);
|
|
1275
|
-
|
|
1411
|
+
kill(-1);
|
|
1412
|
+
return;
|
|
1276
1413
|
}
|
|
1277
1414
|
}
|
|
1278
1415
|
{
|
|
@@ -1283,10 +1420,6 @@ class LiveMainService {
|
|
|
1283
1420
|
this.frontendProviderService.connect();
|
|
1284
1421
|
this.telegramProviderService.connect();
|
|
1285
1422
|
}
|
|
1286
|
-
{
|
|
1287
|
-
const cwd = process.cwd();
|
|
1288
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1289
|
-
}
|
|
1290
1423
|
let absolutePath;
|
|
1291
1424
|
{
|
|
1292
1425
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -1362,6 +1495,10 @@ class PaperMainService {
|
|
|
1362
1495
|
this.configConnectionService = inject(TYPES.configConnectionService);
|
|
1363
1496
|
this.run = functoolsKit.singleshot(async (payload) => {
|
|
1364
1497
|
this.loggerService.log("paperMainService init");
|
|
1498
|
+
{
|
|
1499
|
+
const cwd = process.cwd();
|
|
1500
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1501
|
+
}
|
|
1365
1502
|
await this.configConnectionService.loadConfig("setup.config");
|
|
1366
1503
|
{
|
|
1367
1504
|
const loader = await this.configConnectionService.loadConfig("loader.config");
|
|
@@ -1375,7 +1512,8 @@ class PaperMainService {
|
|
|
1375
1512
|
}
|
|
1376
1513
|
catch (error) {
|
|
1377
1514
|
console.error("Module loader failed", error);
|
|
1378
|
-
|
|
1515
|
+
kill(-1);
|
|
1516
|
+
return;
|
|
1379
1517
|
}
|
|
1380
1518
|
}
|
|
1381
1519
|
{
|
|
@@ -1386,10 +1524,6 @@ class PaperMainService {
|
|
|
1386
1524
|
this.frontendProviderService.connect();
|
|
1387
1525
|
this.telegramProviderService.connect();
|
|
1388
1526
|
}
|
|
1389
|
-
{
|
|
1390
|
-
const cwd = process.cwd();
|
|
1391
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
1392
|
-
}
|
|
1393
1527
|
let absolutePath;
|
|
1394
1528
|
{
|
|
1395
1529
|
absolutePath = await this.resolveService.attachJavascript(payload.entryPoint);
|
|
@@ -1857,7 +1991,7 @@ class TelegramApiService {
|
|
|
1857
1991
|
console.error("Telegram publish failure", {
|
|
1858
1992
|
error,
|
|
1859
1993
|
});
|
|
1860
|
-
setTimeout(() =>
|
|
1994
|
+
setTimeout(() => kill(-1), 5000);
|
|
1861
1995
|
});
|
|
1862
1996
|
const result = await Promise.race([
|
|
1863
1997
|
waitForResult,
|
|
@@ -1871,7 +2005,7 @@ class TelegramApiService {
|
|
|
1871
2005
|
return "Message scheduled for publication";
|
|
1872
2006
|
}
|
|
1873
2007
|
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
1874
|
-
setTimeout(() =>
|
|
2008
|
+
setTimeout(() => kill(-1), 5000);
|
|
1875
2009
|
}
|
|
1876
2010
|
return "Message published successfully";
|
|
1877
2011
|
};
|
|
@@ -2584,7 +2718,8 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
|
|
|
2584
2718
|
}
|
|
2585
2719
|
catch {
|
|
2586
2720
|
console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
|
|
2587
|
-
|
|
2721
|
+
kill(-1);
|
|
2722
|
+
return false;
|
|
2588
2723
|
}
|
|
2589
2724
|
}
|
|
2590
2725
|
return false;
|
|
@@ -2704,7 +2839,8 @@ const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, se
|
|
|
2704
2839
|
}
|
|
2705
2840
|
catch (error) {
|
|
2706
2841
|
console.log(`Error during transpilation error=\`${functoolsKit.getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
2707
|
-
|
|
2842
|
+
kill(-1);
|
|
2843
|
+
return;
|
|
2708
2844
|
}
|
|
2709
2845
|
return {
|
|
2710
2846
|
require,
|
|
@@ -2968,7 +3104,7 @@ const GET_ALIAS_EXPORTS_FN = (self) => {
|
|
|
2968
3104
|
}
|
|
2969
3105
|
return null;
|
|
2970
3106
|
};
|
|
2971
|
-
const INIT_ALIAS_FN = (self) => {
|
|
3107
|
+
const INIT_ALIAS_FN = functoolsKit.singleshot((self) => {
|
|
2972
3108
|
const alias = GET_ALIAS_EXPORTS_FN(self);
|
|
2973
3109
|
if (!alias) {
|
|
2974
3110
|
return;
|
|
@@ -2980,18 +3116,21 @@ const INIT_ALIAS_FN = (self) => {
|
|
|
2980
3116
|
Object.entries(alias).forEach(([name, module]) => overrideModule(name, module));
|
|
2981
3117
|
Object.assign(IMPORT_ALIAS, alias);
|
|
2982
3118
|
}
|
|
2983
|
-
};
|
|
3119
|
+
});
|
|
2984
3120
|
class LoaderService {
|
|
2985
3121
|
constructor() {
|
|
2986
3122
|
this.babelService = inject(TYPES.babelService);
|
|
2987
3123
|
this.loggerService = inject(TYPES.loggerService);
|
|
2988
3124
|
this.resolveService = inject(TYPES.resolveService);
|
|
2989
|
-
this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) =>
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3125
|
+
this.getInstance = functoolsKit.memoize(([basePath]) => `${basePath}`, (basePath) => {
|
|
3126
|
+
INIT_ALIAS_FN(this);
|
|
3127
|
+
return new ClientLoader({
|
|
3128
|
+
babel: this.babelService,
|
|
3129
|
+
logger: this.loggerService,
|
|
3130
|
+
resolve: this.resolveService,
|
|
3131
|
+
path: basePath,
|
|
3132
|
+
});
|
|
3133
|
+
});
|
|
2995
3134
|
this.import = (filePath, basePath = process.cwd()) => {
|
|
2996
3135
|
this.loggerService.log("loaderService import", {
|
|
2997
3136
|
filePath,
|
|
@@ -3008,10 +3147,6 @@ class LoaderService {
|
|
|
3008
3147
|
const instance = this.getInstance(basePath);
|
|
3009
3148
|
return instance.check(filePath);
|
|
3010
3149
|
};
|
|
3011
|
-
this.init = functoolsKit.singleshot(() => {
|
|
3012
|
-
this.loggerService.log("loaderService init");
|
|
3013
|
-
INIT_ALIAS_FN(this);
|
|
3014
|
-
});
|
|
3015
3150
|
}
|
|
3016
3151
|
}
|
|
3017
3152
|
|
|
@@ -3040,7 +3175,8 @@ const LOAD_CONFIG_CONFIG_FN = async (fileName, self) => {
|
|
|
3040
3175
|
}
|
|
3041
3176
|
catch {
|
|
3042
3177
|
console.warn(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
|
|
3043
|
-
|
|
3178
|
+
kill(-1);
|
|
3179
|
+
return;
|
|
3044
3180
|
}
|
|
3045
3181
|
}
|
|
3046
3182
|
return null;
|
|
@@ -3212,10 +3348,10 @@ init();
|
|
|
3212
3348
|
|
|
3213
3349
|
const MODES = ["backtest", "walker", "paper", "live", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3214
3350
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3215
|
-
const HELP_TEXT$1 = `
|
|
3216
|
-
Example:
|
|
3217
|
-
|
|
3218
|
-
node ${ENTRY_PATH$1} --help
|
|
3351
|
+
const HELP_TEXT$1 = `
|
|
3352
|
+
Example:
|
|
3353
|
+
|
|
3354
|
+
node ${ENTRY_PATH$1} --help
|
|
3219
3355
|
`.trimStart();
|
|
3220
3356
|
const main$g = async () => {
|
|
3221
3357
|
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)))) {
|
|
@@ -3225,7 +3361,7 @@ const main$g = async () => {
|
|
|
3225
3361
|
if (MODES.some((mode) => values[mode])) {
|
|
3226
3362
|
return;
|
|
3227
3363
|
}
|
|
3228
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
3364
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
|
|
3229
3365
|
process.stdout.write("\n");
|
|
3230
3366
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3231
3367
|
process.stdout.write("\n");
|
|
@@ -3235,7 +3371,7 @@ const main$g = async () => {
|
|
|
3235
3371
|
main$g();
|
|
3236
3372
|
|
|
3237
3373
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
3238
|
-
console.log("Graceful shutdown initiated.
|
|
3374
|
+
console.log("Graceful shutdown initiated.");
|
|
3239
3375
|
});
|
|
3240
3376
|
|
|
3241
3377
|
const FLUSH_DIRS = ["report", "log", "markdown", "agent"];
|
|
@@ -3274,6 +3410,7 @@ const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
|
3274
3410
|
return;
|
|
3275
3411
|
}
|
|
3276
3412
|
notifyShutdown();
|
|
3413
|
+
notifyKill();
|
|
3277
3414
|
const { exchangeName, frameName, strategyName, symbol, status } = running;
|
|
3278
3415
|
if (status === "fulfilled") {
|
|
3279
3416
|
return;
|
|
@@ -3314,6 +3451,7 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
3314
3451
|
return;
|
|
3315
3452
|
}
|
|
3316
3453
|
notifyShutdown();
|
|
3454
|
+
notifyKill();
|
|
3317
3455
|
const { walkerName, symbol, status } = running;
|
|
3318
3456
|
if (status === "fulfilled") {
|
|
3319
3457
|
return;
|
|
@@ -3351,6 +3489,7 @@ const BEFORE_EXIT_FN$3 = functoolsKit.singleshot(async () => {
|
|
|
3351
3489
|
return;
|
|
3352
3490
|
}
|
|
3353
3491
|
notifyShutdown();
|
|
3492
|
+
notifyKill();
|
|
3354
3493
|
const { exchangeName, strategyName, symbol, status } = running;
|
|
3355
3494
|
if (status === "fulfilled") {
|
|
3356
3495
|
return;
|
|
@@ -3386,6 +3525,7 @@ const BEFORE_EXIT_FN$2 = functoolsKit.singleshot(async () => {
|
|
|
3386
3525
|
return;
|
|
3387
3526
|
}
|
|
3388
3527
|
notifyShutdown();
|
|
3528
|
+
notifyKill();
|
|
3389
3529
|
const { exchangeName, strategyName, symbol, status } = running;
|
|
3390
3530
|
if (status === "fulfilled") {
|
|
3391
3531
|
return;
|
|
@@ -3486,6 +3626,7 @@ const createGracefulShutdown = (mode) => {
|
|
|
3486
3626
|
const handler = functoolsKit.singleshot(async () => {
|
|
3487
3627
|
process.off("SIGINT", handler);
|
|
3488
3628
|
notifyShutdown();
|
|
3629
|
+
notifyKill();
|
|
3489
3630
|
await stop();
|
|
3490
3631
|
});
|
|
3491
3632
|
return functoolsKit.singleshot(() => {
|
|
@@ -3503,13 +3644,17 @@ const main$a = async () => {
|
|
|
3503
3644
|
const mode = resolveMode(values);
|
|
3504
3645
|
if (!mode) {
|
|
3505
3646
|
console.error("--entry requires exactly one of --backtest, --live, --paper, --walker");
|
|
3506
|
-
|
|
3647
|
+
kill(1);
|
|
3507
3648
|
return;
|
|
3508
3649
|
}
|
|
3509
3650
|
const entryPoints = getPositionals();
|
|
3510
3651
|
if (!entryPoints.length) {
|
|
3511
3652
|
throw new Error("At least one entry point is required");
|
|
3512
3653
|
}
|
|
3654
|
+
{
|
|
3655
|
+
const cwd = process.cwd();
|
|
3656
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3657
|
+
}
|
|
3513
3658
|
await cli.configConnectionService.loadConfig("setup.config");
|
|
3514
3659
|
{
|
|
3515
3660
|
const loader = await cli.configConnectionService.loadConfig("loader.config");
|
|
@@ -3523,7 +3668,8 @@ const main$a = async () => {
|
|
|
3523
3668
|
}
|
|
3524
3669
|
catch (error) {
|
|
3525
3670
|
console.error("Module loader failed", error);
|
|
3526
|
-
|
|
3671
|
+
kill(-1);
|
|
3672
|
+
return;
|
|
3527
3673
|
}
|
|
3528
3674
|
}
|
|
3529
3675
|
{
|
|
@@ -3533,9 +3679,6 @@ const main$a = async () => {
|
|
|
3533
3679
|
cli.frontendProviderService.connect();
|
|
3534
3680
|
cli.telegramProviderService.connect();
|
|
3535
3681
|
const cwd = process.cwd();
|
|
3536
|
-
{
|
|
3537
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3538
|
-
}
|
|
3539
3682
|
if (entryPoints.length === 1) {
|
|
3540
3683
|
const absolutePath = path.resolve(entryPoints[0]);
|
|
3541
3684
|
const moduleRoot = path.dirname(absolutePath);
|
|
@@ -3565,6 +3708,7 @@ main$a();
|
|
|
3565
3708
|
const BEFORE_EXIT_FN$1 = functoolsKit.singleshot(async () => {
|
|
3566
3709
|
process.off("SIGINT", BEFORE_EXIT_FN$1);
|
|
3567
3710
|
notifyShutdown();
|
|
3711
|
+
notifyKill();
|
|
3568
3712
|
cli.frontendProviderService.disable();
|
|
3569
3713
|
});
|
|
3570
3714
|
const listenGracefulShutdown$1 = functoolsKit.singleshot(() => {
|
|
@@ -3585,6 +3729,7 @@ main$9();
|
|
|
3585
3729
|
const BEFORE_EXIT_FN = functoolsKit.singleshot(async () => {
|
|
3586
3730
|
process.off("SIGINT", BEFORE_EXIT_FN);
|
|
3587
3731
|
notifyShutdown();
|
|
3732
|
+
notifyKill();
|
|
3588
3733
|
cli.telegramProviderService.disable();
|
|
3589
3734
|
});
|
|
3590
3735
|
const listenGracefulShutdown = functoolsKit.singleshot(() => {
|
|
@@ -3734,7 +3879,12 @@ const main$6 = async () => {
|
|
|
3734
3879
|
}
|
|
3735
3880
|
if (values.pine) {
|
|
3736
3881
|
console.warn("--editor and --pine are mutually exclusive. Use one at a time.");
|
|
3737
|
-
|
|
3882
|
+
kill(1);
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
{
|
|
3886
|
+
const cwd = process.cwd();
|
|
3887
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3738
3888
|
}
|
|
3739
3889
|
await cli.configConnectionService.loadConfig("setup.config");
|
|
3740
3890
|
{
|
|
@@ -3749,17 +3899,14 @@ const main$6 = async () => {
|
|
|
3749
3899
|
}
|
|
3750
3900
|
catch (error) {
|
|
3751
3901
|
console.error("Module loader failed", error);
|
|
3752
|
-
|
|
3902
|
+
kill(-1);
|
|
3903
|
+
return;
|
|
3753
3904
|
}
|
|
3754
3905
|
}
|
|
3755
3906
|
{
|
|
3756
3907
|
await cli.configService.waitForInit();
|
|
3757
3908
|
Setup.enable();
|
|
3758
3909
|
}
|
|
3759
|
-
{
|
|
3760
|
-
const cwd = process.cwd();
|
|
3761
|
-
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
3762
|
-
}
|
|
3763
3910
|
await cli.moduleConnectionService.loadModule("editor.module");
|
|
3764
3911
|
{
|
|
3765
3912
|
await cli.exchangeSchemaService.addSchema();
|
|
@@ -3779,7 +3926,8 @@ const main$6 = async () => {
|
|
|
3779
3926
|
const beforeExit = () => {
|
|
3780
3927
|
process.off("SIGINT", beforeExit);
|
|
3781
3928
|
unServer();
|
|
3782
|
-
|
|
3929
|
+
kill(0);
|
|
3930
|
+
return;
|
|
3783
3931
|
};
|
|
3784
3932
|
process.on("SIGINT", beforeExit);
|
|
3785
3933
|
};
|
|
@@ -4164,183 +4312,183 @@ const main$2 = async () => {
|
|
|
4164
4312
|
main$2();
|
|
4165
4313
|
|
|
4166
4314
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
4167
|
-
const HELP_TEXT = `
|
|
4168
|
-
Usage:
|
|
4169
|
-
node index.mjs --<mode> [flags] [entry-point]
|
|
4170
|
-
|
|
4171
|
-
Modes:
|
|
4172
|
-
|
|
4173
|
-
--backtest <entry> Run strategy against historical candle data
|
|
4174
|
-
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4175
|
-
--paper <entry> Paper trading (live prices, no real orders)
|
|
4176
|
-
--live <entry> Live trading with real orders
|
|
4177
|
-
--pine <entry> Execute a local .pine indicator file
|
|
4178
|
-
--editor Open the Pine Script visual editor in the browser
|
|
4179
|
-
--dump Fetch and save raw OHLCV candles
|
|
4180
|
-
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4181
|
-
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4182
|
-
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4183
|
-
--init Scaffold a new project in the current directory
|
|
4184
|
-
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4185
|
-
--help Print this help message
|
|
4186
|
-
|
|
4187
|
-
Backtest flags:
|
|
4188
|
-
|
|
4189
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4190
|
-
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4191
|
-
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4192
|
-
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4193
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4194
|
-
--noCache Skip candle cache warming before the run
|
|
4195
|
-
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4196
|
-
--verbose Log every candle fetch to stdout
|
|
4197
|
-
--ui Start web dashboard at http://localhost:60050
|
|
4198
|
-
--telegram Send trade notifications to Telegram
|
|
4199
|
-
|
|
4200
|
-
Walker flags (--walker):
|
|
4201
|
-
|
|
4202
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4203
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4204
|
-
--noCache Skip candle cache warming before the run
|
|
4205
|
-
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4206
|
-
--verbose Log every candle fetch to stdout
|
|
4207
|
-
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4208
|
-
--json Save results as JSON to ./dump/<output>.json
|
|
4209
|
-
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4210
|
-
|
|
4211
|
-
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
4212
|
-
changing process.cwd() — .env is read from the working directory only.
|
|
4213
|
-
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4214
|
-
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4215
|
-
|
|
4216
|
-
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4217
|
-
|
|
4218
|
-
Paper / Live flags:
|
|
4219
|
-
|
|
4220
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4221
|
-
--strategy <string> Strategy name (default: first registered)
|
|
4222
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4223
|
-
--verbose Log every candle fetch to stdout
|
|
4224
|
-
--ui Start web dashboard
|
|
4225
|
-
--telegram Send Telegram notifications
|
|
4226
|
-
|
|
4227
|
-
PineScript flags (--pine):
|
|
4228
|
-
|
|
4229
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4230
|
-
--timeframe <string> Candle interval (default: 15m)
|
|
4231
|
-
--limit <string> Number of candles to fetch (default: 250)
|
|
4232
|
-
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4233
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4234
|
-
--output <string> Output file base name without extension
|
|
4235
|
-
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4236
|
-
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4237
|
-
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4238
|
-
|
|
4239
|
-
Only plot() calls with display=display.data_window produce output columns.
|
|
4240
|
-
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4241
|
-
|
|
4242
|
-
Candle dump flags (--dump):
|
|
4243
|
-
|
|
4244
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4245
|
-
--timeframe <string> Candle interval (default: 15m)
|
|
4246
|
-
--limit <string> Number of candles (default: 250)
|
|
4247
|
-
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4248
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4249
|
-
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4250
|
-
--json Save as JSON array to ./dump/<output>.json
|
|
4251
|
-
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4252
|
-
|
|
4253
|
-
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4254
|
-
|
|
4255
|
-
PnL debug flags (--pnldebug):
|
|
4256
|
-
|
|
4257
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4258
|
-
--priceopen <number> Entry price (required)
|
|
4259
|
-
--direction <string> Position direction: long or short (default: long)
|
|
4260
|
-
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4261
|
-
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4262
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4263
|
-
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4264
|
-
--json Save as JSON array to ./dump/<output>.json
|
|
4265
|
-
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4266
|
-
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4267
|
-
|
|
4268
|
-
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4269
|
-
|
|
4270
|
-
Broker debug flags (--brokerdebug):
|
|
4271
|
-
|
|
4272
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4273
|
-
--exchange <string> Exchange name (default: first registered)
|
|
4274
|
-
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4275
|
-
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4276
|
-
(default: signal-open)
|
|
4277
|
-
|
|
4278
|
-
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4279
|
-
the selected broker commit with synthetic payload values derived from current price.
|
|
4280
|
-
|
|
4281
|
-
Flush flags (--flush):
|
|
4282
|
-
|
|
4283
|
-
One or more positional entry points. For each entry point the following
|
|
4284
|
-
subdirectories are removed from <entry-dir>/dump/:
|
|
4285
|
-
|
|
4286
|
-
report log markdown agent
|
|
4287
|
-
|
|
4288
|
-
Init flags (--init):
|
|
4289
|
-
|
|
4290
|
-
--output <string> Target directory name (default: backtest-kit-project)
|
|
4291
|
-
|
|
4292
|
-
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4293
|
-
|
|
4294
|
-
Docker flags (--docker):
|
|
4295
|
-
|
|
4296
|
-
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4297
|
-
|
|
4298
|
-
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4299
|
-
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4300
|
-
docker compose up to start the container.
|
|
4301
|
-
|
|
4302
|
-
Module hooks (loaded automatically by each mode):
|
|
4303
|
-
|
|
4304
|
-
modules/backtest.module --backtest Broker adapter for backtest
|
|
4305
|
-
modules/walker.module --walker Broker adapter for walker comparison
|
|
4306
|
-
modules/paper.module --paper Broker adapter for paper trading
|
|
4307
|
-
modules/live.module --live Broker adapter for live trading
|
|
4308
|
-
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4309
|
-
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4310
|
-
modules/dump.module --dump Exchange schema for candle dumps
|
|
4311
|
-
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4312
|
-
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4313
|
-
|
|
4314
|
-
--flush has no associated module. It only removes dump subdirectories.
|
|
4315
|
-
|
|
4316
|
-
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4317
|
-
|
|
4318
|
-
Environment variables:
|
|
4319
|
-
|
|
4320
|
-
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4321
|
-
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4322
|
-
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4323
|
-
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4324
|
-
|
|
4325
|
-
Examples:
|
|
4326
|
-
|
|
4327
|
-
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4328
|
-
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4329
|
-
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4330
|
-
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4331
|
-
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4332
|
-
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4333
|
-
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4334
|
-
node ${ENTRY_PATH} --editor
|
|
4335
|
-
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4336
|
-
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4337
|
-
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4338
|
-
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4339
|
-
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4340
|
-
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4341
|
-
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4342
|
-
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4343
|
-
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4315
|
+
const HELP_TEXT = `
|
|
4316
|
+
Usage:
|
|
4317
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
4318
|
+
|
|
4319
|
+
Modes:
|
|
4320
|
+
|
|
4321
|
+
--backtest <entry> Run strategy against historical candle data
|
|
4322
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4323
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
4324
|
+
--live <entry> Live trading with real orders
|
|
4325
|
+
--pine <entry> Execute a local .pine indicator file
|
|
4326
|
+
--editor Open the Pine Script visual editor in the browser
|
|
4327
|
+
--dump Fetch and save raw OHLCV candles
|
|
4328
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4329
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4330
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4331
|
+
--init Scaffold a new project in the current directory
|
|
4332
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4333
|
+
--help Print this help message
|
|
4334
|
+
|
|
4335
|
+
Backtest flags:
|
|
4336
|
+
|
|
4337
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4338
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4339
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4340
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4341
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4342
|
+
--noCache Skip candle cache warming before the run
|
|
4343
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4344
|
+
--verbose Log every candle fetch to stdout
|
|
4345
|
+
--ui Start web dashboard at http://localhost:60050
|
|
4346
|
+
--telegram Send trade notifications to Telegram
|
|
4347
|
+
|
|
4348
|
+
Walker flags (--walker):
|
|
4349
|
+
|
|
4350
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4351
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4352
|
+
--noCache Skip candle cache warming before the run
|
|
4353
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4354
|
+
--verbose Log every candle fetch to stdout
|
|
4355
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4356
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
4357
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4358
|
+
|
|
4359
|
+
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
4360
|
+
changing process.cwd() — .env is read from the working directory only.
|
|
4361
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4362
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4363
|
+
|
|
4364
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4365
|
+
|
|
4366
|
+
Paper / Live flags:
|
|
4367
|
+
|
|
4368
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4369
|
+
--strategy <string> Strategy name (default: first registered)
|
|
4370
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4371
|
+
--verbose Log every candle fetch to stdout
|
|
4372
|
+
--ui Start web dashboard
|
|
4373
|
+
--telegram Send Telegram notifications
|
|
4374
|
+
|
|
4375
|
+
PineScript flags (--pine):
|
|
4376
|
+
|
|
4377
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4378
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4379
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
4380
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4381
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4382
|
+
--output <string> Output file base name without extension
|
|
4383
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4384
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4385
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4386
|
+
|
|
4387
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
4388
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4389
|
+
|
|
4390
|
+
Candle dump flags (--dump):
|
|
4391
|
+
|
|
4392
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4393
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4394
|
+
--limit <string> Number of candles (default: 250)
|
|
4395
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4396
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4397
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4398
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4399
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4400
|
+
|
|
4401
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4402
|
+
|
|
4403
|
+
PnL debug flags (--pnldebug):
|
|
4404
|
+
|
|
4405
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4406
|
+
--priceopen <number> Entry price (required)
|
|
4407
|
+
--direction <string> Position direction: long or short (default: long)
|
|
4408
|
+
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4409
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4410
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4411
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4412
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4413
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4414
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4415
|
+
|
|
4416
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4417
|
+
|
|
4418
|
+
Broker debug flags (--brokerdebug):
|
|
4419
|
+
|
|
4420
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4421
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4422
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4423
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4424
|
+
(default: signal-open)
|
|
4425
|
+
|
|
4426
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4427
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
4428
|
+
|
|
4429
|
+
Flush flags (--flush):
|
|
4430
|
+
|
|
4431
|
+
One or more positional entry points. For each entry point the following
|
|
4432
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
4433
|
+
|
|
4434
|
+
report log markdown agent
|
|
4435
|
+
|
|
4436
|
+
Init flags (--init):
|
|
4437
|
+
|
|
4438
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
4439
|
+
|
|
4440
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4441
|
+
|
|
4442
|
+
Docker flags (--docker):
|
|
4443
|
+
|
|
4444
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4445
|
+
|
|
4446
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4447
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4448
|
+
docker compose up to start the container.
|
|
4449
|
+
|
|
4450
|
+
Module hooks (loaded automatically by each mode):
|
|
4451
|
+
|
|
4452
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
4453
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
4454
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
4455
|
+
modules/live.module --live Broker adapter for live trading
|
|
4456
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4457
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4458
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
4459
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4460
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4461
|
+
|
|
4462
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
4463
|
+
|
|
4464
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4465
|
+
|
|
4466
|
+
Environment variables:
|
|
4467
|
+
|
|
4468
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4469
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4470
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4471
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4472
|
+
|
|
4473
|
+
Examples:
|
|
4474
|
+
|
|
4475
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4476
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4477
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4478
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4479
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4480
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4481
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4482
|
+
node ${ENTRY_PATH} --editor
|
|
4483
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4484
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4485
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4486
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4487
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4488
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4489
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4490
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4491
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4344
4492
|
`.trimStart();
|
|
4345
4493
|
const main$1 = async () => {
|
|
4346
4494
|
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)))) {
|
|
@@ -4350,7 +4498,7 @@ const main$1 = async () => {
|
|
|
4350
4498
|
if (!values.help) {
|
|
4351
4499
|
return;
|
|
4352
4500
|
}
|
|
4353
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4501
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n\n`);
|
|
4354
4502
|
process.stdout.write(HELP_TEXT);
|
|
4355
4503
|
process.exit(0);
|
|
4356
4504
|
};
|
|
@@ -4364,7 +4512,7 @@ const main = async () => {
|
|
|
4364
4512
|
if (!values.version) {
|
|
4365
4513
|
return;
|
|
4366
4514
|
}
|
|
4367
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4515
|
+
process.stdout.write(`@backtest-kit/cli ${"10.1.0"}\n`);
|
|
4368
4516
|
process.exit(0);
|
|
4369
4517
|
};
|
|
4370
4518
|
main();
|