@backtest-kit/cli 14.0.0 → 15.0.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 +874 -874
- package/build/index.cjs +387 -274
- package/build/index.mjs +381 -271
- 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 +39 -39
- package/docker/tsconfig.json +36 -36
- package/package.json +129 -129
- 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 +7 -7
package/build/index.mjs
CHANGED
|
@@ -85,17 +85,28 @@ const main$i = () => {
|
|
|
85
85
|
main$i();
|
|
86
86
|
|
|
87
87
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
88
|
-
function dumpStackTrace() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
result
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
function dumpStackTrace(error) {
|
|
89
|
+
// Parse the ERROR's own stack — stackTrace.get() captured the handler's
|
|
90
|
+
// stack instead, so error.txt carried a useless trace of ErrorService itself.
|
|
91
|
+
if (!(error instanceof Error) || !error.stack) {
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const trace = stackTrace.parse(error);
|
|
96
|
+
const result = [];
|
|
97
|
+
trace.forEach((callSite) => {
|
|
98
|
+
result.push(`File: ${callSite.getFileName()}`);
|
|
99
|
+
result.push(`Line: ${callSite.getLineNumber()}`);
|
|
100
|
+
result.push(`Function: ${callSite.getFunctionName() || "anonymous"}`);
|
|
101
|
+
result.push(`Method: ${callSite.getMethodName() || "none"}`);
|
|
102
|
+
result.push("---");
|
|
103
|
+
});
|
|
104
|
+
return str.newline(result);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Unparseable/exotic stack format — dump the raw stack rather than nothing
|
|
108
|
+
return error.stack;
|
|
109
|
+
}
|
|
99
110
|
}
|
|
100
111
|
const timeNow = () => {
|
|
101
112
|
const d = new Date();
|
|
@@ -115,7 +126,7 @@ class ErrorService {
|
|
|
115
126
|
message: getErrorMessage(error),
|
|
116
127
|
data: errorData(error),
|
|
117
128
|
}, null, 2);
|
|
118
|
-
const trace = dumpStackTrace();
|
|
129
|
+
const trace = dumpStackTrace(error);
|
|
119
130
|
fs.appendFileSync("./error.txt", `${date}\n${msg}\n${trace}\n\n`);
|
|
120
131
|
};
|
|
121
132
|
this._listenForError = () => {
|
|
@@ -240,10 +251,6 @@ const SETUP_ADAPTER_FN = () => {
|
|
|
240
251
|
StorageLive.usePersist();
|
|
241
252
|
StorageBacktest.useMemory();
|
|
242
253
|
}
|
|
243
|
-
{
|
|
244
|
-
RecentLive.usePersist();
|
|
245
|
-
RecentBacktest.useMemory();
|
|
246
|
-
}
|
|
247
254
|
{
|
|
248
255
|
NotificationLive.usePersist();
|
|
249
256
|
NotificationBacktest.useMemory();
|
|
@@ -309,6 +316,7 @@ class SetupUtils {
|
|
|
309
316
|
Markdown.disable();
|
|
310
317
|
Report.disable();
|
|
311
318
|
Dump.disable();
|
|
319
|
+
State.disable();
|
|
312
320
|
Memory.disable();
|
|
313
321
|
}
|
|
314
322
|
{
|
|
@@ -497,7 +505,11 @@ const ADD_EXCHANGE_FN = (self) => {
|
|
|
497
505
|
formatPrice: async (symbol, price) => {
|
|
498
506
|
const exchange = await getExchange();
|
|
499
507
|
const market = exchange.market(symbol);
|
|
500
|
-
|
|
508
|
+
// ccxt binance uses TICK_SIZE precision mode, so precision.price IS the
|
|
509
|
+
// tick size. limits.price.min (PRICE_FILTER minPrice) is only a fallback:
|
|
510
|
+
// it merely coincides with the tick on most Binance markets and is NOT
|
|
511
|
+
// the tick size by contract.
|
|
512
|
+
const tickSize = [market.precision?.price, market.limits?.price?.min].find((value) => typeof value === "number" && isFinite(value) && value > 0);
|
|
501
513
|
if (tickSize !== undefined) {
|
|
502
514
|
return roundTicks(price, tickSize);
|
|
503
515
|
}
|
|
@@ -506,7 +518,10 @@ const ADD_EXCHANGE_FN = (self) => {
|
|
|
506
518
|
formatQuantity: async (symbol, quantity) => {
|
|
507
519
|
const exchange = await getExchange();
|
|
508
520
|
const market = exchange.market(symbol);
|
|
509
|
-
|
|
521
|
+
// Same rule as formatPrice: precision.amount is the LOT_SIZE step;
|
|
522
|
+
// limits.amount.min is minQty, which can exceed the step on some markets
|
|
523
|
+
// and would over-truncate quantities if preferred.
|
|
524
|
+
const stepSize = [market.precision?.amount, market.limits?.amount?.min].find((value) => typeof value === "number" && isFinite(value) && value > 0);
|
|
510
525
|
if (stepSize !== undefined) {
|
|
511
526
|
return roundTicks(quantity, stepSize);
|
|
512
527
|
}
|
|
@@ -651,7 +666,7 @@ const getArgs = singleshot(() => {
|
|
|
651
666
|
},
|
|
652
667
|
cacheInterval: {
|
|
653
668
|
type: "string",
|
|
654
|
-
default: "1m, 15m, 30m, 4h",
|
|
669
|
+
default: "1m, 15m, 30m, 1h, 4h",
|
|
655
670
|
},
|
|
656
671
|
// pinescript entry
|
|
657
672
|
editor: {
|
|
@@ -983,6 +998,8 @@ const notifyKill = singleshot(() => {
|
|
|
983
998
|
process.on("SIGINT", () => kill());
|
|
984
999
|
});
|
|
985
1000
|
|
|
1001
|
+
// Must match the getArgs --cacheInterval default ("1m, 15m, 30m, 1h, 4h"):
|
|
1002
|
+
// this fallback is only reachable with an explicit --cacheInterval ""
|
|
986
1003
|
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
987
1004
|
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
988
1005
|
const { values } = getArgs();
|
|
@@ -1125,6 +1142,8 @@ var WalkerName;
|
|
|
1125
1142
|
})(WalkerName || (WalkerName = {}));
|
|
1126
1143
|
var WalkerName$1 = WalkerName;
|
|
1127
1144
|
|
|
1145
|
+
// Must match the getArgs --cacheInterval default ("1m, 15m, 30m, 1h, 4h"):
|
|
1146
|
+
// this fallback is only reachable with an explicit --cacheInterval ""
|
|
1128
1147
|
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
1129
1148
|
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
1130
1149
|
const { values } = getArgs();
|
|
@@ -1635,7 +1654,7 @@ const MAP_SYMBOL_CONFIG_FN = (config) => {
|
|
|
1635
1654
|
symbol,
|
|
1636
1655
|
icon,
|
|
1637
1656
|
logo: logo ?? icon,
|
|
1638
|
-
priority: priority ?? idx,
|
|
1657
|
+
priority: priority ?? -idx,
|
|
1639
1658
|
displayName: displayName ?? symbol,
|
|
1640
1659
|
index: idx,
|
|
1641
1660
|
...other,
|
|
@@ -1799,20 +1818,33 @@ class QuickchartApiService {
|
|
|
1799
1818
|
const HEALTH_CHECK_DELAY = 60000;
|
|
1800
1819
|
let _is_stopped = false;
|
|
1801
1820
|
const handleHealthCheck = singleshot(async (bot) => {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1821
|
+
// Первый чек выполняется синхронно с запуском: ошибка уходит вызывающему
|
|
1822
|
+
// getTelegram (бот не стартует, если Telegram недоступен сразу).
|
|
1823
|
+
try {
|
|
1824
|
+
await bot.telegram.getMe();
|
|
1825
|
+
}
|
|
1826
|
+
catch {
|
|
1827
|
+
console.log("Bot is offline");
|
|
1828
|
+
throw new Error("Telegram goes offline");
|
|
1829
|
+
}
|
|
1830
|
+
// Периодические чеки: раньше throw внутри setTimeout-цикла становился
|
|
1831
|
+
// unhandledRejection (никем не await-ился) и ронял процесс неконтролируемо,
|
|
1832
|
+
// оставляя дочерние процессы (UI-сервер) живыми. Теперь — управляемый kill.
|
|
1833
|
+
const schedule = () => {
|
|
1834
|
+
setTimeout(() => {
|
|
1835
|
+
if (_is_stopped) {
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
bot.telegram
|
|
1839
|
+
.getMe()
|
|
1840
|
+
.then(schedule)
|
|
1841
|
+
.catch(() => {
|
|
1842
|
+
console.log("Bot is offline");
|
|
1843
|
+
kill(-1);
|
|
1844
|
+
});
|
|
1845
|
+
}, HEALTH_CHECK_DELAY);
|
|
1814
1846
|
};
|
|
1815
|
-
|
|
1847
|
+
schedule();
|
|
1816
1848
|
});
|
|
1817
1849
|
const getTelegram = singleshot(async () => {
|
|
1818
1850
|
if (_is_stopped) {
|
|
@@ -1911,8 +1943,11 @@ const publishInternal = queued(async ({ channel, msg, images = [], onScheduled,
|
|
|
1911
1943
|
onScheduled();
|
|
1912
1944
|
let isOk = true;
|
|
1913
1945
|
try {
|
|
1946
|
+
// Must persist across flood retries: declared inside `execute` it was reset
|
|
1947
|
+
// on every recursive retry, re-sending the media group (duplicate channel
|
|
1948
|
+
// posts) when the flood error hit the follow-up sendMessage.
|
|
1949
|
+
let isImagesPublished = false;
|
|
1914
1950
|
const execute = async (retry = 0) => {
|
|
1915
|
-
let isImagesPublished = false;
|
|
1916
1951
|
try {
|
|
1917
1952
|
if (images?.length) {
|
|
1918
1953
|
console.log("Bot fetching images");
|
|
@@ -2013,21 +2048,67 @@ class TelegramApiService {
|
|
|
2013
2048
|
task.finally(() => {
|
|
2014
2049
|
TIMEOUT_COUNTER -= 1;
|
|
2015
2050
|
});
|
|
2051
|
+
// The watchdog must fire HERE: when every publish is stuck, this branch is
|
|
2052
|
+
// the only one executing — checking after the early return made the kill
|
|
2053
|
+
// reachable only once a publish suddenly succeeded (problem already gone).
|
|
2054
|
+
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
2055
|
+
setTimeout(() => kill(-1), 5000);
|
|
2056
|
+
}
|
|
2016
2057
|
return "Message scheduled for publication";
|
|
2017
2058
|
}
|
|
2018
|
-
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
2019
|
-
setTimeout(() => kill(-1), 5000);
|
|
2020
|
-
}
|
|
2021
2059
|
return "Message published successfully";
|
|
2022
2060
|
};
|
|
2023
2061
|
}
|
|
2024
2062
|
}
|
|
2025
2063
|
|
|
2026
|
-
const
|
|
2064
|
+
const TELEGRAM_MAX_SYMBOLS = 4090;
|
|
2027
2065
|
const TELEGRAM_SYMBOL_RESERVED = 96;
|
|
2066
|
+
/** Void tags never get a closing counterpart when balancing truncated HTML. */
|
|
2067
|
+
const VOID_TAGS = new Set(["br"]);
|
|
2068
|
+
/**
|
|
2069
|
+
* Truncates Telegram HTML without breaking it: a blunt substring could cut a
|
|
2070
|
+
* tag or entity in half and leave unbalanced tags — Telegram then rejects the
|
|
2071
|
+
* WHOLE message ("can't parse entities"), so the notification would be lost
|
|
2072
|
+
* instead of shortened. Closing tags appended here fit into
|
|
2073
|
+
* TELEGRAM_SYMBOL_RESERVED headroom.
|
|
2074
|
+
*/
|
|
2075
|
+
const truncateTelegramHtml = (html, maxChars) => {
|
|
2076
|
+
let result = html.substring(0, maxChars);
|
|
2077
|
+
// Don't cut a tag in half
|
|
2078
|
+
const lastOpen = result.lastIndexOf("<");
|
|
2079
|
+
if (lastOpen > result.lastIndexOf(">")) {
|
|
2080
|
+
result = result.substring(0, lastOpen);
|
|
2081
|
+
}
|
|
2082
|
+
// Don't cut an HTML entity in half
|
|
2083
|
+
result = result.replace(/&[a-zA-Z0-9#]*$/, "");
|
|
2084
|
+
// Close tags left open by the cut (input is sanitize-html output — well-formed)
|
|
2085
|
+
const stack = [];
|
|
2086
|
+
const tagRe = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)(?:\s[^>]*)?>/g;
|
|
2087
|
+
let match;
|
|
2088
|
+
while ((match = tagRe.exec(result))) {
|
|
2089
|
+
const [, slash, rawName] = match;
|
|
2090
|
+
const name = rawName.toLowerCase();
|
|
2091
|
+
if (VOID_TAGS.has(name)) {
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
2094
|
+
if (slash) {
|
|
2095
|
+
const index = stack.lastIndexOf(name);
|
|
2096
|
+
if (index !== -1) {
|
|
2097
|
+
stack.splice(index, 1);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
else {
|
|
2101
|
+
stack.push(name);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
2105
|
+
result += `</${stack[i]}>`;
|
|
2106
|
+
}
|
|
2107
|
+
return result;
|
|
2108
|
+
};
|
|
2028
2109
|
// Function to convert Markdown to Telegram-compatible HTML
|
|
2029
2110
|
function toTelegramHtml(markdown) {
|
|
2030
|
-
const maxChars =
|
|
2111
|
+
const maxChars = TELEGRAM_MAX_SYMBOLS - TELEGRAM_SYMBOL_RESERVED;
|
|
2031
2112
|
// Initialize markdown-it with options
|
|
2032
2113
|
const md = new MarkdownIt({
|
|
2033
2114
|
html: false,
|
|
@@ -2040,6 +2121,15 @@ function toTelegramHtml(markdown) {
|
|
|
2040
2121
|
markdown = markdown.replaceAll(typo.bullet, "•");
|
|
2041
2122
|
// Render Markdown to HTML
|
|
2042
2123
|
let telegramHtml = md.render(markdown);
|
|
2124
|
+
// List markers must be injected as TEXT inside <li> before sanitizing:
|
|
2125
|
+
// sanitize-html transformTags cannot insert text (a transform returning a
|
|
2126
|
+
// string like "- " is silently ignored), so list items were losing their
|
|
2127
|
+
// bullets/numbers entirely. Ordered lists get 1./2./..., unordered get "-".
|
|
2128
|
+
telegramHtml = telegramHtml.replace(/<ol>([\s\S]*?)<\/ol>/g, (_, inner) => {
|
|
2129
|
+
let counter = 0;
|
|
2130
|
+
return `<ol>${inner.replace(/<li>/g, () => `<li>${++counter}. `)}</ol>`;
|
|
2131
|
+
});
|
|
2132
|
+
telegramHtml = telegramHtml.replace(/<li>(?!\d+\. )/g, "<li>- ");
|
|
2043
2133
|
// Post-process with sanitize-html to ensure Telegram compatibility
|
|
2044
2134
|
telegramHtml = sanitizeHtml(telegramHtml, {
|
|
2045
2135
|
allowedTags: [
|
|
@@ -2071,10 +2161,9 @@ function toTelegramHtml(markdown) {
|
|
|
2071
2161
|
em: "i",
|
|
2072
2162
|
// Remove p tags, replace with newlines
|
|
2073
2163
|
p: () => "",
|
|
2074
|
-
//
|
|
2164
|
+
// ul/ol/li tags are dropped by allowedTags; their text markers are injected
|
|
2165
|
+
// BEFORE sanitizing (see above) — a string-returning transform can't do it
|
|
2075
2166
|
ul: () => "",
|
|
2076
|
-
li: () => "- ",
|
|
2077
|
-
// Emulate ordered lists with numbers
|
|
2078
2167
|
ol: () => "",
|
|
2079
2168
|
// Remove hr, replace with text-based separator
|
|
2080
2169
|
hr: () => "\n",
|
|
@@ -2095,7 +2184,7 @@ function toTelegramHtml(markdown) {
|
|
|
2095
2184
|
// Check Telegram message length limit (4096 characters)
|
|
2096
2185
|
if (telegramHtml.length > maxChars) {
|
|
2097
2186
|
console.warn("HTML exceeds Telegram's 4096-character limit. Truncating...");
|
|
2098
|
-
telegramHtml = telegramHtml
|
|
2187
|
+
telegramHtml = truncateTelegramHtml(telegramHtml, maxChars);
|
|
2099
2188
|
}
|
|
2100
2189
|
const telegramDom = new JSDOM(telegramHtml, {
|
|
2101
2190
|
contentType: "text/html",
|
|
@@ -2336,7 +2425,7 @@ class TelegramLogicService {
|
|
|
2336
2425
|
});
|
|
2337
2426
|
};
|
|
2338
2427
|
this.notifyRisk = async (event) => {
|
|
2339
|
-
this.loggerService.log("telegramLogicService
|
|
2428
|
+
this.loggerService.log("telegramLogicService notifyRisk", {
|
|
2340
2429
|
event,
|
|
2341
2430
|
});
|
|
2342
2431
|
const markdown = await this.telegramTemplateService.getRiskMarkdown(event);
|
|
@@ -2485,6 +2574,11 @@ class TelegramLogicService {
|
|
|
2485
2574
|
});
|
|
2486
2575
|
const unSync = listenSync(async (event) => {
|
|
2487
2576
|
if (event.action === "signal-open") {
|
|
2577
|
+
// type "schedule" is a resting-order PLACEMENT, not a position open:
|
|
2578
|
+
// the user already got the "scheduled" notification from listenSignal
|
|
2579
|
+
if (event.type !== "active") {
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2488
2582
|
await this.notifySignalOpen(event);
|
|
2489
2583
|
return;
|
|
2490
2584
|
}
|
|
@@ -2727,8 +2821,8 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
|
|
|
2727
2821
|
return true;
|
|
2728
2822
|
}
|
|
2729
2823
|
}
|
|
2730
|
-
catch {
|
|
2731
|
-
console.warn(`Module
|
|
2824
|
+
catch (error) {
|
|
2825
|
+
console.warn(`Module import failed filePath=${filePath} baseDir=${baseDir}`, error);
|
|
2732
2826
|
kill(-1);
|
|
2733
2827
|
return false;
|
|
2734
2828
|
}
|
|
@@ -2763,12 +2857,12 @@ registerPlugin("plugin-transform-modules-umd", pluginUMD);
|
|
|
2763
2857
|
class BabelService {
|
|
2764
2858
|
constructor() {
|
|
2765
2859
|
this.loggerService = inject(TYPES.loggerService);
|
|
2766
|
-
this.transpile = (code) => {
|
|
2767
|
-
this.loggerService.log("babelService transpile", { codeLen: code.length });
|
|
2860
|
+
this.transpile = (code, filename) => {
|
|
2861
|
+
this.loggerService.log("babelService transpile", { codeLen: code.length, filename });
|
|
2768
2862
|
const { values } = getArgs();
|
|
2769
2863
|
const result = transform(code, {
|
|
2770
|
-
filename
|
|
2771
|
-
presets: ["env", "typescript"],
|
|
2864
|
+
filename,
|
|
2865
|
+
presets: filename.endsWith(".tsx") ? ["env", "react", "typescript"] : ["env", "typescript"],
|
|
2772
2866
|
plugins: [
|
|
2773
2867
|
[
|
|
2774
2868
|
"plugin-transform-modules-umd",
|
|
@@ -2846,7 +2940,7 @@ const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require)
|
|
|
2846
2940
|
const module = { exports: {} };
|
|
2847
2941
|
const exports = module.exports;
|
|
2848
2942
|
try {
|
|
2849
|
-
eval(self.params.babel.transpile(code));
|
|
2943
|
+
eval(self.params.babel.transpile(code, path));
|
|
2850
2944
|
}
|
|
2851
2945
|
catch (error) {
|
|
2852
2946
|
console.log(`Error during transpilation error=\`${getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
@@ -2861,25 +2955,35 @@ const TRANSPILE_FN = memoize(([path]) => `${path}`, (path, code, self, require)
|
|
|
2861
2955
|
module,
|
|
2862
2956
|
};
|
|
2863
2957
|
});
|
|
2864
|
-
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2958
|
+
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen, errors) => {
|
|
2865
2959
|
{
|
|
2866
2960
|
return null;
|
|
2867
2961
|
}
|
|
2868
2962
|
};
|
|
2869
|
-
const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2963
|
+
const BABEL_ENTRY_FACTORY = (filePath, self, seen, errors) => {
|
|
2870
2964
|
try {
|
|
2871
2965
|
const resolvedPath = path.resolve(self.__dirname, filePath);
|
|
2872
2966
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
2873
2967
|
const child = self.fork(path.dirname(resolvedPath));
|
|
2874
|
-
const
|
|
2968
|
+
const transpiled = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
2969
|
+
if (!transpiled) {
|
|
2970
|
+
// TRANSPILE_FN already reported the error and scheduled kill(-1)
|
|
2971
|
+
return null;
|
|
2972
|
+
}
|
|
2973
|
+
const { module } = transpiled;
|
|
2875
2974
|
if (!USE_ESMODULE_DEFAULT) {
|
|
2876
|
-
return module.exports;
|
|
2975
|
+
return { exports: module.exports };
|
|
2877
2976
|
}
|
|
2878
|
-
return
|
|
2879
|
-
|
|
2880
|
-
|
|
2977
|
+
return {
|
|
2978
|
+
exports: "default" in module.exports
|
|
2979
|
+
? module.exports.default
|
|
2980
|
+
: module.exports,
|
|
2981
|
+
};
|
|
2881
2982
|
}
|
|
2882
|
-
catch {
|
|
2983
|
+
catch (error) {
|
|
2984
|
+
// Never swallow the cause: a nested "Circular dependency detected" (or any
|
|
2985
|
+
// real load error) must surface instead of a generic "Failed to load module".
|
|
2986
|
+
errors.push(error);
|
|
2883
2987
|
return null;
|
|
2884
2988
|
}
|
|
2885
2989
|
};
|
|
@@ -2924,16 +3028,18 @@ const GET_RESOLVED_EXT_FN = (filePath) => {
|
|
|
2924
3028
|
return filePath;
|
|
2925
3029
|
};
|
|
2926
3030
|
const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
3031
|
+
const errors = [];
|
|
2927
3032
|
{
|
|
2928
3033
|
let result = null;
|
|
2929
3034
|
if ((result = REQUIRE_ENTRY_FACTORY())) {
|
|
2930
|
-
return result;
|
|
3035
|
+
return result.exports;
|
|
2931
3036
|
}
|
|
2932
|
-
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen))) {
|
|
2933
|
-
return result;
|
|
3037
|
+
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen, errors))) {
|
|
3038
|
+
return result.exports;
|
|
2934
3039
|
}
|
|
2935
3040
|
}
|
|
2936
|
-
|
|
3041
|
+
const causes = errors.map(getErrorMessage).filter(Boolean).join("; ");
|
|
3042
|
+
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})${causes ? ` caused by: ${causes}` : ""}`);
|
|
2937
3043
|
};
|
|
2938
3044
|
const READ_IMPORT_PATHS_MAP_FN = singleshot((importPathsDir) => {
|
|
2939
3045
|
const entries = fs.readdirSync(importPathsDir, { withFileTypes: true });
|
|
@@ -3213,8 +3319,8 @@ const LOAD_CONFIG_CONFIG_FN = async (fileName, self) => {
|
|
|
3213
3319
|
return await self.loaderService.import(filePath, baseDir);
|
|
3214
3320
|
}
|
|
3215
3321
|
}
|
|
3216
|
-
catch {
|
|
3217
|
-
console.warn(`
|
|
3322
|
+
catch (error) {
|
|
3323
|
+
console.warn(`Config import failed filePath=${filePath} baseDir=${baseDir}`, error);
|
|
3218
3324
|
kill(-1);
|
|
3219
3325
|
return;
|
|
3220
3326
|
}
|
|
@@ -3388,10 +3494,10 @@ init();
|
|
|
3388
3494
|
|
|
3389
3495
|
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3390
3496
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3391
|
-
const HELP_TEXT$1 = `
|
|
3392
|
-
Example:
|
|
3393
|
-
|
|
3394
|
-
node ${ENTRY_PATH$1} --help
|
|
3497
|
+
const HELP_TEXT$1 = `
|
|
3498
|
+
Example:
|
|
3499
|
+
|
|
3500
|
+
node ${ENTRY_PATH$1} --help
|
|
3395
3501
|
`.trimStart();
|
|
3396
3502
|
const main$h = async () => {
|
|
3397
3503
|
if (!getEntry(import.meta.url)) {
|
|
@@ -3401,7 +3507,7 @@ const main$h = async () => {
|
|
|
3401
3507
|
if (MODES.some((mode) => values[mode])) {
|
|
3402
3508
|
return;
|
|
3403
3509
|
}
|
|
3404
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
3510
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n`);
|
|
3405
3511
|
process.stdout.write("\n");
|
|
3406
3512
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3407
3513
|
process.stdout.write("\n");
|
|
@@ -3553,7 +3659,7 @@ const main$d = async () => {
|
|
|
3553
3659
|
if (values.entry) {
|
|
3554
3660
|
return;
|
|
3555
3661
|
}
|
|
3556
|
-
cli.paperMainService.connect();
|
|
3662
|
+
await cli.paperMainService.connect();
|
|
3557
3663
|
listenGracefulShutdown$4();
|
|
3558
3664
|
};
|
|
3559
3665
|
main$d();
|
|
@@ -4107,6 +4213,7 @@ const main$6 = async () => {
|
|
|
4107
4213
|
isOk = true;
|
|
4108
4214
|
}
|
|
4109
4215
|
catch {
|
|
4216
|
+
isOk = false;
|
|
4110
4217
|
}
|
|
4111
4218
|
finally {
|
|
4112
4219
|
isOk && console.log(`Editor launched: http://localhost:${CC_WWWROOT_PORT}?pine=1`);
|
|
@@ -4500,204 +4607,207 @@ const main$2 = async () => {
|
|
|
4500
4607
|
main$2();
|
|
4501
4608
|
|
|
4502
4609
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
4503
|
-
const HELP_TEXT = `
|
|
4504
|
-
Usage:
|
|
4505
|
-
node index.mjs --<mode> [flags] [entry-point]
|
|
4506
|
-
|
|
4507
|
-
Modes:
|
|
4508
|
-
|
|
4509
|
-
--backtest <entry> Run strategy against historical candle data
|
|
4510
|
-
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4511
|
-
--paper <entry> Paper trading (live prices, no real orders)
|
|
4512
|
-
--live <entry> Live trading with real orders
|
|
4513
|
-
--main <entry> Run an entry point with prepared environment, no trading harness
|
|
4514
|
-
--pine <entry> Execute a local .pine indicator file
|
|
4515
|
-
--editor Open the Pine Script visual editor in the browser
|
|
4516
|
-
--dump Fetch and save raw OHLCV candles
|
|
4517
|
-
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4518
|
-
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4519
|
-
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4520
|
-
--init Scaffold a new project in the current directory
|
|
4521
|
-
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4522
|
-
--help Print this help message
|
|
4523
|
-
|
|
4524
|
-
Backtest flags:
|
|
4525
|
-
|
|
4526
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4527
|
-
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4528
|
-
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4529
|
-
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4530
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4531
|
-
--noCache Skip candle cache warming before the run
|
|
4532
|
-
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4533
|
-
--verbose Log every candle fetch to stdout
|
|
4534
|
-
--ui Start web dashboard at http://localhost:60050
|
|
4535
|
-
--telegram Send trade notifications to Telegram
|
|
4536
|
-
|
|
4537
|
-
Walker flags (--walker):
|
|
4538
|
-
|
|
4539
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4540
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4541
|
-
--noCache Skip candle cache warming before the run
|
|
4542
|
-
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4543
|
-
--verbose Log every candle fetch to stdout
|
|
4544
|
-
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4545
|
-
--json Save results as JSON to ./dump/<output>.json
|
|
4546
|
-
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4547
|
-
|
|
4548
|
-
Each positional argument is a strategy entry point.
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
--
|
|
4560
|
-
--
|
|
4561
|
-
--
|
|
4562
|
-
--
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
and
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
--
|
|
4587
|
-
--
|
|
4588
|
-
--
|
|
4589
|
-
--
|
|
4590
|
-
--
|
|
4591
|
-
--
|
|
4592
|
-
--
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
--
|
|
4602
|
-
--
|
|
4603
|
-
--
|
|
4604
|
-
--
|
|
4605
|
-
--
|
|
4606
|
-
--
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
--
|
|
4616
|
-
--
|
|
4617
|
-
--
|
|
4618
|
-
--
|
|
4619
|
-
--
|
|
4620
|
-
--
|
|
4621
|
-
--
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
modules/
|
|
4663
|
-
modules/
|
|
4664
|
-
modules/
|
|
4665
|
-
modules/
|
|
4666
|
-
modules/
|
|
4667
|
-
modules/
|
|
4668
|
-
modules/
|
|
4669
|
-
|
|
4670
|
-
--
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
node ${ENTRY_PATH} --
|
|
4687
|
-
node ${ENTRY_PATH} --
|
|
4688
|
-
node ${ENTRY_PATH} --
|
|
4689
|
-
node ${ENTRY_PATH} --
|
|
4690
|
-
node ${ENTRY_PATH} --
|
|
4691
|
-
node ${ENTRY_PATH} --
|
|
4692
|
-
node ${ENTRY_PATH} --
|
|
4693
|
-
node ${ENTRY_PATH} --
|
|
4694
|
-
node ${ENTRY_PATH} --
|
|
4695
|
-
node ${ENTRY_PATH} --
|
|
4696
|
-
node ${ENTRY_PATH} --
|
|
4697
|
-
node ${ENTRY_PATH} --
|
|
4698
|
-
node ${ENTRY_PATH} --
|
|
4699
|
-
node ${ENTRY_PATH} --
|
|
4700
|
-
node ${ENTRY_PATH} --
|
|
4610
|
+
const HELP_TEXT = `
|
|
4611
|
+
Usage:
|
|
4612
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
4613
|
+
|
|
4614
|
+
Modes:
|
|
4615
|
+
|
|
4616
|
+
--backtest <entry> Run strategy against historical candle data
|
|
4617
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4618
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
4619
|
+
--live <entry> Live trading with real orders
|
|
4620
|
+
--main <entry> Run an entry point with prepared environment, no trading harness
|
|
4621
|
+
--pine <entry> Execute a local .pine indicator file
|
|
4622
|
+
--editor Open the Pine Script visual editor in the browser
|
|
4623
|
+
--dump Fetch and save raw OHLCV candles
|
|
4624
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4625
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4626
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4627
|
+
--init Scaffold a new project in the current directory
|
|
4628
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4629
|
+
--help Print this help message
|
|
4630
|
+
|
|
4631
|
+
Backtest flags:
|
|
4632
|
+
|
|
4633
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4634
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4635
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4636
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4637
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 1h, 4h")
|
|
4638
|
+
--noCache Skip candle cache warming before the run
|
|
4639
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4640
|
+
--verbose Log every candle fetch to stdout
|
|
4641
|
+
--ui Start web dashboard at http://localhost:60050
|
|
4642
|
+
--telegram Send trade notifications to Telegram
|
|
4643
|
+
|
|
4644
|
+
Walker flags (--walker):
|
|
4645
|
+
|
|
4646
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4647
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 1h, 4h")
|
|
4648
|
+
--noCache Skip candle cache warming before the run
|
|
4649
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4650
|
+
--verbose Log every candle fetch to stdout
|
|
4651
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4652
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
4653
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4654
|
+
|
|
4655
|
+
Each positional argument is a strategy entry point. While an entry point is loaded
|
|
4656
|
+
(and again before its strategy runs) process.cwd() is switched to its directory and
|
|
4657
|
+
restored afterwards; .env is read from the launch directory first, then from the
|
|
4658
|
+
entry point directory (the latter wins).
|
|
4659
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4660
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4661
|
+
|
|
4662
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4663
|
+
|
|
4664
|
+
Paper / Live flags:
|
|
4665
|
+
|
|
4666
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4667
|
+
--strategy <string> Strategy name (default: first registered)
|
|
4668
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4669
|
+
--verbose Log every candle fetch to stdout
|
|
4670
|
+
--ui Start web dashboard
|
|
4671
|
+
--telegram Send Telegram notifications
|
|
4672
|
+
|
|
4673
|
+
Main flags (--main):
|
|
4674
|
+
|
|
4675
|
+
--noFlush Skip removing report/log/markdown/agent folders before the run
|
|
4676
|
+
|
|
4677
|
+
Prepares the runtime environment (loads .env, setup.config, loader.config and
|
|
4678
|
+
modules/main.module) and runs the single positional entry point — but does NOT
|
|
4679
|
+
start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
|
|
4680
|
+
Backtest/Live/Walker.background; the entry point decides what to run.
|
|
4681
|
+
|
|
4682
|
+
Exactly one positional entry point is required. process.cwd() is changed to the
|
|
4683
|
+
entry point directory and its local .env is loaded.
|
|
4684
|
+
|
|
4685
|
+
Backtest, Live and Walker runs started from userspace are still tracked: the run
|
|
4686
|
+
finishes automatically when one of them completes, Ctrl+C stops any active run,
|
|
4687
|
+
and a second Ctrl+C force-quits.
|
|
4688
|
+
|
|
4689
|
+
Module file ./modules/main.module is loaded automatically if it exists.
|
|
4690
|
+
|
|
4691
|
+
PineScript flags (--pine):
|
|
4692
|
+
|
|
4693
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4694
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4695
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
4696
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4697
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4698
|
+
--output <string> Output file base name without extension
|
|
4699
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4700
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4701
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4702
|
+
|
|
4703
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
4704
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4705
|
+
|
|
4706
|
+
Candle dump flags (--dump):
|
|
4707
|
+
|
|
4708
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4709
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4710
|
+
--limit <string> Number of candles (default: 250)
|
|
4711
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4712
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4713
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4714
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4715
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4716
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4717
|
+
|
|
4718
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4719
|
+
|
|
4720
|
+
PnL debug flags (--pnldebug):
|
|
4721
|
+
|
|
4722
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4723
|
+
--priceopen <number> Entry price (required)
|
|
4724
|
+
--direction <string> Position direction: long or short (default: long)
|
|
4725
|
+
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4726
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4727
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4728
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4729
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4730
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4731
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4732
|
+
|
|
4733
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4734
|
+
|
|
4735
|
+
Broker debug flags (--brokerdebug):
|
|
4736
|
+
|
|
4737
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4738
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4739
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4740
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4741
|
+
(default: signal-open)
|
|
4742
|
+
|
|
4743
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4744
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
4745
|
+
|
|
4746
|
+
Flush flags (--flush):
|
|
4747
|
+
|
|
4748
|
+
One or more positional entry points. For each entry point the following
|
|
4749
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
4750
|
+
|
|
4751
|
+
report log markdown agent
|
|
4752
|
+
|
|
4753
|
+
Init flags (--init):
|
|
4754
|
+
|
|
4755
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
4756
|
+
|
|
4757
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4758
|
+
|
|
4759
|
+
Docker flags (--docker):
|
|
4760
|
+
|
|
4761
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4762
|
+
|
|
4763
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4764
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4765
|
+
docker compose up to start the container.
|
|
4766
|
+
|
|
4767
|
+
Module hooks (loaded automatically by each mode):
|
|
4768
|
+
|
|
4769
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
4770
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
4771
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
4772
|
+
modules/live.module --live Broker adapter for live trading
|
|
4773
|
+
modules/main.module --main Environment setup for a custom entry point
|
|
4774
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4775
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4776
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
4777
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4778
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4779
|
+
|
|
4780
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
4781
|
+
|
|
4782
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4783
|
+
|
|
4784
|
+
Environment variables:
|
|
4785
|
+
|
|
4786
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4787
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4788
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4789
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4790
|
+
|
|
4791
|
+
Examples:
|
|
4792
|
+
|
|
4793
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4794
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4795
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4796
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4797
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4798
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4799
|
+
node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
|
|
4800
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4801
|
+
node ${ENTRY_PATH} --editor
|
|
4802
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4803
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4804
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4805
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4806
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4807
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4808
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4809
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4810
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4701
4811
|
`.trimStart();
|
|
4702
4812
|
const main$1 = async () => {
|
|
4703
4813
|
if (!getEntry(import.meta.url)) {
|
|
@@ -4707,7 +4817,7 @@ const main$1 = async () => {
|
|
|
4707
4817
|
if (!values.help) {
|
|
4708
4818
|
return;
|
|
4709
4819
|
}
|
|
4710
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4820
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n\n`);
|
|
4711
4821
|
process.stdout.write(HELP_TEXT);
|
|
4712
4822
|
process.exit(0);
|
|
4713
4823
|
};
|
|
@@ -4721,7 +4831,7 @@ const main = async () => {
|
|
|
4721
4831
|
if (!values.version) {
|
|
4722
4832
|
return;
|
|
4723
4833
|
}
|
|
4724
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4834
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n`);
|
|
4725
4835
|
process.exit(0);
|
|
4726
4836
|
};
|
|
4727
4837
|
main();
|