@backtest-kit/cli 6.1.4 → 6.2.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 +60 -1
- package/build/index.cjs +399 -145
- package/build/index.mjs +400 -146
- 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.cjs
CHANGED
|
@@ -249,6 +249,7 @@ const connectionServices$1 = {
|
|
|
249
249
|
};
|
|
250
250
|
const mainServices$1 = {
|
|
251
251
|
backtestMainService: Symbol('backtestMainService'),
|
|
252
|
+
walkerMainService: Symbol('walkerMainService'),
|
|
252
253
|
paperMainService: Symbol('paperMainService'),
|
|
253
254
|
liveMainService: Symbol('liveMainService'),
|
|
254
255
|
};
|
|
@@ -317,6 +318,19 @@ class ResolveService {
|
|
|
317
318
|
_is_launched = true;
|
|
318
319
|
return await fs$1.readFile(absolutePath, "utf-8");
|
|
319
320
|
};
|
|
321
|
+
this.attachStrategy = async (jsPath) => {
|
|
322
|
+
this.loggerService.log("resolveService attachStrategy", {
|
|
323
|
+
jsPath
|
|
324
|
+
});
|
|
325
|
+
const absolutePath = path.resolve(jsPath);
|
|
326
|
+
await fs$1.access(absolutePath, fs.constants.F_OK | fs.constants.R_OK);
|
|
327
|
+
const moduleRoot = path.dirname(absolutePath);
|
|
328
|
+
{
|
|
329
|
+
const cwd = process.cwd();
|
|
330
|
+
dotenv.config({ path: path.join(cwd, '.env'), override: true, quiet: true });
|
|
331
|
+
}
|
|
332
|
+
this.loaderService.import(absolutePath, moduleRoot);
|
|
333
|
+
};
|
|
320
334
|
this.attachJavascript = async (jsPath) => {
|
|
321
335
|
this.loggerService.log("resolveService attachJavascript", {
|
|
322
336
|
jsPath
|
|
@@ -484,6 +498,10 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
484
498
|
type: "boolean",
|
|
485
499
|
default: false,
|
|
486
500
|
},
|
|
501
|
+
walker: {
|
|
502
|
+
type: "boolean",
|
|
503
|
+
default: false,
|
|
504
|
+
},
|
|
487
505
|
live: {
|
|
488
506
|
type: "boolean",
|
|
489
507
|
default: false,
|
|
@@ -574,12 +592,11 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
574
592
|
positionals,
|
|
575
593
|
};
|
|
576
594
|
});
|
|
577
|
-
const
|
|
595
|
+
const getPositionals = functoolsKit.singleshot(() => {
|
|
578
596
|
const { positionals = [] } = getArgs();
|
|
579
|
-
|
|
597
|
+
return positionals
|
|
580
598
|
.filter((value) => !DISALLOWED_PATHS.some((path) => value.includes(path)))
|
|
581
|
-
.
|
|
582
|
-
return result || null;
|
|
599
|
+
.filter((value) => ALLOWED_EXTENSIONS.some((ext) => value.endsWith(ext)));
|
|
583
600
|
});
|
|
584
601
|
|
|
585
602
|
const ADD_FRAME_FN = (self) => {
|
|
@@ -684,11 +701,11 @@ const notifyVerbose = functoolsKit.singleshot(() => {
|
|
|
684
701
|
});
|
|
685
702
|
});
|
|
686
703
|
|
|
687
|
-
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
688
|
-
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
704
|
+
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
705
|
+
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
689
706
|
const { values } = getArgs();
|
|
690
707
|
if (!values.cacheInterval) {
|
|
691
|
-
return DEFAULT_CACHE_LIST;
|
|
708
|
+
return DEFAULT_CACHE_LIST$1;
|
|
692
709
|
}
|
|
693
710
|
return String(values.cacheInterval)
|
|
694
711
|
.split(",")
|
|
@@ -713,7 +730,10 @@ class BacktestMainService {
|
|
|
713
730
|
this.frontendProviderService.connect();
|
|
714
731
|
this.telegramProviderService.connect();
|
|
715
732
|
}
|
|
716
|
-
|
|
733
|
+
{
|
|
734
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
735
|
+
await this.moduleConnectionService.loadModule("./backtest.module");
|
|
736
|
+
}
|
|
717
737
|
{
|
|
718
738
|
this.exchangeSchemaService.addSchema();
|
|
719
739
|
this.symbolSchemaService.addSchema();
|
|
@@ -753,7 +773,6 @@ class BacktestMainService {
|
|
|
753
773
|
});
|
|
754
774
|
notifyVerbose();
|
|
755
775
|
}
|
|
756
|
-
await this.moduleConnectionService.loadModule("./backtest.module");
|
|
757
776
|
BacktestKit.Backtest.background(symbol, {
|
|
758
777
|
strategyName,
|
|
759
778
|
frameName,
|
|
@@ -770,11 +789,11 @@ class BacktestMainService {
|
|
|
770
789
|
if (!values.backtest) {
|
|
771
790
|
return;
|
|
772
791
|
}
|
|
773
|
-
const entryPoint =
|
|
792
|
+
const [entryPoint = null] = getPositionals();
|
|
774
793
|
if (!entryPoint) {
|
|
775
794
|
throw new Error("Entry point is required");
|
|
776
795
|
}
|
|
777
|
-
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
|
|
796
|
+
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN$1();
|
|
778
797
|
return await this.run({
|
|
779
798
|
symbol: values.symbol,
|
|
780
799
|
entryPoint,
|
|
@@ -789,6 +808,170 @@ class BacktestMainService {
|
|
|
789
808
|
}
|
|
790
809
|
}
|
|
791
810
|
|
|
811
|
+
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
812
|
+
const WALKER_NAME = "cli-walker";
|
|
813
|
+
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
814
|
+
const { values } = getArgs();
|
|
815
|
+
if (!values.cacheInterval) {
|
|
816
|
+
return DEFAULT_CACHE_LIST;
|
|
817
|
+
}
|
|
818
|
+
return String(values.cacheInterval)
|
|
819
|
+
.split(",")
|
|
820
|
+
.map((timeframe) => timeframe.trim());
|
|
821
|
+
};
|
|
822
|
+
class WalkerMainService {
|
|
823
|
+
constructor() {
|
|
824
|
+
this.loggerService = inject(TYPES.loggerService);
|
|
825
|
+
this.resolveService = inject(TYPES.resolveService);
|
|
826
|
+
this.exchangeSchemaService = inject(TYPES.exchangeSchemaService);
|
|
827
|
+
this.symbolSchemaService = inject(TYPES.symbolSchemaService);
|
|
828
|
+
this.cacheLogicService = inject(TYPES.cacheLogicService);
|
|
829
|
+
this.moduleConnectionService = inject(TYPES.moduleConnectionService);
|
|
830
|
+
this.run = functoolsKit.singleshot(async (payload) => {
|
|
831
|
+
this.loggerService.log("walkerMainService run", { payload });
|
|
832
|
+
for (const entryPoint of payload.entryPoints) {
|
|
833
|
+
await this.resolveService.attachStrategy(entryPoint);
|
|
834
|
+
}
|
|
835
|
+
await this.moduleConnectionService.loadModule("./walker.module");
|
|
836
|
+
{
|
|
837
|
+
this.exchangeSchemaService.addSchema();
|
|
838
|
+
this.symbolSchemaService.addSchema();
|
|
839
|
+
}
|
|
840
|
+
{
|
|
841
|
+
const { length } = await BacktestKit.listFrameSchema();
|
|
842
|
+
if (!length) {
|
|
843
|
+
const endDate = BacktestKit.alignToInterval(new Date(Date.now() - 1 * 24 * 60 * 60 * 1000), "1m");
|
|
844
|
+
const startDate = BacktestKit.alignToInterval(new Date(Date.now() - 31 * 24 * 60 * 60 * 1000), "1m");
|
|
845
|
+
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.`);
|
|
846
|
+
BacktestKit.addFrameSchema({
|
|
847
|
+
frameName: FrameName$1.DefaultFrame,
|
|
848
|
+
interval: "1m",
|
|
849
|
+
startDate,
|
|
850
|
+
endDate,
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const symbol = payload.symbol || "BTCUSDT";
|
|
855
|
+
const strategyList = await BacktestKit.listStrategySchema();
|
|
856
|
+
const strategyNames = strategyList.map((s) => s.strategyName);
|
|
857
|
+
if (!strategyNames.length) {
|
|
858
|
+
throw new Error("No strategies found in provided entry points");
|
|
859
|
+
}
|
|
860
|
+
const [defaultExchangeName = null] = await BacktestKit.listExchangeSchema();
|
|
861
|
+
const [defaultFrameName = null] = await BacktestKit.listFrameSchema();
|
|
862
|
+
const exchangeName = defaultExchangeName?.exchangeName;
|
|
863
|
+
const frameName = defaultFrameName?.frameName;
|
|
864
|
+
if (!exchangeName) {
|
|
865
|
+
throw new Error("Exchange name is required");
|
|
866
|
+
}
|
|
867
|
+
if (!frameName) {
|
|
868
|
+
throw new Error("Frame name is required");
|
|
869
|
+
}
|
|
870
|
+
BacktestKit.addWalkerSchema({
|
|
871
|
+
walkerName: WALKER_NAME,
|
|
872
|
+
exchangeName,
|
|
873
|
+
frameName,
|
|
874
|
+
strategies: strategyNames,
|
|
875
|
+
});
|
|
876
|
+
if (!payload.noCache) {
|
|
877
|
+
await this.cacheLogicService.execute(payload.cacheInterval, {
|
|
878
|
+
exchangeName,
|
|
879
|
+
frameName,
|
|
880
|
+
symbol,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
if (payload.verbose) {
|
|
884
|
+
BacktestKit.overrideExchangeSchema({
|
|
885
|
+
exchangeName,
|
|
886
|
+
callbacks: {
|
|
887
|
+
onCandleData(symbol, interval, since) {
|
|
888
|
+
console.log(`Received candle data for symbol: ${symbol}, interval: ${interval}, since: ${since.toUTCString()}`);
|
|
889
|
+
},
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
notifyVerbose();
|
|
893
|
+
}
|
|
894
|
+
if (payload.verbose) {
|
|
895
|
+
BacktestKit.overrideWalkerSchema({
|
|
896
|
+
walkerName: WALKER_NAME,
|
|
897
|
+
callbacks: {
|
|
898
|
+
onStrategyStart(strategyName, symbol) {
|
|
899
|
+
console.log(`Strategy started: ${strategyName} for symbol: ${symbol}`);
|
|
900
|
+
},
|
|
901
|
+
onStrategyError(strategyName, symbol, error) {
|
|
902
|
+
console.error(`Strategy error: ${strategyName} for symbol: ${symbol}`, error);
|
|
903
|
+
},
|
|
904
|
+
onStrategyComplete(strategyName, symbol) {
|
|
905
|
+
console.log(`Strategy completed: ${strategyName} for symbol: ${symbol}`);
|
|
906
|
+
},
|
|
907
|
+
onComplete(results) {
|
|
908
|
+
console.log(`Walker completed for symbol: ${results.symbol}`, results);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
BacktestKit.Walker.background(symbol, { walkerName: WALKER_NAME });
|
|
914
|
+
const [awaiter, { resolve: res }] = functoolsKit.createAwaiter();
|
|
915
|
+
const unWalker = BacktestKit.listenDoneWalker(() => {
|
|
916
|
+
console.log("Walker comparison finished");
|
|
917
|
+
unWalker();
|
|
918
|
+
res();
|
|
919
|
+
});
|
|
920
|
+
payload.verbose && console.time("Walker");
|
|
921
|
+
await awaiter;
|
|
922
|
+
payload.verbose && console.timeEnd("Walker");
|
|
923
|
+
const dumpName = payload.output || `walker_${symbol}_${Date.now()}`;
|
|
924
|
+
const dumpDir = path.join(process.cwd(), "dump");
|
|
925
|
+
if (payload.json) {
|
|
926
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.json`);
|
|
927
|
+
const data = await BacktestKit.Walker.getData(symbol, { walkerName: WALKER_NAME });
|
|
928
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
929
|
+
await fs$1.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
930
|
+
console.log(`Saved: ${filePath}`);
|
|
931
|
+
process.exit(0);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (payload.markdown) {
|
|
935
|
+
const filePath = path.resolve(dumpDir, `${dumpName}.md`);
|
|
936
|
+
const report = await BacktestKit.Walker.getReport(symbol, { walkerName: WALKER_NAME });
|
|
937
|
+
await fs$1.mkdir(dumpDir, { recursive: true });
|
|
938
|
+
await fs$1.writeFile(filePath, report, "utf-8");
|
|
939
|
+
console.log(`Saved: ${filePath}`);
|
|
940
|
+
process.exit(0);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
const report = await BacktestKit.Walker.getReport(symbol, { walkerName: WALKER_NAME });
|
|
944
|
+
console.log(report);
|
|
945
|
+
process.exit(0);
|
|
946
|
+
});
|
|
947
|
+
this.connect = functoolsKit.singleshot(async () => {
|
|
948
|
+
this.loggerService.log("walkerMainService connect");
|
|
949
|
+
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)))) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const { values } = getArgs();
|
|
953
|
+
if (!values.walker) {
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
const entryPoints = getPositionals();
|
|
957
|
+
if (!entryPoints.length) {
|
|
958
|
+
throw new Error("At least one entry point is required");
|
|
959
|
+
}
|
|
960
|
+
const cacheInterval = GET_CACHE_INTERVAL_LIST_FN();
|
|
961
|
+
return await this.run({
|
|
962
|
+
entryPoints,
|
|
963
|
+
json: values.json,
|
|
964
|
+
markdown: values.markdown,
|
|
965
|
+
symbol: values.symbol,
|
|
966
|
+
output: values.output,
|
|
967
|
+
cacheInterval,
|
|
968
|
+
verbose: values.verbose,
|
|
969
|
+
noCache: values.noCache,
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
792
975
|
class LiveMainService {
|
|
793
976
|
constructor() {
|
|
794
977
|
this.loggerService = inject(TYPES.loggerService);
|
|
@@ -806,7 +989,10 @@ class LiveMainService {
|
|
|
806
989
|
this.frontendProviderService.connect();
|
|
807
990
|
this.telegramProviderService.connect();
|
|
808
991
|
}
|
|
809
|
-
|
|
992
|
+
{
|
|
993
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
994
|
+
await this.moduleConnectionService.loadModule("./live.module");
|
|
995
|
+
}
|
|
810
996
|
{
|
|
811
997
|
this.exchangeSchemaService.addSchema();
|
|
812
998
|
this.symbolSchemaService.addSchema();
|
|
@@ -833,7 +1019,6 @@ class LiveMainService {
|
|
|
833
1019
|
});
|
|
834
1020
|
notifyVerbose();
|
|
835
1021
|
}
|
|
836
|
-
await this.moduleConnectionService.loadModule("./live.module");
|
|
837
1022
|
BacktestKit.Live.background(symbol, {
|
|
838
1023
|
strategyName,
|
|
839
1024
|
exchangeName,
|
|
@@ -849,7 +1034,7 @@ class LiveMainService {
|
|
|
849
1034
|
if (!values.live) {
|
|
850
1035
|
return;
|
|
851
1036
|
}
|
|
852
|
-
const entryPoint =
|
|
1037
|
+
const [entryPoint = null] = getPositionals();
|
|
853
1038
|
if (!entryPoint) {
|
|
854
1039
|
throw new Error("Entry point is required");
|
|
855
1040
|
}
|
|
@@ -879,7 +1064,10 @@ class PaperMainService {
|
|
|
879
1064
|
this.frontendProviderService.connect();
|
|
880
1065
|
this.telegramProviderService.connect();
|
|
881
1066
|
}
|
|
882
|
-
|
|
1067
|
+
{
|
|
1068
|
+
await this.resolveService.attachJavascript(payload.entryPoint);
|
|
1069
|
+
await this.moduleConnectionService.loadModule("./paper.module");
|
|
1070
|
+
}
|
|
883
1071
|
{
|
|
884
1072
|
this.exchangeSchemaService.addSchema();
|
|
885
1073
|
this.symbolSchemaService.addSchema();
|
|
@@ -906,7 +1094,6 @@ class PaperMainService {
|
|
|
906
1094
|
});
|
|
907
1095
|
notifyVerbose();
|
|
908
1096
|
}
|
|
909
|
-
await this.moduleConnectionService.loadModule("./paper.module");
|
|
910
1097
|
BacktestKit.Live.background(symbol, {
|
|
911
1098
|
strategyName,
|
|
912
1099
|
exchangeName,
|
|
@@ -922,7 +1109,7 @@ class PaperMainService {
|
|
|
922
1109
|
if (!values.paper) {
|
|
923
1110
|
return;
|
|
924
1111
|
}
|
|
925
|
-
const entryPoint =
|
|
1112
|
+
const [entryPoint = null] = getPositionals();
|
|
926
1113
|
if (!entryPoint) {
|
|
927
1114
|
throw new Error("Entry point is required");
|
|
928
1115
|
}
|
|
@@ -1923,8 +2110,7 @@ class BabelService {
|
|
|
1923
2110
|
}
|
|
1924
2111
|
}
|
|
1925
2112
|
|
|
1926
|
-
const TRANSPILE_FN = (code, self) => {
|
|
1927
|
-
const require = self.getBaseRequire();
|
|
2113
|
+
const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, self, require) => {
|
|
1928
2114
|
const __filename = self.__filename;
|
|
1929
2115
|
const __dirname = self.__dirname;
|
|
1930
2116
|
const module = { exports: {} };
|
|
@@ -1933,7 +2119,7 @@ const TRANSPILE_FN = (code, self) => {
|
|
|
1933
2119
|
eval(self.params.babel.transpile(code));
|
|
1934
2120
|
}
|
|
1935
2121
|
catch (error) {
|
|
1936
|
-
console.log(`Error during transpilation error=\`${functoolsKit.getErrorMessage(error)}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
2122
|
+
console.log(`Error during transpilation error=\`${functoolsKit.getErrorMessage(error)}\` path=${path} __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
1937
2123
|
process.exit(-1);
|
|
1938
2124
|
}
|
|
1939
2125
|
return {
|
|
@@ -1943,9 +2129,9 @@ const TRANSPILE_FN = (code, self) => {
|
|
|
1943
2129
|
exports,
|
|
1944
2130
|
module,
|
|
1945
2131
|
};
|
|
1946
|
-
};
|
|
1947
|
-
const REQUIRE_ENTRY_FACTORY = (filePath, self) => {
|
|
1948
|
-
const baseRequire = self
|
|
2132
|
+
});
|
|
2133
|
+
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2134
|
+
const baseRequire = CREATE_BASE_REQUIRE_FN(self, seen);
|
|
1949
2135
|
try {
|
|
1950
2136
|
return baseRequire(filePath);
|
|
1951
2137
|
}
|
|
@@ -1953,12 +2139,12 @@ const REQUIRE_ENTRY_FACTORY = (filePath, self) => {
|
|
|
1953
2139
|
return null;
|
|
1954
2140
|
}
|
|
1955
2141
|
};
|
|
1956
|
-
const BABEL_ENTRY_FACTORY = (filePath, self) => {
|
|
2142
|
+
const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
1957
2143
|
try {
|
|
1958
2144
|
const resolvedPath = path.resolve(self.__dirname, filePath);
|
|
1959
2145
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
1960
2146
|
const child = self.fork(path.dirname(resolvedPath));
|
|
1961
|
-
const { module } = TRANSPILE_FN(code, child);
|
|
2147
|
+
const { module } = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
1962
2148
|
return "default" in module.exports
|
|
1963
2149
|
? module.exports.default
|
|
1964
2150
|
: module.exports;
|
|
@@ -1999,21 +2185,20 @@ const GET_RESOLVED_EXT_FN = (filePath) => {
|
|
|
1999
2185
|
}
|
|
2000
2186
|
return filePath;
|
|
2001
2187
|
};
|
|
2002
|
-
const ENTRY_FACTORY = (filePath, self) => {
|
|
2003
|
-
filePath = GET_RESOLVED_EXT_FN(filePath);
|
|
2188
|
+
const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2004
2189
|
{
|
|
2005
2190
|
let result = null;
|
|
2006
|
-
if ((result = REQUIRE_ENTRY_FACTORY(filePath, self))) {
|
|
2191
|
+
if ((result = REQUIRE_ENTRY_FACTORY(filePath, self, seen))) {
|
|
2007
2192
|
return result;
|
|
2008
2193
|
}
|
|
2009
|
-
if ((result = BABEL_ENTRY_FACTORY(filePath, self))) {
|
|
2194
|
+
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen))) {
|
|
2010
2195
|
return result;
|
|
2011
2196
|
}
|
|
2012
2197
|
}
|
|
2013
2198
|
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
|
|
2014
2199
|
};
|
|
2015
|
-
const CREATE_BASE_REQUIRE_FN = (self) => {
|
|
2016
|
-
const baseRequire =
|
|
2200
|
+
const CREATE_BASE_REQUIRE_FN = (self, seen) => {
|
|
2201
|
+
const baseRequire = self.baseRequire();
|
|
2017
2202
|
return new Proxy(baseRequire, {
|
|
2018
2203
|
apply(_target, _this, args) {
|
|
2019
2204
|
const id = args[0];
|
|
@@ -2034,7 +2219,7 @@ const CREATE_BASE_REQUIRE_FN = (self) => {
|
|
|
2034
2219
|
if (id.startsWith("./") || id.startsWith("../")) {
|
|
2035
2220
|
const resolved = path.resolve(self.__dirname, id);
|
|
2036
2221
|
const child = self.fork(path.dirname(resolved));
|
|
2037
|
-
return child.import(resolved);
|
|
2222
|
+
return child.import(resolved, seen);
|
|
2038
2223
|
}
|
|
2039
2224
|
return baseRequire(id);
|
|
2040
2225
|
},
|
|
@@ -2048,11 +2233,11 @@ const BacktestKitCli = new Proxy({}, {
|
|
|
2048
2233
|
class ClientLoader {
|
|
2049
2234
|
constructor(params) {
|
|
2050
2235
|
this.params = params;
|
|
2051
|
-
this.
|
|
2052
|
-
this.params.logger.log("ClientLoader
|
|
2236
|
+
this.baseRequire = functoolsKit.singleshot(() => {
|
|
2237
|
+
this.params.logger.log("ClientLoader baseRequire", {
|
|
2053
2238
|
basePath: this.params.path,
|
|
2054
2239
|
});
|
|
2055
|
-
return
|
|
2240
|
+
return module$1.createRequire(this.__filename);
|
|
2056
2241
|
});
|
|
2057
2242
|
this.__filename = path.join(params.path, "index.cjs");
|
|
2058
2243
|
this.__dirname = path.dirname(this.__filename);
|
|
@@ -2068,12 +2253,21 @@ class ClientLoader {
|
|
|
2068
2253
|
logger: this.params.logger,
|
|
2069
2254
|
});
|
|
2070
2255
|
}
|
|
2071
|
-
import(filePath) {
|
|
2256
|
+
import(filePath, seen = new Set()) {
|
|
2072
2257
|
this.params.logger.log("ClientLoader import", {
|
|
2073
2258
|
filePath,
|
|
2074
2259
|
basePath: this.params.path,
|
|
2075
2260
|
});
|
|
2076
|
-
|
|
2261
|
+
const resolved = GET_RESOLVED_EXT_FN(filePath);
|
|
2262
|
+
if (seen.has(resolved)) {
|
|
2263
|
+
throw new Error(`Circular dependency detected: ${resolved} (seen: ${[...seen].join("->")}->${resolved})`);
|
|
2264
|
+
}
|
|
2265
|
+
const currentSeen = new Set(seen);
|
|
2266
|
+
if (!seen.size) {
|
|
2267
|
+
currentSeen.add(path.resolve(this.__dirname, filePath));
|
|
2268
|
+
}
|
|
2269
|
+
currentSeen.add(resolved);
|
|
2270
|
+
return ENTRY_FACTORY(resolved, this, currentSeen);
|
|
2077
2271
|
}
|
|
2078
2272
|
check(filePath) {
|
|
2079
2273
|
this.params.logger.log("ClientLoader check", {
|
|
@@ -2141,6 +2335,7 @@ class LoaderService {
|
|
|
2141
2335
|
}
|
|
2142
2336
|
{
|
|
2143
2337
|
provide(TYPES.backtestMainService, () => new BacktestMainService());
|
|
2338
|
+
provide(TYPES.walkerMainService, () => new WalkerMainService());
|
|
2144
2339
|
provide(TYPES.paperMainService, () => new PaperMainService());
|
|
2145
2340
|
provide(TYPES.liveMainService, () => new LiveMainService());
|
|
2146
2341
|
}
|
|
@@ -2180,6 +2375,7 @@ const connectionServices = {
|
|
|
2180
2375
|
};
|
|
2181
2376
|
const mainServices = {
|
|
2182
2377
|
backtestMainService: inject(TYPES.backtestMainService),
|
|
2378
|
+
walkerMainService: inject(TYPES.walkerMainService),
|
|
2183
2379
|
paperMainService: inject(TYPES.paperMainService),
|
|
2184
2380
|
liveMainService: inject(TYPES.liveMainService),
|
|
2185
2381
|
};
|
|
@@ -2215,8 +2411,14 @@ const cli = {
|
|
|
2215
2411
|
};
|
|
2216
2412
|
init();
|
|
2217
2413
|
|
|
2218
|
-
const MODES = ["backtest", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2219
|
-
const
|
|
2414
|
+
const MODES = ["backtest", "walker", "paper", "live", "pine", "dump", "init", "help", "version"];
|
|
2415
|
+
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2416
|
+
const HELP_TEXT$1 = `
|
|
2417
|
+
Example:
|
|
2418
|
+
|
|
2419
|
+
node ${ENTRY_PATH$1} --help
|
|
2420
|
+
`.trimStart();
|
|
2421
|
+
const main$b = async () => {
|
|
2220
2422
|
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)))) {
|
|
2221
2423
|
return;
|
|
2222
2424
|
}
|
|
@@ -2224,18 +2426,21 @@ const main$a = async () => {
|
|
|
2224
2426
|
if (MODES.some((mode) => values[mode])) {
|
|
2225
2427
|
return;
|
|
2226
2428
|
}
|
|
2227
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
2429
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.0"}\n`);
|
|
2430
|
+
process.stdout.write("\n");
|
|
2228
2431
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
2432
|
+
process.stdout.write("\n");
|
|
2433
|
+
process.stdout.write(HELP_TEXT$1);
|
|
2229
2434
|
process.exit(0);
|
|
2230
2435
|
};
|
|
2231
|
-
main$
|
|
2436
|
+
main$b();
|
|
2232
2437
|
|
|
2233
2438
|
const notifyShutdown = functoolsKit.singleshot(async () => {
|
|
2234
2439
|
console.log("Graceful shutdown initiated. Press Ctrl+C again to force quit.");
|
|
2235
2440
|
});
|
|
2236
2441
|
|
|
2237
|
-
const BEFORE_EXIT_FN$
|
|
2238
|
-
process.off("SIGINT", BEFORE_EXIT_FN$
|
|
2442
|
+
const BEFORE_EXIT_FN$5 = functoolsKit.singleshot(async () => {
|
|
2443
|
+
process.off("SIGINT", BEFORE_EXIT_FN$5);
|
|
2239
2444
|
const [running = null] = await BacktestKit.Backtest.list();
|
|
2240
2445
|
if (!running) {
|
|
2241
2446
|
return;
|
|
@@ -2251,6 +2456,35 @@ const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
|
2251
2456
|
frameName,
|
|
2252
2457
|
});
|
|
2253
2458
|
});
|
|
2459
|
+
const listenGracefulShutdown$5 = functoolsKit.singleshot(() => {
|
|
2460
|
+
process.on("SIGINT", BEFORE_EXIT_FN$5);
|
|
2461
|
+
});
|
|
2462
|
+
const main$a = async () => {
|
|
2463
|
+
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)))) {
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const { values } = getArgs();
|
|
2467
|
+
if (!values.backtest) {
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
await cli.backtestMainService.connect();
|
|
2471
|
+
listenGracefulShutdown$5();
|
|
2472
|
+
};
|
|
2473
|
+
main$a();
|
|
2474
|
+
|
|
2475
|
+
const BEFORE_EXIT_FN$4 = functoolsKit.singleshot(async () => {
|
|
2476
|
+
process.off("SIGINT", BEFORE_EXIT_FN$4);
|
|
2477
|
+
const [running = null] = await BacktestKit.Walker.list();
|
|
2478
|
+
if (!running) {
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
notifyShutdown();
|
|
2482
|
+
const { walkerName, symbol, status } = running;
|
|
2483
|
+
if (status === "fulfilled") {
|
|
2484
|
+
return;
|
|
2485
|
+
}
|
|
2486
|
+
BacktestKit.Walker.stop(symbol, { walkerName });
|
|
2487
|
+
});
|
|
2254
2488
|
const listenGracefulShutdown$4 = functoolsKit.singleshot(() => {
|
|
2255
2489
|
process.on("SIGINT", BEFORE_EXIT_FN$4);
|
|
2256
2490
|
});
|
|
@@ -2259,11 +2493,11 @@ const main$9 = async () => {
|
|
|
2259
2493
|
return;
|
|
2260
2494
|
}
|
|
2261
2495
|
const { values } = getArgs();
|
|
2262
|
-
if (!values.
|
|
2496
|
+
if (!values.walker) {
|
|
2263
2497
|
return;
|
|
2264
2498
|
}
|
|
2265
|
-
await cli.backtestMainService.connect();
|
|
2266
2499
|
listenGracefulShutdown$4();
|
|
2500
|
+
await cli.walkerMainService.connect();
|
|
2267
2501
|
};
|
|
2268
2502
|
main$9();
|
|
2269
2503
|
|
|
@@ -2399,7 +2633,7 @@ const main$4 = async () => {
|
|
|
2399
2633
|
if (!values.pine) {
|
|
2400
2634
|
return;
|
|
2401
2635
|
}
|
|
2402
|
-
const entryPoint =
|
|
2636
|
+
const [entryPoint = null] = getPositionals();
|
|
2403
2637
|
if (!entryPoint) {
|
|
2404
2638
|
return;
|
|
2405
2639
|
}
|
|
@@ -2619,103 +2853,122 @@ const main$2 = async () => {
|
|
|
2619
2853
|
main$2();
|
|
2620
2854
|
|
|
2621
2855
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
2622
|
-
const HELP_TEXT = `
|
|
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
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2856
|
+
const HELP_TEXT = `
|
|
2857
|
+
Usage:
|
|
2858
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
2859
|
+
|
|
2860
|
+
Modes:
|
|
2861
|
+
|
|
2862
|
+
--backtest <entry> Run strategy against historical candle data
|
|
2863
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
2864
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
2865
|
+
--live <entry> Live trading with real orders
|
|
2866
|
+
--pine <entry> Execute a local .pine indicator file
|
|
2867
|
+
--dump Fetch and save raw OHLCV candles
|
|
2868
|
+
--init Scaffold a new project in the current directory
|
|
2869
|
+
--help Print this help message
|
|
2870
|
+
|
|
2871
|
+
Backtest flags:
|
|
2872
|
+
|
|
2873
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2874
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
2875
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
2876
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
2877
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
2878
|
+
--noCache Skip candle cache warming before the run
|
|
2879
|
+
--verbose Log every candle fetch to stdout
|
|
2880
|
+
--ui Start web dashboard at http://localhost:60050
|
|
2881
|
+
--telegram Send trade notifications to Telegram
|
|
2882
|
+
|
|
2883
|
+
Walker flags (--walker):
|
|
2884
|
+
|
|
2885
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2886
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
2887
|
+
--noCache Skip candle cache warming before the run
|
|
2888
|
+
--verbose Log every candle fetch to stdout
|
|
2889
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
2890
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
2891
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
2892
|
+
|
|
2893
|
+
Each positional argument is a strategy entry point. All strategy files are loaded without
|
|
2894
|
+
changing process.cwd() — .env is read from the working directory only.
|
|
2895
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
2896
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
2897
|
+
|
|
2898
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
2899
|
+
|
|
2900
|
+
Paper / Live flags:
|
|
2901
|
+
|
|
2902
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2903
|
+
--strategy <string> Strategy name (default: first registered)
|
|
2904
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2905
|
+
--verbose Log every candle fetch to stdout
|
|
2906
|
+
--ui Start web dashboard
|
|
2907
|
+
--telegram Send Telegram notifications
|
|
2908
|
+
|
|
2909
|
+
PineScript flags (--pine):
|
|
2910
|
+
|
|
2911
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2912
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
2913
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
2914
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
2915
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2916
|
+
--output <string> Output file base name without extension
|
|
2917
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
2918
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
2919
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
2920
|
+
|
|
2921
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
2922
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
2923
|
+
|
|
2924
|
+
Candle dump flags (--dump):
|
|
2925
|
+
|
|
2926
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
2927
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
2928
|
+
--limit <string> Number of candles (default: 250)
|
|
2929
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
2930
|
+
--exchange <string> Exchange name (default: first registered)
|
|
2931
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
2932
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
2933
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
2934
|
+
|
|
2935
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
2936
|
+
|
|
2937
|
+
Init flags (--init):
|
|
2938
|
+
|
|
2939
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
2940
|
+
|
|
2941
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
2942
|
+
|
|
2943
|
+
Module hooks (loaded automatically by each mode):
|
|
2944
|
+
|
|
2945
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
2946
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
2947
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
2948
|
+
modules/live.module --live Broker adapter for live trading
|
|
2949
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
2950
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
2951
|
+
|
|
2952
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
2953
|
+
|
|
2954
|
+
Environment variables:
|
|
2955
|
+
|
|
2956
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
2957
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
2958
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
2959
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
2960
|
+
|
|
2961
|
+
Examples:
|
|
2962
|
+
|
|
2963
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
2964
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --ui ./content/feb_2026.strategy.ts
|
|
2965
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
2966
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
2967
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
2968
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
2969
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
2970
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
2971
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
2719
2972
|
`.trimStart();
|
|
2720
2973
|
const main$1 = async () => {
|
|
2721
2974
|
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)))) {
|
|
@@ -2725,6 +2978,7 @@ const main$1 = async () => {
|
|
|
2725
2978
|
if (!values.help) {
|
|
2726
2979
|
return;
|
|
2727
2980
|
}
|
|
2981
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.0"}\n\n`);
|
|
2728
2982
|
process.stdout.write(HELP_TEXT);
|
|
2729
2983
|
process.exit(0);
|
|
2730
2984
|
};
|
|
@@ -2738,7 +2992,7 @@ const main = async () => {
|
|
|
2738
2992
|
if (!values.version) {
|
|
2739
2993
|
return;
|
|
2740
2994
|
}
|
|
2741
|
-
process.stdout.write(`@backtest-kit/cli ${"6.
|
|
2995
|
+
process.stdout.write(`@backtest-kit/cli ${"6.2.0"}\n`);
|
|
2742
2996
|
process.exit(0);
|
|
2743
2997
|
};
|
|
2744
2998
|
main();
|
|
@@ -2760,7 +3014,7 @@ async function run(mode, args) {
|
|
|
2760
3014
|
}
|
|
2761
3015
|
if (mode === "backtest") {
|
|
2762
3016
|
await cli.backtestMainService.run(args);
|
|
2763
|
-
listenGracefulShutdown$
|
|
3017
|
+
listenGracefulShutdown$5();
|
|
2764
3018
|
return;
|
|
2765
3019
|
}
|
|
2766
3020
|
if (mode === "paper") {
|