@backtest-kit/cli 14.1.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.cjs
CHANGED
|
@@ -110,17 +110,28 @@ const main$i = () => {
|
|
|
110
110
|
main$i();
|
|
111
111
|
|
|
112
112
|
const ERROR_HANDLER_INSTALLED = Symbol.for("error-handler-installed");
|
|
113
|
-
function dumpStackTrace() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
result
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
function dumpStackTrace(error) {
|
|
114
|
+
// Parse the ERROR's own stack — stackTrace.get() captured the handler's
|
|
115
|
+
// stack instead, so error.txt carried a useless trace of ErrorService itself.
|
|
116
|
+
if (!(error instanceof Error) || !error.stack) {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const trace = stackTrace__namespace.parse(error);
|
|
121
|
+
const result = [];
|
|
122
|
+
trace.forEach((callSite) => {
|
|
123
|
+
result.push(`File: ${callSite.getFileName()}`);
|
|
124
|
+
result.push(`Line: ${callSite.getLineNumber()}`);
|
|
125
|
+
result.push(`Function: ${callSite.getFunctionName() || "anonymous"}`);
|
|
126
|
+
result.push(`Method: ${callSite.getMethodName() || "none"}`);
|
|
127
|
+
result.push("---");
|
|
128
|
+
});
|
|
129
|
+
return functoolsKit.str.newline(result);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Unparseable/exotic stack format — dump the raw stack rather than nothing
|
|
133
|
+
return error.stack;
|
|
134
|
+
}
|
|
124
135
|
}
|
|
125
136
|
const timeNow = () => {
|
|
126
137
|
const d = new Date();
|
|
@@ -140,7 +151,7 @@ class ErrorService {
|
|
|
140
151
|
message: functoolsKit.getErrorMessage(error),
|
|
141
152
|
data: functoolsKit.errorData(error),
|
|
142
153
|
}, null, 2);
|
|
143
|
-
const trace = dumpStackTrace();
|
|
154
|
+
const trace = dumpStackTrace(error);
|
|
144
155
|
fs.appendFileSync("./error.txt", `${date}\n${msg}\n${trace}\n\n`);
|
|
145
156
|
};
|
|
146
157
|
this._listenForError = () => {
|
|
@@ -265,10 +276,6 @@ const SETUP_ADAPTER_FN = () => {
|
|
|
265
276
|
BacktestKit.StorageLive.usePersist();
|
|
266
277
|
BacktestKit.StorageBacktest.useMemory();
|
|
267
278
|
}
|
|
268
|
-
{
|
|
269
|
-
BacktestKit.RecentLive.usePersist();
|
|
270
|
-
BacktestKit.RecentBacktest.useMemory();
|
|
271
|
-
}
|
|
272
279
|
{
|
|
273
280
|
BacktestKit.NotificationLive.usePersist();
|
|
274
281
|
BacktestKit.NotificationBacktest.useMemory();
|
|
@@ -334,6 +341,7 @@ class SetupUtils {
|
|
|
334
341
|
BacktestKit.Markdown.disable();
|
|
335
342
|
BacktestKit.Report.disable();
|
|
336
343
|
BacktestKit.Dump.disable();
|
|
344
|
+
BacktestKit.State.disable();
|
|
337
345
|
BacktestKit.Memory.disable();
|
|
338
346
|
}
|
|
339
347
|
{
|
|
@@ -522,7 +530,11 @@ const ADD_EXCHANGE_FN = (self) => {
|
|
|
522
530
|
formatPrice: async (symbol, price) => {
|
|
523
531
|
const exchange = await getExchange();
|
|
524
532
|
const market = exchange.market(symbol);
|
|
525
|
-
|
|
533
|
+
// ccxt binance uses TICK_SIZE precision mode, so precision.price IS the
|
|
534
|
+
// tick size. limits.price.min (PRICE_FILTER minPrice) is only a fallback:
|
|
535
|
+
// it merely coincides with the tick on most Binance markets and is NOT
|
|
536
|
+
// the tick size by contract.
|
|
537
|
+
const tickSize = [market.precision?.price, market.limits?.price?.min].find((value) => typeof value === "number" && isFinite(value) && value > 0);
|
|
526
538
|
if (tickSize !== undefined) {
|
|
527
539
|
return BacktestKit.roundTicks(price, tickSize);
|
|
528
540
|
}
|
|
@@ -531,7 +543,10 @@ const ADD_EXCHANGE_FN = (self) => {
|
|
|
531
543
|
formatQuantity: async (symbol, quantity) => {
|
|
532
544
|
const exchange = await getExchange();
|
|
533
545
|
const market = exchange.market(symbol);
|
|
534
|
-
|
|
546
|
+
// Same rule as formatPrice: precision.amount is the LOT_SIZE step;
|
|
547
|
+
// limits.amount.min is minQty, which can exceed the step on some markets
|
|
548
|
+
// and would over-truncate quantities if preferred.
|
|
549
|
+
const stepSize = [market.precision?.amount, market.limits?.amount?.min].find((value) => typeof value === "number" && isFinite(value) && value > 0);
|
|
535
550
|
if (stepSize !== undefined) {
|
|
536
551
|
return BacktestKit.roundTicks(quantity, stepSize);
|
|
537
552
|
}
|
|
@@ -676,7 +691,7 @@ const getArgs = functoolsKit.singleshot(() => {
|
|
|
676
691
|
},
|
|
677
692
|
cacheInterval: {
|
|
678
693
|
type: "string",
|
|
679
|
-
default: "1m, 15m, 30m, 4h",
|
|
694
|
+
default: "1m, 15m, 30m, 1h, 4h",
|
|
680
695
|
},
|
|
681
696
|
// pinescript entry
|
|
682
697
|
editor: {
|
|
@@ -1008,6 +1023,8 @@ const notifyKill = functoolsKit.singleshot(() => {
|
|
|
1008
1023
|
process.on("SIGINT", () => kill());
|
|
1009
1024
|
});
|
|
1010
1025
|
|
|
1026
|
+
// Must match the getArgs --cacheInterval default ("1m, 15m, 30m, 1h, 4h"):
|
|
1027
|
+
// this fallback is only reachable with an explicit --cacheInterval ""
|
|
1011
1028
|
const DEFAULT_CACHE_LIST$1 = ["1m", "15m", "30m", "1h", "4h"];
|
|
1012
1029
|
const GET_CACHE_INTERVAL_LIST_FN$1 = () => {
|
|
1013
1030
|
const { values } = getArgs();
|
|
@@ -1150,6 +1167,8 @@ var WalkerName;
|
|
|
1150
1167
|
})(WalkerName || (WalkerName = {}));
|
|
1151
1168
|
var WalkerName$1 = WalkerName;
|
|
1152
1169
|
|
|
1170
|
+
// Must match the getArgs --cacheInterval default ("1m, 15m, 30m, 1h, 4h"):
|
|
1171
|
+
// this fallback is only reachable with an explicit --cacheInterval ""
|
|
1153
1172
|
const DEFAULT_CACHE_LIST = ["1m", "15m", "30m", "1h", "4h"];
|
|
1154
1173
|
const GET_CACHE_INTERVAL_LIST_FN = () => {
|
|
1155
1174
|
const { values } = getArgs();
|
|
@@ -1660,7 +1679,7 @@ const MAP_SYMBOL_CONFIG_FN = (config) => {
|
|
|
1660
1679
|
symbol,
|
|
1661
1680
|
icon,
|
|
1662
1681
|
logo: logo ?? icon,
|
|
1663
|
-
priority: priority ?? idx,
|
|
1682
|
+
priority: priority ?? -idx,
|
|
1664
1683
|
displayName: displayName ?? symbol,
|
|
1665
1684
|
index: idx,
|
|
1666
1685
|
...other,
|
|
@@ -1824,20 +1843,33 @@ class QuickchartApiService {
|
|
|
1824
1843
|
const HEALTH_CHECK_DELAY = 60000;
|
|
1825
1844
|
let _is_stopped = false;
|
|
1826
1845
|
const handleHealthCheck = functoolsKit.singleshot(async (bot) => {
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1846
|
+
// Первый чек выполняется синхронно с запуском: ошибка уходит вызывающему
|
|
1847
|
+
// getTelegram (бот не стартует, если Telegram недоступен сразу).
|
|
1848
|
+
try {
|
|
1849
|
+
await bot.telegram.getMe();
|
|
1850
|
+
}
|
|
1851
|
+
catch {
|
|
1852
|
+
console.log("Bot is offline");
|
|
1853
|
+
throw new Error("Telegram goes offline");
|
|
1854
|
+
}
|
|
1855
|
+
// Периодические чеки: раньше throw внутри setTimeout-цикла становился
|
|
1856
|
+
// unhandledRejection (никем не await-ился) и ронял процесс неконтролируемо,
|
|
1857
|
+
// оставляя дочерние процессы (UI-сервер) живыми. Теперь — управляемый kill.
|
|
1858
|
+
const schedule = () => {
|
|
1859
|
+
setTimeout(() => {
|
|
1860
|
+
if (_is_stopped) {
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
bot.telegram
|
|
1864
|
+
.getMe()
|
|
1865
|
+
.then(schedule)
|
|
1866
|
+
.catch(() => {
|
|
1867
|
+
console.log("Bot is offline");
|
|
1868
|
+
kill(-1);
|
|
1869
|
+
});
|
|
1870
|
+
}, HEALTH_CHECK_DELAY);
|
|
1839
1871
|
};
|
|
1840
|
-
|
|
1872
|
+
schedule();
|
|
1841
1873
|
});
|
|
1842
1874
|
const getTelegram = functoolsKit.singleshot(async () => {
|
|
1843
1875
|
if (_is_stopped) {
|
|
@@ -1936,8 +1968,11 @@ const publishInternal = functoolsKit.queued(async ({ channel, msg, images = [],
|
|
|
1936
1968
|
onScheduled();
|
|
1937
1969
|
let isOk = true;
|
|
1938
1970
|
try {
|
|
1971
|
+
// Must persist across flood retries: declared inside `execute` it was reset
|
|
1972
|
+
// on every recursive retry, re-sending the media group (duplicate channel
|
|
1973
|
+
// posts) when the flood error hit the follow-up sendMessage.
|
|
1974
|
+
let isImagesPublished = false;
|
|
1939
1975
|
const execute = async (retry = 0) => {
|
|
1940
|
-
let isImagesPublished = false;
|
|
1941
1976
|
try {
|
|
1942
1977
|
if (images?.length) {
|
|
1943
1978
|
console.log("Bot fetching images");
|
|
@@ -2038,21 +2073,67 @@ class TelegramApiService {
|
|
|
2038
2073
|
task.finally(() => {
|
|
2039
2074
|
TIMEOUT_COUNTER -= 1;
|
|
2040
2075
|
});
|
|
2076
|
+
// The watchdog must fire HERE: when every publish is stuck, this branch is
|
|
2077
|
+
// the only one executing — checking after the early return made the kill
|
|
2078
|
+
// reachable only once a publish suddenly succeeded (problem already gone).
|
|
2079
|
+
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
2080
|
+
setTimeout(() => kill(-1), 5000);
|
|
2081
|
+
}
|
|
2041
2082
|
return "Message scheduled for publication";
|
|
2042
2083
|
}
|
|
2043
|
-
if (TIMEOUT_COUNTER > MAX_TIMEOUT_COUNT) {
|
|
2044
|
-
setTimeout(() => kill(-1), 5000);
|
|
2045
|
-
}
|
|
2046
2084
|
return "Message published successfully";
|
|
2047
2085
|
};
|
|
2048
2086
|
}
|
|
2049
2087
|
}
|
|
2050
2088
|
|
|
2051
|
-
const
|
|
2089
|
+
const TELEGRAM_MAX_SYMBOLS = 4090;
|
|
2052
2090
|
const TELEGRAM_SYMBOL_RESERVED = 96;
|
|
2091
|
+
/** Void tags never get a closing counterpart when balancing truncated HTML. */
|
|
2092
|
+
const VOID_TAGS = new Set(["br"]);
|
|
2093
|
+
/**
|
|
2094
|
+
* Truncates Telegram HTML without breaking it: a blunt substring could cut a
|
|
2095
|
+
* tag or entity in half and leave unbalanced tags — Telegram then rejects the
|
|
2096
|
+
* WHOLE message ("can't parse entities"), so the notification would be lost
|
|
2097
|
+
* instead of shortened. Closing tags appended here fit into
|
|
2098
|
+
* TELEGRAM_SYMBOL_RESERVED headroom.
|
|
2099
|
+
*/
|
|
2100
|
+
const truncateTelegramHtml = (html, maxChars) => {
|
|
2101
|
+
let result = html.substring(0, maxChars);
|
|
2102
|
+
// Don't cut a tag in half
|
|
2103
|
+
const lastOpen = result.lastIndexOf("<");
|
|
2104
|
+
if (lastOpen > result.lastIndexOf(">")) {
|
|
2105
|
+
result = result.substring(0, lastOpen);
|
|
2106
|
+
}
|
|
2107
|
+
// Don't cut an HTML entity in half
|
|
2108
|
+
result = result.replace(/&[a-zA-Z0-9#]*$/, "");
|
|
2109
|
+
// Close tags left open by the cut (input is sanitize-html output — well-formed)
|
|
2110
|
+
const stack = [];
|
|
2111
|
+
const tagRe = /<(\/?)([a-zA-Z][a-zA-Z0-9-]*)(?:\s[^>]*)?>/g;
|
|
2112
|
+
let match;
|
|
2113
|
+
while ((match = tagRe.exec(result))) {
|
|
2114
|
+
const [, slash, rawName] = match;
|
|
2115
|
+
const name = rawName.toLowerCase();
|
|
2116
|
+
if (VOID_TAGS.has(name)) {
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
if (slash) {
|
|
2120
|
+
const index = stack.lastIndexOf(name);
|
|
2121
|
+
if (index !== -1) {
|
|
2122
|
+
stack.splice(index, 1);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
else {
|
|
2126
|
+
stack.push(name);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
2130
|
+
result += `</${stack[i]}>`;
|
|
2131
|
+
}
|
|
2132
|
+
return result;
|
|
2133
|
+
};
|
|
2053
2134
|
// Function to convert Markdown to Telegram-compatible HTML
|
|
2054
2135
|
function toTelegramHtml(markdown) {
|
|
2055
|
-
const maxChars =
|
|
2136
|
+
const maxChars = TELEGRAM_MAX_SYMBOLS - TELEGRAM_SYMBOL_RESERVED;
|
|
2056
2137
|
// Initialize markdown-it with options
|
|
2057
2138
|
const md = new MarkdownIt({
|
|
2058
2139
|
html: false,
|
|
@@ -2065,6 +2146,15 @@ function toTelegramHtml(markdown) {
|
|
|
2065
2146
|
markdown = markdown.replaceAll(functoolsKit.typo.bullet, "•");
|
|
2066
2147
|
// Render Markdown to HTML
|
|
2067
2148
|
let telegramHtml = md.render(markdown);
|
|
2149
|
+
// List markers must be injected as TEXT inside <li> before sanitizing:
|
|
2150
|
+
// sanitize-html transformTags cannot insert text (a transform returning a
|
|
2151
|
+
// string like "- " is silently ignored), so list items were losing their
|
|
2152
|
+
// bullets/numbers entirely. Ordered lists get 1./2./..., unordered get "-".
|
|
2153
|
+
telegramHtml = telegramHtml.replace(/<ol>([\s\S]*?)<\/ol>/g, (_, inner) => {
|
|
2154
|
+
let counter = 0;
|
|
2155
|
+
return `<ol>${inner.replace(/<li>/g, () => `<li>${++counter}. `)}</ol>`;
|
|
2156
|
+
});
|
|
2157
|
+
telegramHtml = telegramHtml.replace(/<li>(?!\d+\. )/g, "<li>- ");
|
|
2068
2158
|
// Post-process with sanitize-html to ensure Telegram compatibility
|
|
2069
2159
|
telegramHtml = sanitizeHtml(telegramHtml, {
|
|
2070
2160
|
allowedTags: [
|
|
@@ -2096,10 +2186,9 @@ function toTelegramHtml(markdown) {
|
|
|
2096
2186
|
em: "i",
|
|
2097
2187
|
// Remove p tags, replace with newlines
|
|
2098
2188
|
p: () => "",
|
|
2099
|
-
//
|
|
2189
|
+
// ul/ol/li tags are dropped by allowedTags; their text markers are injected
|
|
2190
|
+
// BEFORE sanitizing (see above) — a string-returning transform can't do it
|
|
2100
2191
|
ul: () => "",
|
|
2101
|
-
li: () => "- ",
|
|
2102
|
-
// Emulate ordered lists with numbers
|
|
2103
2192
|
ol: () => "",
|
|
2104
2193
|
// Remove hr, replace with text-based separator
|
|
2105
2194
|
hr: () => "\n",
|
|
@@ -2120,7 +2209,7 @@ function toTelegramHtml(markdown) {
|
|
|
2120
2209
|
// Check Telegram message length limit (4096 characters)
|
|
2121
2210
|
if (telegramHtml.length > maxChars) {
|
|
2122
2211
|
console.warn("HTML exceeds Telegram's 4096-character limit. Truncating...");
|
|
2123
|
-
telegramHtml = telegramHtml
|
|
2212
|
+
telegramHtml = truncateTelegramHtml(telegramHtml, maxChars);
|
|
2124
2213
|
}
|
|
2125
2214
|
const telegramDom = new jsdom.JSDOM(telegramHtml, {
|
|
2126
2215
|
contentType: "text/html",
|
|
@@ -2361,7 +2450,7 @@ class TelegramLogicService {
|
|
|
2361
2450
|
});
|
|
2362
2451
|
};
|
|
2363
2452
|
this.notifyRisk = async (event) => {
|
|
2364
|
-
this.loggerService.log("telegramLogicService
|
|
2453
|
+
this.loggerService.log("telegramLogicService notifyRisk", {
|
|
2365
2454
|
event,
|
|
2366
2455
|
});
|
|
2367
2456
|
const markdown = await this.telegramTemplateService.getRiskMarkdown(event);
|
|
@@ -2510,6 +2599,11 @@ class TelegramLogicService {
|
|
|
2510
2599
|
});
|
|
2511
2600
|
const unSync = BacktestKit.listenSync(async (event) => {
|
|
2512
2601
|
if (event.action === "signal-open") {
|
|
2602
|
+
// type "schedule" is a resting-order PLACEMENT, not a position open:
|
|
2603
|
+
// the user already got the "scheduled" notification from listenSignal
|
|
2604
|
+
if (event.type !== "active") {
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2513
2607
|
await this.notifySignalOpen(event);
|
|
2514
2608
|
return;
|
|
2515
2609
|
}
|
|
@@ -2752,8 +2846,8 @@ const LOAD_MODULE_MODULE_FN = async (fileName, self) => {
|
|
|
2752
2846
|
return true;
|
|
2753
2847
|
}
|
|
2754
2848
|
}
|
|
2755
|
-
catch {
|
|
2756
|
-
console.warn(`Module
|
|
2849
|
+
catch (error) {
|
|
2850
|
+
console.warn(`Module import failed filePath=${filePath} baseDir=${baseDir}`, error);
|
|
2757
2851
|
kill(-1);
|
|
2758
2852
|
return false;
|
|
2759
2853
|
}
|
|
@@ -2788,12 +2882,12 @@ standalone.registerPlugin("plugin-transform-modules-umd", pluginUMD);
|
|
|
2788
2882
|
class BabelService {
|
|
2789
2883
|
constructor() {
|
|
2790
2884
|
this.loggerService = inject(TYPES.loggerService);
|
|
2791
|
-
this.transpile = (code) => {
|
|
2792
|
-
this.loggerService.log("babelService transpile", { codeLen: code.length });
|
|
2885
|
+
this.transpile = (code, filename) => {
|
|
2886
|
+
this.loggerService.log("babelService transpile", { codeLen: code.length, filename });
|
|
2793
2887
|
const { values } = getArgs();
|
|
2794
2888
|
const result = standalone.transform(code, {
|
|
2795
|
-
filename
|
|
2796
|
-
presets: ["env", "typescript"],
|
|
2889
|
+
filename,
|
|
2890
|
+
presets: filename.endsWith(".tsx") ? ["env", "react", "typescript"] : ["env", "typescript"],
|
|
2797
2891
|
plugins: [
|
|
2798
2892
|
[
|
|
2799
2893
|
"plugin-transform-modules-umd",
|
|
@@ -2871,7 +2965,7 @@ const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, se
|
|
|
2871
2965
|
const module = { exports: {} };
|
|
2872
2966
|
const exports = module.exports;
|
|
2873
2967
|
try {
|
|
2874
|
-
eval(self.params.babel.transpile(code));
|
|
2968
|
+
eval(self.params.babel.transpile(code, path));
|
|
2875
2969
|
}
|
|
2876
2970
|
catch (error) {
|
|
2877
2971
|
console.log(`Error during transpilation error=\`${functoolsKit.getErrorMessage(error)}\` path=\`${path}\` __filename=\`${__filename}\` __dirname=\`${__dirname}\``);
|
|
@@ -2886,29 +2980,42 @@ const TRANSPILE_FN = functoolsKit.memoize(([path]) => `${path}`, (path, code, se
|
|
|
2886
2980
|
module,
|
|
2887
2981
|
};
|
|
2888
2982
|
});
|
|
2889
|
-
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2983
|
+
const REQUIRE_ENTRY_FACTORY = (filePath, self, seen, errors) => {
|
|
2890
2984
|
const baseRequire = CREATE_BASE_REQUIRE_FN(self, seen);
|
|
2891
2985
|
try {
|
|
2892
|
-
return baseRequire(filePath);
|
|
2986
|
+
return { exports: baseRequire(filePath) };
|
|
2893
2987
|
}
|
|
2894
|
-
catch {
|
|
2988
|
+
catch (error) {
|
|
2989
|
+
// Expected for TS/TSX sources (native require can't parse them) — the Babel
|
|
2990
|
+
// path below handles those. Keep the error so a total failure reports why.
|
|
2991
|
+
errors.push(error);
|
|
2895
2992
|
return null;
|
|
2896
2993
|
}
|
|
2897
2994
|
};
|
|
2898
|
-
const BABEL_ENTRY_FACTORY = (filePath, self, seen) => {
|
|
2995
|
+
const BABEL_ENTRY_FACTORY = (filePath, self, seen, errors) => {
|
|
2899
2996
|
try {
|
|
2900
2997
|
const resolvedPath = path.resolve(self.__dirname, filePath);
|
|
2901
2998
|
const code = fs.readFileSync(resolvedPath, "utf-8");
|
|
2902
2999
|
const child = self.fork(path.dirname(resolvedPath));
|
|
2903
|
-
const
|
|
3000
|
+
const transpiled = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
|
|
3001
|
+
if (!transpiled) {
|
|
3002
|
+
// TRANSPILE_FN already reported the error and scheduled kill(-1)
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
const { module } = transpiled;
|
|
2904
3006
|
if (!USE_ESMODULE_DEFAULT) {
|
|
2905
|
-
return module.exports;
|
|
3007
|
+
return { exports: module.exports };
|
|
2906
3008
|
}
|
|
2907
|
-
return
|
|
2908
|
-
|
|
2909
|
-
|
|
3009
|
+
return {
|
|
3010
|
+
exports: "default" in module.exports
|
|
3011
|
+
? module.exports.default
|
|
3012
|
+
: module.exports,
|
|
3013
|
+
};
|
|
2910
3014
|
}
|
|
2911
|
-
catch {
|
|
3015
|
+
catch (error) {
|
|
3016
|
+
// Never swallow the cause: a nested "Circular dependency detected" (or any
|
|
3017
|
+
// real load error) must surface instead of a generic "Failed to load module".
|
|
3018
|
+
errors.push(error);
|
|
2912
3019
|
return null;
|
|
2913
3020
|
}
|
|
2914
3021
|
};
|
|
@@ -2953,16 +3060,18 @@ const GET_RESOLVED_EXT_FN = (filePath) => {
|
|
|
2953
3060
|
return filePath;
|
|
2954
3061
|
};
|
|
2955
3062
|
const ENTRY_FACTORY = (filePath, self, seen) => {
|
|
3063
|
+
const errors = [];
|
|
2956
3064
|
{
|
|
2957
3065
|
let result = null;
|
|
2958
|
-
if ((result = REQUIRE_ENTRY_FACTORY(filePath, self, seen))) {
|
|
2959
|
-
return result;
|
|
3066
|
+
if ((result = REQUIRE_ENTRY_FACTORY(filePath, self, seen, errors))) {
|
|
3067
|
+
return result.exports;
|
|
2960
3068
|
}
|
|
2961
|
-
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen))) {
|
|
2962
|
-
return result;
|
|
3069
|
+
if ((result = BABEL_ENTRY_FACTORY(filePath, self, seen, errors))) {
|
|
3070
|
+
return result.exports;
|
|
2963
3071
|
}
|
|
2964
3072
|
}
|
|
2965
|
-
|
|
3073
|
+
const causes = errors.map(functoolsKit.getErrorMessage).filter(Boolean).join("; ");
|
|
3074
|
+
throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})${causes ? ` caused by: ${causes}` : ""}`);
|
|
2966
3075
|
};
|
|
2967
3076
|
const READ_IMPORT_PATHS_MAP_FN = functoolsKit.singleshot((importPathsDir) => {
|
|
2968
3077
|
const entries = fs.readdirSync(importPathsDir, { withFileTypes: true });
|
|
@@ -3242,8 +3351,8 @@ const LOAD_CONFIG_CONFIG_FN = async (fileName, self) => {
|
|
|
3242
3351
|
return await self.loaderService.import(filePath, baseDir);
|
|
3243
3352
|
}
|
|
3244
3353
|
}
|
|
3245
|
-
catch {
|
|
3246
|
-
console.warn(`
|
|
3354
|
+
catch (error) {
|
|
3355
|
+
console.warn(`Config import failed filePath=${filePath} baseDir=${baseDir}`, error);
|
|
3247
3356
|
kill(-1);
|
|
3248
3357
|
return;
|
|
3249
3358
|
}
|
|
@@ -3417,10 +3526,10 @@ init();
|
|
|
3417
3526
|
|
|
3418
3527
|
const MODES = ["backtest", "walker", "paper", "live", "main", "pine", "editor", "dump", "pnldebug", "brokerdebug", "flush", "init", "docker", "help", "version"];
|
|
3419
3528
|
const ENTRY_PATH$1 = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
3420
|
-
const HELP_TEXT$1 = `
|
|
3421
|
-
Example:
|
|
3422
|
-
|
|
3423
|
-
node ${ENTRY_PATH$1} --help
|
|
3529
|
+
const HELP_TEXT$1 = `
|
|
3530
|
+
Example:
|
|
3531
|
+
|
|
3532
|
+
node ${ENTRY_PATH$1} --help
|
|
3424
3533
|
`.trimStart();
|
|
3425
3534
|
const main$h = async () => {
|
|
3426
3535
|
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)))) {
|
|
@@ -3430,7 +3539,7 @@ const main$h = async () => {
|
|
|
3430
3539
|
if (MODES.some((mode) => values[mode])) {
|
|
3431
3540
|
return;
|
|
3432
3541
|
}
|
|
3433
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
3542
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n`);
|
|
3434
3543
|
process.stdout.write("\n");
|
|
3435
3544
|
process.stdout.write(`Run with --help to see available commands.\n`);
|
|
3436
3545
|
process.stdout.write("\n");
|
|
@@ -3582,7 +3691,7 @@ const main$d = async () => {
|
|
|
3582
3691
|
if (values.entry) {
|
|
3583
3692
|
return;
|
|
3584
3693
|
}
|
|
3585
|
-
cli.paperMainService.connect();
|
|
3694
|
+
await cli.paperMainService.connect();
|
|
3586
3695
|
listenGracefulShutdown$4();
|
|
3587
3696
|
};
|
|
3588
3697
|
main$d();
|
|
@@ -4136,6 +4245,7 @@ const main$6 = async () => {
|
|
|
4136
4245
|
isOk = true;
|
|
4137
4246
|
}
|
|
4138
4247
|
catch {
|
|
4248
|
+
isOk = false;
|
|
4139
4249
|
}
|
|
4140
4250
|
finally {
|
|
4141
4251
|
isOk && console.log(`Editor launched: http://localhost:${CC_WWWROOT_PORT}?pine=1`);
|
|
@@ -4529,204 +4639,207 @@ const main$2 = async () => {
|
|
|
4529
4639
|
main$2();
|
|
4530
4640
|
|
|
4531
4641
|
const ENTRY_PATH = "./node_modules/@backtest-kit/cli/build/index.mjs";
|
|
4532
|
-
const HELP_TEXT = `
|
|
4533
|
-
Usage:
|
|
4534
|
-
node index.mjs --<mode> [flags] [entry-point]
|
|
4535
|
-
|
|
4536
|
-
Modes:
|
|
4537
|
-
|
|
4538
|
-
--backtest <entry> Run strategy against historical candle data
|
|
4539
|
-
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4540
|
-
--paper <entry> Paper trading (live prices, no real orders)
|
|
4541
|
-
--live <entry> Live trading with real orders
|
|
4542
|
-
--main <entry> Run an entry point with prepared environment, no trading harness
|
|
4543
|
-
--pine <entry> Execute a local .pine indicator file
|
|
4544
|
-
--editor Open the Pine Script visual editor in the browser
|
|
4545
|
-
--dump Fetch and save raw OHLCV candles
|
|
4546
|
-
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4547
|
-
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4548
|
-
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4549
|
-
--init Scaffold a new project in the current directory
|
|
4550
|
-
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4551
|
-
--help Print this help message
|
|
4552
|
-
|
|
4553
|
-
Backtest flags:
|
|
4554
|
-
|
|
4555
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4556
|
-
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4557
|
-
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4558
|
-
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4559
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4560
|
-
--noCache Skip candle cache warming before the run
|
|
4561
|
-
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4562
|
-
--verbose Log every candle fetch to stdout
|
|
4563
|
-
--ui Start web dashboard at http://localhost:60050
|
|
4564
|
-
--telegram Send trade notifications to Telegram
|
|
4565
|
-
|
|
4566
|
-
Walker flags (--walker):
|
|
4567
|
-
|
|
4568
|
-
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4569
|
-
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 4h")
|
|
4570
|
-
--noCache Skip candle cache warming before the run
|
|
4571
|
-
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4572
|
-
--verbose Log every candle fetch to stdout
|
|
4573
|
-
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4574
|
-
--json Save results as JSON to ./dump/<output>.json
|
|
4575
|
-
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4576
|
-
|
|
4577
|
-
Each positional argument is a strategy entry point.
|
|
4578
|
-
|
|
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
|
-
and
|
|
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
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
modules/
|
|
4692
|
-
modules/
|
|
4693
|
-
modules/
|
|
4694
|
-
modules/
|
|
4695
|
-
modules/
|
|
4696
|
-
modules/
|
|
4697
|
-
modules/
|
|
4698
|
-
|
|
4699
|
-
--
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
node ${ENTRY_PATH} --
|
|
4716
|
-
node ${ENTRY_PATH} --
|
|
4717
|
-
node ${ENTRY_PATH} --
|
|
4718
|
-
node ${ENTRY_PATH} --
|
|
4719
|
-
node ${ENTRY_PATH} --
|
|
4720
|
-
node ${ENTRY_PATH} --
|
|
4721
|
-
node ${ENTRY_PATH} --
|
|
4722
|
-
node ${ENTRY_PATH} --
|
|
4723
|
-
node ${ENTRY_PATH} --
|
|
4724
|
-
node ${ENTRY_PATH} --
|
|
4725
|
-
node ${ENTRY_PATH} --
|
|
4726
|
-
node ${ENTRY_PATH} --
|
|
4727
|
-
node ${ENTRY_PATH} --
|
|
4728
|
-
node ${ENTRY_PATH} --
|
|
4729
|
-
node ${ENTRY_PATH} --
|
|
4642
|
+
const HELP_TEXT = `
|
|
4643
|
+
Usage:
|
|
4644
|
+
node index.mjs --<mode> [flags] [entry-point]
|
|
4645
|
+
|
|
4646
|
+
Modes:
|
|
4647
|
+
|
|
4648
|
+
--backtest <entry> Run strategy against historical candle data
|
|
4649
|
+
--walker <entry...> Run Walker A/B strategy comparison across multiple strategies
|
|
4650
|
+
--paper <entry> Paper trading (live prices, no real orders)
|
|
4651
|
+
--live <entry> Live trading with real orders
|
|
4652
|
+
--main <entry> Run an entry point with prepared environment, no trading harness
|
|
4653
|
+
--pine <entry> Execute a local .pine indicator file
|
|
4654
|
+
--editor Open the Pine Script visual editor in the browser
|
|
4655
|
+
--dump Fetch and save raw OHLCV candles
|
|
4656
|
+
--pnldebug Simulate PnL per minute for a given entry price and direction
|
|
4657
|
+
--brokerdebug Fire a single broker commit against the live broker adapter
|
|
4658
|
+
--flush <entry...> Delete report/log/markdown/agent folders from strategy dump dir
|
|
4659
|
+
--init Scaffold a new project in the current directory
|
|
4660
|
+
--docker Scaffold a Docker workspace for running strategies in a container
|
|
4661
|
+
--help Print this help message
|
|
4662
|
+
|
|
4663
|
+
Backtest flags:
|
|
4664
|
+
|
|
4665
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4666
|
+
--strategy <string> Strategy name from addStrategySchema (default: first registered)
|
|
4667
|
+
--exchange <string> Exchange name from addExchangeSchema (default: first registered)
|
|
4668
|
+
--frame <string> Frame name from addFrameSchema (default: first registered)
|
|
4669
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 1h, 4h")
|
|
4670
|
+
--noCache Skip candle cache warming before the run
|
|
4671
|
+
--noFlush Skip removing report/log/markdown/agent folders before backtest run
|
|
4672
|
+
--verbose Log every candle fetch to stdout
|
|
4673
|
+
--ui Start web dashboard at http://localhost:60050
|
|
4674
|
+
--telegram Send trade notifications to Telegram
|
|
4675
|
+
|
|
4676
|
+
Walker flags (--walker):
|
|
4677
|
+
|
|
4678
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4679
|
+
--cacheInterval <string> Comma-separated intervals to pre-cache (default: "1m, 15m, 30m, 1h, 4h")
|
|
4680
|
+
--noCache Skip candle cache warming before the run
|
|
4681
|
+
--noFlush Skip removing report/log/markdown/agent folders before walker run
|
|
4682
|
+
--verbose Log every candle fetch to stdout
|
|
4683
|
+
--output <string> Output file base name (default: walker_{SYMBOL}_{TIMESTAMP})
|
|
4684
|
+
--json Save results as JSON to ./dump/<output>.json
|
|
4685
|
+
--markdown Save report as Markdown to ./dump/<output>.md
|
|
4686
|
+
|
|
4687
|
+
Each positional argument is a strategy entry point. While an entry point is loaded
|
|
4688
|
+
(and again before its strategy runs) process.cwd() is switched to its directory and
|
|
4689
|
+
restored afterwards; .env is read from the launch directory first, then from the
|
|
4690
|
+
entry point directory (the latter wins).
|
|
4691
|
+
addWalkerSchema is called automatically using the registered exchange and frame.
|
|
4692
|
+
After comparison completes the report is printed to stdout (or saved if --json/--markdown).
|
|
4693
|
+
|
|
4694
|
+
Module file ./modules/walker.module is loaded automatically if it exists.
|
|
4695
|
+
|
|
4696
|
+
Paper / Live flags:
|
|
4697
|
+
|
|
4698
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4699
|
+
--strategy <string> Strategy name (default: first registered)
|
|
4700
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4701
|
+
--verbose Log every candle fetch to stdout
|
|
4702
|
+
--ui Start web dashboard
|
|
4703
|
+
--telegram Send Telegram notifications
|
|
4704
|
+
|
|
4705
|
+
Main flags (--main):
|
|
4706
|
+
|
|
4707
|
+
--noFlush Skip removing report/log/markdown/agent folders before the run
|
|
4708
|
+
|
|
4709
|
+
Prepares the runtime environment (loads .env, setup.config, loader.config and
|
|
4710
|
+
modules/main.module) and runs the single positional entry point — but does NOT
|
|
4711
|
+
start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
|
|
4712
|
+
Backtest/Live/Walker.background; the entry point decides what to run.
|
|
4713
|
+
|
|
4714
|
+
Exactly one positional entry point is required. process.cwd() is changed to the
|
|
4715
|
+
entry point directory and its local .env is loaded.
|
|
4716
|
+
|
|
4717
|
+
Backtest, Live and Walker runs started from userspace are still tracked: the run
|
|
4718
|
+
finishes automatically when one of them completes, Ctrl+C stops any active run,
|
|
4719
|
+
and a second Ctrl+C force-quits.
|
|
4720
|
+
|
|
4721
|
+
Module file ./modules/main.module is loaded automatically if it exists.
|
|
4722
|
+
|
|
4723
|
+
PineScript flags (--pine):
|
|
4724
|
+
|
|
4725
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4726
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4727
|
+
--limit <string> Number of candles to fetch (default: 250)
|
|
4728
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4729
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4730
|
+
--output <string> Output file base name without extension
|
|
4731
|
+
--json Save output as JSON array to <pine-dir>/dump/<output>.json
|
|
4732
|
+
--jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
|
|
4733
|
+
--markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
|
|
4734
|
+
|
|
4735
|
+
Only plot() calls with display=display.data_window produce output columns.
|
|
4736
|
+
Module file ./modules/pine.module is loaded automatically if it exists.
|
|
4737
|
+
|
|
4738
|
+
Candle dump flags (--dump):
|
|
4739
|
+
|
|
4740
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4741
|
+
--timeframe <string> Candle interval (default: 15m)
|
|
4742
|
+
--limit <string> Number of candles (default: 250)
|
|
4743
|
+
--when <string> End date — ISO 8601 or Unix ms (default: now)
|
|
4744
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4745
|
+
--output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
|
|
4746
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4747
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4748
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4749
|
+
|
|
4750
|
+
Module file ./modules/dump.module is loaded automatically if it exists.
|
|
4751
|
+
|
|
4752
|
+
PnL debug flags (--pnldebug):
|
|
4753
|
+
|
|
4754
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4755
|
+
--priceopen <number> Entry price (required)
|
|
4756
|
+
--direction <string> Position direction: long or short (default: long)
|
|
4757
|
+
--when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
|
|
4758
|
+
--minutes <string> Number of 1m candles to simulate (default: 60)
|
|
4759
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4760
|
+
--output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
|
|
4761
|
+
--json Save as JSON array to ./dump/<output>.json
|
|
4762
|
+
--jsonl Save as JSONL to ./dump/<output>.jsonl
|
|
4763
|
+
--markdown Save as Markdown table to ./dump/<output>.md
|
|
4764
|
+
|
|
4765
|
+
Module file ./modules/pnldebug.module is loaded automatically if it exists.
|
|
4766
|
+
|
|
4767
|
+
Broker debug flags (--brokerdebug):
|
|
4768
|
+
|
|
4769
|
+
--symbol <string> Trading pair (default: BTCUSDT)
|
|
4770
|
+
--exchange <string> Exchange name (default: first registered)
|
|
4771
|
+
--commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
|
|
4772
|
+
partial-loss, average-buy, trailing-stop, trailing-take, breakeven
|
|
4773
|
+
(default: signal-open)
|
|
4774
|
+
|
|
4775
|
+
Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
|
|
4776
|
+
the selected broker commit with synthetic payload values derived from current price.
|
|
4777
|
+
|
|
4778
|
+
Flush flags (--flush):
|
|
4779
|
+
|
|
4780
|
+
One or more positional entry points. For each entry point the following
|
|
4781
|
+
subdirectories are removed from <entry-dir>/dump/:
|
|
4782
|
+
|
|
4783
|
+
report log markdown agent
|
|
4784
|
+
|
|
4785
|
+
Init flags (--init):
|
|
4786
|
+
|
|
4787
|
+
--output <string> Target directory name (default: backtest-kit-project)
|
|
4788
|
+
|
|
4789
|
+
Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
|
|
4790
|
+
|
|
4791
|
+
Docker flags (--docker):
|
|
4792
|
+
|
|
4793
|
+
--output <string> Target directory name (default: backtest-kit-docker)
|
|
4794
|
+
|
|
4795
|
+
Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
|
|
4796
|
+
tsconfig.json, and a sample strategy under content/. Run npm install then
|
|
4797
|
+
docker compose up to start the container.
|
|
4798
|
+
|
|
4799
|
+
Module hooks (loaded automatically by each mode):
|
|
4800
|
+
|
|
4801
|
+
modules/backtest.module --backtest Broker adapter for backtest
|
|
4802
|
+
modules/walker.module --walker Broker adapter for walker comparison
|
|
4803
|
+
modules/paper.module --paper Broker adapter for paper trading
|
|
4804
|
+
modules/live.module --live Broker adapter for live trading
|
|
4805
|
+
modules/main.module --main Environment setup for a custom entry point
|
|
4806
|
+
modules/pine.module --pine Exchange schema for PineScript runs
|
|
4807
|
+
modules/editor.module --editor Exchange schema for the visual Pine editor
|
|
4808
|
+
modules/dump.module --dump Exchange schema for candle dumps
|
|
4809
|
+
modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
|
|
4810
|
+
modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
|
|
4811
|
+
|
|
4812
|
+
--flush has no associated module. It only removes dump subdirectories.
|
|
4813
|
+
|
|
4814
|
+
Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
|
|
4815
|
+
|
|
4816
|
+
Environment variables:
|
|
4817
|
+
|
|
4818
|
+
CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
|
|
4819
|
+
CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
|
|
4820
|
+
CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
|
|
4821
|
+
CC_WWWROOT_PORT UI server port (default: 60050)
|
|
4822
|
+
|
|
4823
|
+
Examples:
|
|
4824
|
+
|
|
4825
|
+
node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
|
|
4826
|
+
node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
|
|
4827
|
+
node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
|
|
4828
|
+
node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
|
|
4829
|
+
node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
|
|
4830
|
+
node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
|
|
4831
|
+
node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
|
|
4832
|
+
node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
|
|
4833
|
+
node ${ENTRY_PATH} --editor
|
|
4834
|
+
node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
|
|
4835
|
+
node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
|
|
4836
|
+
node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
|
|
4837
|
+
node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
|
|
4838
|
+
node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
|
|
4839
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
|
|
4840
|
+
node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
|
|
4841
|
+
node ${ENTRY_PATH} --init --output my-trading-bot
|
|
4842
|
+
node ${ENTRY_PATH} --docker --output my-docker-workspace
|
|
4730
4843
|
`.trimStart();
|
|
4731
4844
|
const main$1 = async () => {
|
|
4732
4845
|
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)))) {
|
|
@@ -4736,7 +4849,7 @@ const main$1 = async () => {
|
|
|
4736
4849
|
if (!values.help) {
|
|
4737
4850
|
return;
|
|
4738
4851
|
}
|
|
4739
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4852
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n\n`);
|
|
4740
4853
|
process.stdout.write(HELP_TEXT);
|
|
4741
4854
|
process.exit(0);
|
|
4742
4855
|
};
|
|
@@ -4750,7 +4863,7 @@ const main = async () => {
|
|
|
4750
4863
|
if (!values.version) {
|
|
4751
4864
|
return;
|
|
4752
4865
|
}
|
|
4753
|
-
process.stdout.write(`@backtest-kit/cli ${"
|
|
4866
|
+
process.stdout.write(`@backtest-kit/cli ${"15.0.0"}\n`);
|
|
4754
4867
|
process.exit(0);
|
|
4755
4868
|
};
|
|
4756
4869
|
main();
|