@backtest-kit/cli 6.1.5 → 6.2.1
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 +60 -1
- package/build/index.cjs +440 -149
- package/build/index.mjs +441 -150
- package/package.json +13 -13
- package/template/project/README.md +29 -0
- package/template/project/package.mustache +5 -5
- package/types.d.ts +22 -0
package/build/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as BacktestKit from 'backtest-kit';
|
|
3
|
-
import { Storage, Notification, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync,
|
|
4
|
-
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, execpool, queued, sleep, randomString,
|
|
3
|
+
import { Storage, Notification, Markdown, Report, Dump, Memory, StorageLive, StorageBacktest, NotificationLive, NotificationBacktest, setConfig, Log, listExchangeSchema, addExchangeSchema, roundTicks, listFrameSchema, addFrameSchema, listenDoneLive, listenDoneBacktest, shutdown, listenSignal, listStrategySchema, overrideExchangeSchema, Backtest, alignToInterval, addWalkerSchema, overrideWalkerSchema, Walker, listenDoneWalker, Live, getCandles, checkCandles, warmCandles, listenRisk, listenStrategyCommit, listenSync, Exchange } from 'backtest-kit';
|
|
4
|
+
import { getErrorMessage, errorData, singleshot, str, BehaviorSubject, compose, createAwaiter, execpool, queued, sleep, randomString, TIMEOUT_SYMBOL, typo, retry, trycatch, memoize } from 'functools-kit';
|
|
5
5
|
import fs, { constants } from 'fs';
|
|
6
6
|
import * as stackTrace from 'stack-trace';
|
|
7
|
-
import path, {
|
|
7
|
+
import path, { join, resolve, basename, extname, dirname } from 'path';
|
|
8
8
|
import fs$1, { access, readFile, mkdir, writeFile, readdir, copyFile } from 'fs/promises';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
10
|
import { createActivator } from 'di-kit';
|
|
@@ -224,6 +224,7 @@ const connectionServices$1 = {
|
|
|
224
224
|
};
|
|
225
225
|
const mainServices$1 = {
|
|
226
226
|
backtestMainService: Symbol('backtestMainService'),
|
|
227
|
+
walkerMainService: Symbol('walkerMainService'),
|
|
227
228
|
paperMainService: Symbol('paperMainService'),
|
|
228
229
|
liveMainService: Symbol('liveMainService'),
|
|
229
230
|
};
|
|
@@ -292,6 +293,19 @@ class ResolveService {
|
|
|
292
293
|
_is_launched = true;
|
|
293
294
|
return await readFile(absolutePath, "utf-8");
|
|
294
295
|
};
|
|
296
|
+
this.attachStrategy = async (jsPath) => {
|
|
297
|
+
this.loggerService.log("resolveService attachStrategy", {
|
|
298
|
+
jsPath
|
|
299
|
+
});
|
|
300
|
+
const absolutePath = path.resolve(jsPath);
|
|
301
|
+
await access(absolutePath, constants.F_OK | constants.R_OK);
|
|
302
|
+
const moduleRoot = path.dirname(absolutePath);
|
|
303
|
+
{
|
|
304
|
+
const cwd = process.cwd();
|
|
305
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
306
|
+
}
|
|
307
|
+
this.loaderService.import(absolutePath, moduleRoot);
|
|
308
|
+
};
|
|
295
309
|
this.attachJavascript = async (jsPath) => {
|
|
296
310
|
this.loggerService.log("resolveService attachJavascript", {
|
|
297
311
|
jsPath
|
|
@@ -459,6 +473,10 @@ const getArgs = singleshot(() => {
|
|
|
459
473
|
type: "boolean",
|
|
460
474
|
default: false,
|
|
461
475
|
},
|
|
476
|
+
walker: {
|
|
477
|
+
type: "boolean",
|
|
478
|
+
default: false,
|
|
479
|
+
},
|
|
462
480
|
live: {
|
|
463
481
|
type: "boolean",
|
|
464
482
|
default: false,
|
|
@@ -549,12 +567,11 @@ const getArgs = singleshot(() => {
|
|
|
549
567
|
positionals,
|
|
550
568
|
};
|
|
551
569
|
});
|
|
552
|
-
const
|
|
570
|
+
const getPositionals = singleshot(() => {
|
|
553
571
|
const { positionals = [] } = getArgs();
|
|
554
|
-
|
|
572
|
+
return positionals
|
|
555
573
|
.filter((value) => !DISALLOWED_PATHS.some((path) => value.includes(path)))
|
|
556
|
-
.
|
|
557
|
-
return result || null;
|
|
574
|
+
.filter((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
|
|
558
575
|
});
|
|
559
576
|
|
|
560
577
|
const ADD_FRAME_FN = (self) => {
|
|
@@ -659,11 +676,11 @@ const notifyVerbose = singleshot(() => {
|
|
|
659
676
|
});
|
|
660
677
|
});
|
|
661
678
|
|
|
662
|
-
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
663
|
-
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
679
|
+
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
680
|
+
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
664
681
|
const { values } = getArgs();
|
|
665
682
|
if (!values.cacheInterval) {
|
|
666
|
-
return DEFAULT_CACHE_LIST;
|
|
683
|
+
return DEFAULT_CACHE_LIST$1;
|
|
667
684
|
}
|
|
668
685
|
return String(values.cacheInterval)
|
|
669
686
|
.split(",")
|
|
@@ -688,7 +705,10 @@ class BacktestMainService {
|
|
|
688
705
|
this.frontendProviderService.connect();
|
|
689
706
|
this.telegramProviderService.connect();
|
|
690
707
|
}
|
|
691
|
-
|
|
708
|
+
{
|
|
709
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
710
|
+
await this.moduleConnectionService.loadModule("./backtest.module");
|
|
711
|
+
}
|
|
692
712
|
{
|
|
693
713
|
this.exchangeSchemaService.addSchema();
|
|
694
714
|
this.symbolSchemaService.addSchema();
|
|
@@ -728,7 +748,6 @@ class BacktestMainService {
|
|
|
728
748
|
});
|
|
729
749
|
notifyVerbose();
|
|
730
750
|
}
|
|
731
|
-
await this.moduleConnectionService.loadModule("./backtest.module");
|
|
732
751
|
Backtest.background(symbol, {
|
|
733
752
|
strategyName,
|
|
734
753
|
frameName,
|
|
@@ -745,11 +764,11 @@ class BacktestMainService {
|
|
|
745
764
|
if (!values.backtest) {
|
|
746
765
|
return;
|
|
747
766
|
}
|
|
748
|
-
const entryPoint =
|
|
767
|
+
const [entryPoint = null] = getPositionals();
|
|
749
768
|
if (!entryPoint) {
|
|
750
769
|
throw new Error("Entry point is required");
|
|
751
770
|
}
|
|
752
|
-
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
|
|
771
|
+
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN$1();
|
|
753
772
|
return await this.run({
|
|
754
773
|
symbol: values.symbol,
|
|
755
774
|
entryPoint,
|
|
@@ -764,6 +783,200 @@ class BacktestMainService {
|
|
|
764
783
|
}
|
|
765
784
|
}
|
|
766
785
|
|
|
786
|
+
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
787
|
+
const WALKER_NAME = "cli-walker";
|
|
788
|
+
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
789
|
+
const { values } = getArgs();
|
|
790
|
+
if (!values.cacheInterval) {
|
|
791
|
+
return DEFAULT_CACHE_LIST;
|
|
792
|
+
}
|
|
793
|
+
return String(values.cacheInterval)
|
|
794
|
+
.split(",")
|
|
795
|
+
.map((timeframe) => timeframe.trim());
|
|
796
|
+
};
|
|
797
|
+
class WalkerMainService {
|
|
798
|
+
constructor() {
|
|
799
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
800
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
801
|
+
this.exchangeSchemaService = inject(TYPES.exchangeSchemaService);
|
|
802
|
+
this.symbolSchemaService = inject(TYPES.symbolSchemaService);
|
|
803
|
+
this.cacheLogicService = inject(TYPES.cacheLogicService);
|
|
804
|
+
this.moduleConnectionService = inject(TYPES.moduleConnectionService);
|
|
805
|
+
this.run = singleshot(async (payload) => {
|
|
806
|
+
this.loggerService.log("walkerMainService run", { payload });
|
|
807
|
+
const strategyMap = new Map();
|
|
808
|
+
for (const entryPoint of payload.entryPoints) {
|
|
809
|
+
await this.resolveService.attachStrategy(entryPoint);
|
|
810
|
+
for (const { strategyName } of await listStrategySchema()) {
|
|
811
|
+
if (strategyMap.has(strategyName)) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
strategyMap.set(strategyName, entryPoint);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
await this.moduleConnectionService.loadModule("./walker.module");
|
|
818
|
+
{
|
|
819
|
+
this.exchangeSchemaService.addSchema();
|
|
820
|
+
this.symbolSchemaService.addSchema();
|
|
821
|
+
}
|
|
822
|
+
{
|
|
823
|
+
const { length } = await listFrameSchema();
|
|
824
|
+
if (!length) {
|
|
825
|
+
const endDate = alignToInterval(new Date(Date.now() - 1 * 24 * 60 * 60 * 1000), "1m");
|
|
826
|
+
const startDate = alignToInterval(new Date(Date.now() - 31 * 24 * 60 * 60 * 1000), "1m");
|
|
827
|
+
console.warn(`Warning: The default frame schema is set to the interval ${startDate.toISOString()} — ${endDate.toISOString()}. Please make sure to update it according to your needs using addFrameSchema in your strategy files.`);
|
|
828
|
+
addFrameSchema({
|
|
829
|
+
frameName: FrameName$1.DefaultFrame,
|
|
830
|
+
interval: "1m",
|
|
831
|
+
startDate,
|
|
832
|
+
endDate,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
const symbol = payload.symbol || "BTCUSDT";
|
|
837
|
+
const strategyList = await listStrategySchema();
|
|
838
|
+
const strategyNames = strategyList.map((s) => s.strategyName);
|
|
839
|
+
if (!strategyNames.length) {
|
|
840
|
+
throw new Error("No strategies found in provided entry points");
|
|
841
|
+
}
|
|
842
|
+
const [defaultExchangeName = null] = await listExchangeSchema();
|
|
843
|
+
const [defaultFrameName = null] = await listFrameSchema();
|
|
844
|
+
const exchangeName = defaultExchangeName?.exchangeName;
|
|
845
|
+
const frameName = defaultFrameName?.frameName;
|
|
846
|
+
if (!exchangeName) {
|
|
847
|
+
throw new Error("Exchange name is required");
|
|
848
|
+
}
|
|
849
|
+
if (!frameName) {
|
|
850
|
+
throw new Error("Frame name is required");
|
|
851
|
+
}
|
|
852
|
+
const cwd = process.cwd();
|
|
853
|
+
const self = this;
|
|
854
|
+
const callbacks = {
|
|
855
|
+
async onStrategyStart(strategyName) {
|
|
856
|
+
const entryPoint = strategyMap.get(strategyName);
|
|
857
|
+
if (!entryPoint) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const absolutePath = path.resolve(entryPoint);
|
|
861
|
+
const moduleRoot = path.dirname(absolutePath);
|
|
862
|
+
{
|
|
863
|
+
process.chdir(moduleRoot);
|
|
864
|
+
cwd !== moduleRoot && Log.useJsonl();
|
|
865
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
866
|
+
dotenv.config({ path: path.join(moduleRoot, '.env'), override: true, quiet: true });
|
|
867
|
+
}
|
|
868
|
+
if (!payload.noCache) {
|
|
869
|
+
await self.cacheLogicService.execute(payload.cacheInterval, {
|
|
870
|
+
exchangeName,
|
|
871
|
+
frameName,
|
|
872
|
+
symbol,
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
},
|
|
876
|
+
};
|
|
877
|
+
addWalkerSchema({
|
|
878
|
+
walkerName: WALKER_NAME,
|
|
879
|
+
exchangeName,
|
|
880
|
+
frameName,
|
|
881
|
+
strategies: strategyNames,
|
|
882
|
+
callbacks,
|
|
883
|
+
});
|
|
884
|
+
if (payload.verbose) {
|
|
885
|
+
overrideExchangeSchema({
|
|
886
|
+
exchangeName,
|
|
887
|
+
callbacks: {
|
|
888
|
+
onCandleData(symbol, interval, since) {
|
|
889
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
890
|
+
},
|
|
891
|
+
},
|
|
892
|
+
});
|
|
893
|
+
notifyVerbose();
|
|
894
|
+
}
|
|
895
|
+
if (payload.verbose) {
|
|
896
|
+
overrideWalkerSchema({
|
|
897
|
+
walkerName: WALKER_NAME,
|
|
898
|
+
callbacks: {
|
|
899
|
+
async onStrategyStart(strategyName, symbol) {
|
|
900
|
+
console.log(`Strategy started: ${strategyName} for symbol: ${symbol}`);
|
|
901
|
+
await callbacks.onStrategyStart(strategyName);
|
|
902
|
+
},
|
|
903
|
+
onStrategyError(strategyName, symbol, error) {
|
|
904
|
+
console.error(`Strategy error: ${strategyName} for symbol: ${symbol}`, error);
|
|
905
|
+
},
|
|
906
|
+
onStrategyComplete(strategyName, symbol) {
|
|
907
|
+
console.log(`Strategy completed: ${strategyName} for symbol: ${symbol}`);
|
|
908
|
+
},
|
|
909
|
+
onComplete(results) {
|
|
910
|
+
console.log(`Walker completed for symbol: ${results.symbol}`, results);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
Walker.background(symbol, { walkerName: WALKER_NAME });
|
|
916
|
+
const [awaiter, { resolve: res }] = createAwaiter();
|
|
917
|
+
const unWalker = listenDoneWalker(() => {
|
|
918
|
+
console.log("Walker comparison finished");
|
|
919
|
+
unWalker();
|
|
920
|
+
res();
|
|
921
|
+
});
|
|
922
|
+
{
|
|
923
|
+
payload.verbose && console.time("Walker");
|
|
924
|
+
await awaiter;
|
|
925
|
+
payload.verbose && console.timeEnd("Walker");
|
|
926
|
+
}
|
|
927
|
+
process.chdir(cwd);
|
|
928
|
+
const dumpName = payload.output || `walker_${symbol}_${Date.now()}`;
|
|
929
|
+
const dumpDir = join(process.cwd(), "dump");
|
|
930
|
+
if (payload.json) {
|
|
931
|
+
const filePath = resolve(dumpDir, `${dumpName}.json`);
|
|
932
|
+
const data = await Walker.getData(symbol, { walkerName: WALKER_NAME });
|
|
933
|
+
await mkdir(dumpDir, { recursive: true });
|
|
934
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
935
|
+
console.log(`Saved: ${filePath}`);
|
|
936
|
+
process.exit(0);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (payload.markdown) {
|
|
940
|
+
const filePath = resolve(dumpDir, `${dumpName}.md`);
|
|
941
|
+
const report = await Walker.getReport(symbol, { walkerName: WALKER_NAME });
|
|
942
|
+
await mkdir(dumpDir, { recursive: true });
|
|
943
|
+
await writeFile(filePath, report, "utf-8");
|
|
944
|
+
console.log(`Saved: ${filePath}`);
|
|
945
|
+
process.exit(0);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const report = await Walker.getReport(symbol, { walkerName: WALKER_NAME });
|
|
949
|
+
console.log(report);
|
|
950
|
+
process.exit(0);
|
|
951
|
+
});
|
|
952
|
+
this.connect = singleshot(async () => {
|
|
953
|
+
this.loggerService.log("walkerMainService connect");
|
|
954
|
+
if (!getEntry(import.meta.url)) {
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
const { values } = getArgs();
|
|
958
|
+
if (!values.walker) {
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const entryPoints = getPositionals();
|
|
962
|
+
if (!entryPoints.length) {
|
|
963
|
+
throw new Error("At least one entry point is required");
|
|
964
|
+
}
|
|
965
|
+
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
|
|
966
|
+
return await this.run({
|
|
967
|
+
entryPoints,
|
|
968
|
+
json: values.json,
|
|
969
|
+
markdown: values.markdown,
|
|
970
|
+
symbol: values.symbol,
|
|
971
|
+
output: values.output,
|
|
972
|
+
cacheInterval,
|
|
973
|
+
verbose: values.verbose,
|
|
974
|
+
noCache: values.noCache,
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
|
|
767
980
|
class LiveMainService {
|
|
768
981
|
constructor() {
|
|
769
982
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -781,7 +994,10 @@ class LiveMainService {
|
|
|
781
994
|
this.frontendProviderService.connect();
|
|
782
995
|
this.telegramProviderService.connect();
|
|
783
996
|
}
|
|
784
|
-
|
|
997
|
+
{
|
|
998
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
999
|
+
await this.moduleConnectionService.loadModule("./live.module");
|
|
1000
|
+
}
|
|
785
1001
|
{
|
|
786
1002
|
this.exchangeSchemaService.addSchema();
|
|
787
1003
|
this.symbolSchemaService.addSchema();
|
|
@@ -808,7 +1024,6 @@ class LiveMainService {
|
|
|
808
1024
|
});
|
|
809
1025
|
notifyVerbose();
|
|
810
1026
|
}
|
|
811
|
-
await this.moduleConnectionService.loadModule("./live.module");
|
|
812
1027
|
Live.background(symbol, {
|
|
813
1028
|
strategyName,
|
|
814
1029
|
exchangeName,
|
|
@@ -824,7 +1039,7 @@ class LiveMainService {
|
|
|
824
1039
|
if (!values.live) {
|
|
825
1040
|
return;
|
|
826
1041
|
}
|
|
827
|
-
const entryPoint =
|
|
1042
|
+
const [entryPoint = null] = getPositionals();
|
|
828
1043
|
if (!entryPoint) {
|
|
829
1044
|
throw new Error("Entry point is required");
|
|
830
1045
|
}
|
|
@@ -854,7 +1069,10 @@ class PaperMainService {
|
|
|
854
1069
|
this.frontendProviderService.connect();
|
|
855
1070
|
this.telegramProviderService.connect();
|
|
856
1071
|
}
|
|
857
|
-
|
|
1072
|
+
{
|
|
1073
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
1074
|
+
await this.moduleConnectionService.loadModule("./paper.module");
|
|
1075
|
+
}
|
|
858
1076
|
{
|
|
859
1077
|
this.exchangeSchemaService.addSchema();
|
|
860
1078
|
this.symbolSchemaService.addSchema();
|
|
@@ -881,7 +1099,6 @@ class PaperMainService {
|
|
|
881
1099
|
});
|
|
882
1100
|
notifyVerbose();
|
|
883
1101
|
}
|
|
884
|
-
await this.moduleConnectionService.loadModule("./paper.module");
|
|
885
1102
|
Live.background(symbol, {
|
|
886
1103
|
strategyName,
|
|
887
1104
|
exchangeName,
|
|
@@ -897,7 +1114,7 @@ class PaperMainService {
|
|
|
897
1114
|
if (!values.paper) {
|
|
898
1115
|
return;
|
|
899
1116
|
}
|
|
900
|
-
const entryPoint =
|
|
1117
|
+
const [entryPoint = null] = getPositionals();
|
|
901
1118
|
if (!entryPoint) {
|
|
902
1119
|
throw new Error("Entry point is required");
|
|
903
1120
|
}
|
|
@@ -1898,8 +2115,7 @@ class BabelService {
|
|
|
1898
2115
|
}
|
|
1899
2116
|
}
|
|
1900
2117
|
|
|
1901
|
-
const TRANSPILE_FN = (code, self) => {
|
|
1902
|
-
const require = self.getBaseRequire();
|
|
2118
|
+
const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
1903
2119
|
const __filename = self.__filename;
|
|
1904
2120
|
const __dirname = self.__dirname;
|
|
1905
2121
|
const module = { exports: {} };
|
|
@@ -1908,7 +2124,7 @@ const TRANSPILE_FN = (code, self) => {
|
|
|
1908
2124
|
eval(self.params.babel.transpile(code));
|
|
1909
2125
|
}
|
|
1910
2126
|
catch (error) {
|
|
1911
|
-
console.log(`Error during transpilation error=\`${getErrorMessage(error)}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
2127
|
+
console.log(`Error during transpilation error=\`${getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
1912
2128
|
process.exit(-1);
|
|
1913
2129
|
}
|
|
1914
2130
|
return {
|
|
@@ -1918,18 +2134,18 @@ const TRANSPILE_FN = (code, self) => {
|
|
|
1918
2134
|
exports,
|
|
1919
2135
|
module,
|
|
1920
2136
|
};
|
|
1921
|
-
};
|
|
1922
|
-
const REQUIRE_ENTRY_FACTORY = (filePath, self) => {
|
|
2137
|
+
});
|
|
2138
|
+
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
1923
2139
|
{
|
|
1924
2140
|
return null;
|
|
1925
2141
|
}
|
|
1926
2142
|
};
|
|
1927
|
-
const BABEL_ENTRY_FACTORY = (filePath, self) => {
|
|
2143
|
+
const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
1928
2144
|
try {
|
|
1929
2145
|
const resolvedPath = path.resolve(self.__dirname, filePath);
|
|
1930
2146
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
1931
2147
|
const child = self.fork(path.dirname(resolvedPath));
|
|
1932
|
-
const { module } = TRANSPILE_FN(code, child);
|
|
2148
|
+
const { module } = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
1933
2149
|
return "default" in module.exports
|
|
1934
2150
|
? module.exports.default
|
|
1935
2151
|
: module.exports;
|
|
@@ -1970,21 +2186,20 @@ const GET_RESOLVED_EXT_FN = (filePath) => {
|
|
|
1970
2186
|
}
|
|
1971
2187
|
return filePath;
|
|
1972
2188
|
};
|
|
1973
|
-
const ENTRY_FACTORY = (filePath, self) => {
|
|
1974
|
-
filePath = GET_RESOLVED_EXT_FN(filePath);
|
|
2189
|
+
const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
1975
2190
|
{
|
|
1976
2191
|
let result = null;
|
|
1977
2192
|
if ((result = REQUIRE_ENTRY_FACTORY())) {
|
|
1978
2193
|
return result;
|
|
1979
2194
|
}
|
|
1980
|
-
if ((result = BABEL_ENTRY_FACTORY(filePath, self))) {
|
|
2195
|
+
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen))) {
|
|
1981
2196
|
return result;
|
|
1982
2197
|
}
|
|
1983
2198
|
}
|
|
1984
2199
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
1985
2200
|
};
|
|
1986
|
-
const CREATE_BASE_REQUIRE_FN = (self) => {
|
|
1987
|
-
const baseRequire =
|
|
2201
|
+
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2202
|
+
const baseRequire = self.baseRequire();
|
|
1988
2203
|
return new Proxy(baseRequire, {
|
|
1989
2204
|
apply(_target, _this, args) {
|
|
1990
2205
|
const id = args[0];
|
|
@@ -2005,7 +2220,7 @@ const CREATE_BASE_REQUIRE_FN = (self) => {
|
|
|
2005
2220
|
if (id.startsWith("./") || id.startsWith("../")) {
|
|
2006
2221
|
const resolved = path.resolve(self.__dirname, id);
|
|
2007
2222
|
const child = self.fork(path.dirname(resolved));
|
|
2008
|
-
return child.import(resolved);
|
|
2223
|
+
return child.import(resolved, seen);
|
|
2009
2224
|
}
|
|
2010
2225
|
return baseRequire(id);
|
|
2011
2226
|
},
|
|
@@ -2019,11 +2234,11 @@ const BacktestKitCli = new Proxy({}, {
|
|
|
2019
2234
|
class ClientLoader {
|
|
2020
2235
|
constructor(params) {
|
|
2021
2236
|
this.params = params;
|
|
2022
|
-
this.
|
|
2023
|
-
this.params.logger.log("ClientLoader
|
|
2237
|
+
this.baseRequire = singleshot(() => {
|
|
2238
|
+
this.params.logger.log("ClientLoader baseRequire", {
|
|
2024
2239
|
basePath: this.params.path,
|
|
2025
2240
|
});
|
|
2026
|
-
return
|
|
2241
|
+
return createRequire(this.__filename);
|
|
2027
2242
|
});
|
|
2028
2243
|
this.__filename = path.join(params.path, "index.cjs");
|
|
2029
2244
|
this.__dirname = path.dirname(this.__filename);
|
|
@@ -2039,12 +2254,21 @@ class ClientLoader {
|
|
|
2039
2254
|
logger: this.params.logger,
|
|
2040
2255
|
});
|
|
2041
2256
|
}
|
|
2042
|
-
import(filePath) {
|
|
2257
|
+
import(filePath, seen = new Set()) {
|
|
2043
2258
|
this.params.logger.log("ClientLoader import", {
|
|
2044
2259
|
filePath,
|
|
2045
2260
|
basePath: this.params.path,
|
|
2046
2261
|
});
|
|
2047
|
-
|
|
2262
|
+
const resolved = GET_RESOLVED_EXT_FN(filePath);
|
|
2263
|
+
if (seen.has(resolved)) {
|
|
2264
|
+
throw new Error(`Circular dependency detected: ${resolved} (seen: ${[...seen].join("->")}->${resolved})`);
|
|
2265
|
+
}
|
|
2266
|
+
const currentSeen = new Set(seen);
|
|
2267
|
+
if (!seen.size) {
|
|
2268
|
+
currentSeen.add(path.resolve(this.__dirname, filePath));
|
|
2269
|
+
}
|
|
2270
|
+
currentSeen.add(resolved);
|
|
2271
|
+
return ENTRY_FACTORY(resolved, this, currentSeen);
|
|
2048
2272
|
}
|
|
2049
2273
|
check(filePath) {
|
|
2050
2274
|
this.params.logger.log("ClientLoader check", {
|
|
@@ -2112,6 +2336,7 @@ class LoaderService {
|
|
|
2112
2336
|
}
|
|
2113
2337
|
{
|
|
2114
2338
|
provide(TYPES.backtestMainService, () => new BacktestMainService());
|
|
2339
|
+
provide(TYPES.walkerMainService, () => new WalkerMainService());
|
|
2115
2340
|
provide(TYPES.paperMainService, () => new PaperMainService());
|
|
2116
2341
|
provide(TYPES.liveMainService, () => new LiveMainService());
|
|
2117
2342
|
}
|
|
@@ -2151,6 +2376,7 @@ const connectionServices = {
|
|
|
2151
2376
|
};
|
|
2152
2377
|
const mainServices = {
|
|
2153
2378
|
backtestMainService: inject(TYPES.backtestMainService),
|
|
2379
|
+
walkerMainService: inject(TYPES.walkerMainService),
|
|
2154
2380
|
paperMainService: inject(TYPES.paperMainService),
|
|
2155
2381
|
liveMainService: inject(TYPES.liveMainService),
|
|
2156
2382
|
};
|
|
@@ -2186,14 +2412,14 @@ const cli = {
|
|
|
2186
2412
|
};
|
|
2187
2413
|
init();
|
|
2188
2414
|
|
|
2189
|
-
const MODES = ["backtest", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2415
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2190
2416
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2191
|
-
const HELP_TEXT$1 = `
|
|
2192
|
-
Example:
|
|
2193
|
-
|
|
2194
|
-
node ${ENTRY_PATH$1} --help
|
|
2417
|
+
const HELP_TEXT$1 = `
|
|
2418
|
+
Example:
|
|
2419
|
+
|
|
2420
|
+
node ${ENTRY_PATH$1} --help
|
|
2195
2421
|
`.trimStart();
|
|
2196
|
-
const main$
|
|
2422
|
+
const main$b = async () => {
|
|
2197
2423
|
if (!getEntry(import.meta.url)) {
|
|
2198
2424
|
return;
|
|
2199
2425
|
}
|
|
@@ -2201,21 +2427,21 @@ const main$a = async () => {
|
|
|
2201
2427
|
if (MODES.some((mode) => values[mode])) {
|
|
2202
2428
|
return;
|
|
2203
2429
|
}
|
|
2204
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
2430
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.1"}\n`);
|
|
2205
2431
|
process.stdout.write("\n");
|
|
2206
2432
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2207
2433
|
process.stdout.write("\n");
|
|
2208
2434
|
process.stdout.write(HELP_TEXT$1);
|
|
2209
2435
|
process.exit(0);
|
|
2210
2436
|
};
|
|
2211
|
-
main$
|
|
2437
|
+
main$b();
|
|
2212
2438
|
|
|
2213
2439
|
const notifyShutdown = singleshot(async () => {
|
|
2214
2440
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
2215
2441
|
});
|
|
2216
2442
|
|
|
2217
|
-
const BEFORE_EXIT_FN$
|
|
2218
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
2443
|
+
const BEFORE_EXIT_FN$5 = singleshot(async () => {
|
|
2444
|
+
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
2219
2445
|
const [running = null] = await Backtest.list();
|
|
2220
2446
|
if (!running) {
|
|
2221
2447
|
return;
|
|
@@ -2231,6 +2457,35 @@ const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
|
2231
2457
|
frameName,
|
|
2232
2458
|
});
|
|
2233
2459
|
});
|
|
2460
|
+
const listenGracefulShutdown$5 = singleshot(() => {
|
|
2461
|
+
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
2462
|
+
});
|
|
2463
|
+
const main$a = async () => {
|
|
2464
|
+
if (!getEntry(import.meta.url)) {
|
|
2465
|
+
return;
|
|
2466
|
+
}
|
|
2467
|
+
const { values } = getArgs();
|
|
2468
|
+
if (!values.backtest) {
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
await cli.backtestMainService.connect();
|
|
2472
|
+
listenGracefulShutdown$5();
|
|
2473
|
+
};
|
|
2474
|
+
main$a();
|
|
2475
|
+
|
|
2476
|
+
const BEFORE_EXIT_FN$4 = singleshot(async () => {
|
|
2477
|
+
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
2478
|
+
const [running = null] = await Walker.list();
|
|
2479
|
+
if (!running) {
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
notifyShutdown();
|
|
2483
|
+
const { walkerName, symbol, status } = running;
|
|
2484
|
+
if (status === "fulfilled") {
|
|
2485
|
+
return;
|
|
2486
|
+
}
|
|
2487
|
+
Walker.stop(symbol, { walkerName });
|
|
2488
|
+
});
|
|
2234
2489
|
const listenGracefulShutdown$4 = singleshot(() => {
|
|
2235
2490
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2236
2491
|
});
|
|
@@ -2239,11 +2494,11 @@ const main$9 = async () => {
|
|
|
2239
2494
|
return;
|
|
2240
2495
|
}
|
|
2241
2496
|
const { values } = getArgs();
|
|
2242
|
-
if (!values.
|
|
2497
|
+
if (!values.walker) {
|
|
2243
2498
|
return;
|
|
2244
2499
|
}
|
|
2245
|
-
await cli.backtestMainService.connect();
|
|
2246
2500
|
listenGracefulShutdown$4();
|
|
2501
|
+
await cli.walkerMainService.connect();
|
|
2247
2502
|
};
|
|
2248
2503
|
main$9();
|
|
2249
2504
|
|
|
@@ -2379,7 +2634,7 @@ const main$4 = async () => {
|
|
|
2379
2634
|
if (!values.pine) {
|
|
2380
2635
|
return;
|
|
2381
2636
|
}
|
|
2382
|
-
const entryPoint =
|
|
2637
|
+
const [entryPoint = null] = getPositionals();
|
|
2383
2638
|
if (!entryPoint) {
|
|
2384
2639
|
return;
|
|
2385
2640
|
}
|
|
@@ -2573,6 +2828,20 @@ function runScript(scriptPath, cwd) {
|
|
|
2573
2828
|
child.on("error", reject);
|
|
2574
2829
|
});
|
|
2575
2830
|
}
|
|
2831
|
+
function runNpmInstall(cwd) {
|
|
2832
|
+
return new Promise((resolve, reject) => {
|
|
2833
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2834
|
+
const child = spawn(npm, ["install"], { cwd, stdio: "inherit", shell: true });
|
|
2835
|
+
child.on("close", (code) => {
|
|
2836
|
+
if (code !== 0) {
|
|
2837
|
+
reject(new Error(`npm install exited with code ${code}`));
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
resolve();
|
|
2841
|
+
});
|
|
2842
|
+
child.on("error", reject);
|
|
2843
|
+
});
|
|
2844
|
+
}
|
|
2576
2845
|
const main$2 = async () => {
|
|
2577
2846
|
if (!getEntry(import.meta.url)) {
|
|
2578
2847
|
return;
|
|
@@ -2593,109 +2862,130 @@ const main$2 = async () => {
|
|
|
2593
2862
|
await copyDir(templatePath, projectPath, { PROJECT_NAME: projectName });
|
|
2594
2863
|
console.log(`Fetching docs...`);
|
|
2595
2864
|
await runScript(join(projectPath, "scripts/fetch_docs.mjs"), projectPath);
|
|
2865
|
+
console.log(`Installing dependencies...`);
|
|
2866
|
+
await runNpmInstall(projectPath);
|
|
2596
2867
|
console.log(`Done! Project created at ${projectPath}`);
|
|
2597
2868
|
process.exit(0);
|
|
2598
2869
|
};
|
|
2599
2870
|
main$2();
|
|
2600
2871
|
|
|
2601
2872
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2602
|
-
const HELP_TEXT = `
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
--
|
|
2611
|
-
--
|
|
2612
|
-
--
|
|
2613
|
-
--
|
|
2614
|
-
--
|
|
2615
|
-
--
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
--
|
|
2621
|
-
--
|
|
2622
|
-
--
|
|
2623
|
-
--
|
|
2624
|
-
--
|
|
2625
|
-
--
|
|
2626
|
-
--
|
|
2627
|
-
--
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
--
|
|
2633
|
-
--
|
|
2634
|
-
--
|
|
2635
|
-
--
|
|
2636
|
-
--
|
|
2637
|
-
--
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
--
|
|
2649
|
-
--
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
--
|
|
2658
|
-
--
|
|
2659
|
-
--
|
|
2660
|
-
--
|
|
2661
|
-
--
|
|
2662
|
-
--
|
|
2663
|
-
--
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2873
|
+
const HELP_TEXT = `
|
|
2874
|
+
Usage:
|
|
2875
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
2876
|
+
|
|
2877
|
+
Modes:
|
|
2878
|
+
|
|
2879
|
+
--backtest <entry> Run strategy against historical candle data
|
|
2880
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
2881
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
2882
|
+
--live <entry> Live trading with real orders
|
|
2883
|
+
--pine <entry> Execute a local .pine indicator file
|
|
2884
|
+
--dump Fetch and save raw OHLCV candles
|
|
2885
|
+
--init Scaffold a new project in the current directory
|
|
2886
|
+
--help Print this help message
|
|
2887
|
+
|
|
2888
|
+
Backtest flags:
|
|
2889
|
+
|
|
2890
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2891
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
2892
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
2893
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
2894
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
2895
|
+
--noCache Skip candle cache warming before the run
|
|
2896
|
+
--verbose Log every candle fetch to stdout
|
|
2897
|
+
--ui Start web dashboard at http://localhost:60050
|
|
2898
|
+
--telegram Send trade notifications to Telegram
|
|
2899
|
+
|
|
2900
|
+
Walker flags (--walker):
|
|
2901
|
+
|
|
2902
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2903
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
2904
|
+
--noCache Skip candle cache warming before the run
|
|
2905
|
+
--verbose Log every candle fetch to stdout
|
|
2906
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
2907
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
2908
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
2909
|
+
|
|
2910
|
+
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
2911
|
+
changing process.cwd() — .env is read from the working directory only.
|
|
2912
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
2913
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
2914
|
+
|
|
2915
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
2916
|
+
|
|
2917
|
+
Paper / Live flags:
|
|
2918
|
+
|
|
2919
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2920
|
+
--strategy <string> Strategy name (default: first registered)
|
|
2921
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2922
|
+
--verbose Log every candle fetch to stdout
|
|
2923
|
+
--ui Start web dashboard
|
|
2924
|
+
--telegram Send Telegram notifications
|
|
2925
|
+
|
|
2926
|
+
PineScript flags (--pine):
|
|
2927
|
+
|
|
2928
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2929
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
2930
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
2931
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
2932
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2933
|
+
--output <string> Output file base name without extension
|
|
2934
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
2935
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
2936
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
2937
|
+
|
|
2938
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
2939
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
2940
|
+
|
|
2941
|
+
Candle dump flags (--dump):
|
|
2942
|
+
|
|
2943
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2944
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
2945
|
+
--limit <string> Number of candles (default: 250)
|
|
2946
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
2947
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2948
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
2949
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
2950
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
2951
|
+
|
|
2952
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
2953
|
+
|
|
2954
|
+
Init flags (--init):
|
|
2955
|
+
|
|
2956
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
2957
|
+
|
|
2958
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
2959
|
+
|
|
2960
|
+
Module hooks (loaded automatically by each mode):
|
|
2961
|
+
|
|
2962
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
2963
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
2964
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
2965
|
+
modules/live.module --live Broker adapter for live trading
|
|
2966
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
2967
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
2968
|
+
|
|
2969
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
2970
|
+
|
|
2971
|
+
Environment variables:
|
|
2972
|
+
|
|
2973
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
2974
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
2975
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
2976
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
2977
|
+
|
|
2978
|
+
Examples:
|
|
2979
|
+
|
|
2980
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
2981
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
|
|
2982
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
2983
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
2984
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
2985
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
2986
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
2987
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
2988
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
2699
2989
|
`.trimStart();
|
|
2700
2990
|
const main$1 = async () => {
|
|
2701
2991
|
if (!getEntry(import.meta.url)) {
|
|
@@ -2705,6 +2995,7 @@ const main$1 = async () => {
|
|
|
2705
2995
|
if (!values.help) {
|
|
2706
2996
|
return;
|
|
2707
2997
|
}
|
|
2998
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.1"}\n\n`);
|
|
2708
2999
|
process.stdout.write(HELP_TEXT);
|
|
2709
3000
|
process.exit(0);
|
|
2710
3001
|
};
|
|
@@ -2718,7 +3009,7 @@ const main = async () => {
|
|
|
2718
3009
|
if (!values.version) {
|
|
2719
3010
|
return;
|
|
2720
3011
|
}
|
|
2721
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
3012
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.1"}\n`);
|
|
2722
3013
|
process.exit(0);
|
|
2723
3014
|
};
|
|
2724
3015
|
main();
|
|
@@ -2740,7 +3031,7 @@ async function run(mode, args) {
|
|
|
2740
3031
|
}
|
|
2741
3032
|
if (mode === "backtest") {
|
|
2742
3033
|
await cli.backtestMainService.run(args);
|
|
2743
|
-
listenGracefulShutdown$
|
|
3034
|
+
listenGracefulShutdown$5();
|
|
2744
3035
|
return;
|
|
2745
3036
|
}
|
|
2746
3037
|
if (mode === "paper") {
|