@backtest-kit/cli 14.1.0 → 15.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- const trace = stackTrace__namespace.get();
115
- const result = [];
116
- trace.forEach((callSite) => {
117
- result.push(`File: ${callSite.getFileName()}`);
118
- result.push(`Line: ${callSite.getLineNumber()}`);
119
- result.push(`Function: ${callSite.getFunctionName() || "anonymous"}`);
120
- result.push(`Method: ${callSite.getMethodName() || "none"}`);
121
- result.push("---");
122
- });
123
- return functoolsKit.str.newline(result);
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
- const tickSize = market.limits?.price?.min || market.precision?.price;
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
- const stepSize = market.limits?.amount?.min || market.precision?.amount;
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
- const fn = async () => {
1828
- if (_is_stopped) {
1829
- return;
1830
- }
1831
- try {
1832
- await bot.telegram.getMe();
1833
- setTimeout(fn, HEALTH_CHECK_DELAY);
1834
- }
1835
- catch (error) {
1836
- console.log("Bot is offline");
1837
- throw new Error("Telegram goes offline");
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
- await fn();
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 TELEGAM_MAX_SYMBOLS = 4090;
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 = TELEGAM_MAX_SYMBOLS - TELEGRAM_SYMBOL_RESERVED;
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, "&bull;");
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
- // Emulate unordered lists with bullets
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.substring(0, maxChars);
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 notifyClosed", {
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 module import failed filePath=${filePath} baseDir=${baseDir}`);
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: "index.ts",
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 { module } = TRANSPILE_FN(resolvedPath, code, child, CREATE_BASE_REQUIRE_FN(child, seen));
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 "default" in module.exports
2908
- ? module.exports.default
2909
- : module.exports;
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
- throw new Error(`Failed to load module at ${filePath} (basepath: ${self.params.path})`);
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(`Module module import failed filePath=${filePath} baseDir=${baseDir}`);
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 ${"14.1.0"}\n`);
3542
+ process.stdout.write(`@backtest-kit/cli ${"15.1.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. All strategy files are loaded without
4578
- changing process.cwd() — .env is read from the working directory only.
4579
- addWalkerSchema is called automatically using the registered exchange and frame.
4580
- After comparison completes the report is printed to stdout (or saved if --json/--markdown).
4581
-
4582
- Module file ./modules/walker.module is loaded automatically if it exists.
4583
-
4584
- Paper / Live flags:
4585
-
4586
- --symbol <string> Trading pair (default: BTCUSDT)
4587
- --strategy <string> Strategy name (default: first registered)
4588
- --exchange <string> Exchange name (default: first registered)
4589
- --verbose Log every candle fetch to stdout
4590
- --ui Start web dashboard
4591
- --telegram Send Telegram notifications
4592
-
4593
- Main flags (--main):
4594
-
4595
- --noFlush Skip removing report/log/markdown/agent folders before the run
4596
-
4597
- Prepares the runtime environment (loads .env, setup.config, loader.config and
4598
- modules/main.module) and runs the single positional entry point — but does NOT
4599
- start any trading harness. Unlike --backtest/--live/--walker, the CLI never calls
4600
- Backtest/Live/Walker.background; the entry point decides what to run.
4601
-
4602
- Exactly one positional entry point is required. process.cwd() is changed to the
4603
- entry point directory and its local .env is loaded.
4604
-
4605
- Backtest, Live and Walker runs started from userspace are still tracked: the run
4606
- finishes automatically when one of them completes, Ctrl+C stops any active run,
4607
- and a second Ctrl+C force-quits.
4608
-
4609
- Module file ./modules/main.module is loaded automatically if it exists.
4610
-
4611
- PineScript flags (--pine):
4612
-
4613
- --symbol <string> Trading pair (default: BTCUSDT)
4614
- --timeframe <string> Candle interval (default: 15m)
4615
- --limit <string> Number of candles to fetch (default: 250)
4616
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4617
- --exchange <string> Exchange name (default: first registered)
4618
- --output <string> Output file base name without extension
4619
- --json Save output as JSON array to <pine-dir>/dump/<output>.json
4620
- --jsonl Save output as JSONL to <pine-dir>/dump/<output>.jsonl
4621
- --markdown Save output as Markdown table to <pine-dir>/dump/<output>.md
4622
-
4623
- Only plot() calls with display=display.data_window produce output columns.
4624
- Module file ./modules/pine.module is loaded automatically if it exists.
4625
-
4626
- Candle dump flags (--dump):
4627
-
4628
- --symbol <string> Trading pair (default: BTCUSDT)
4629
- --timeframe <string> Candle interval (default: 15m)
4630
- --limit <string> Number of candles (default: 250)
4631
- --when <string> End date — ISO 8601 or Unix ms (default: now)
4632
- --exchange <string> Exchange name (default: first registered)
4633
- --output <string> Output file base name (default: {SYMBOL}_{LIMIT}_{TIMEFRAME}_{TIMESTAMP})
4634
- --json Save as JSON array to ./dump/<output>.json
4635
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4636
-
4637
- Module file ./modules/dump.module is loaded automatically if it exists.
4638
-
4639
- PnL debug flags (--pnldebug):
4640
-
4641
- --symbol <string> Trading pair (default: BTCUSDT)
4642
- --priceopen <number> Entry price (required)
4643
- --direction <string> Position direction: long or short (default: long)
4644
- --when <string> Start timestamp — ISO 8601 or Unix ms (default: now)
4645
- --minutes <string> Number of 1m candles to simulate (default: 60)
4646
- --exchange <string> Exchange name (default: first registered)
4647
- --output <string> Output file base name (default: {SYMBOL}_{DIRECTION}_{PRICEOPEN}_{TIMESTAMP})
4648
- --json Save as JSON array to ./dump/<output>.json
4649
- --jsonl Save as JSONL to ./dump/<output>.jsonl
4650
- --markdown Save as Markdown table to ./dump/<output>.md
4651
-
4652
- Module file ./modules/pnldebug.module is loaded automatically if it exists.
4653
-
4654
- Broker debug flags (--brokerdebug):
4655
-
4656
- --symbol <string> Trading pair (default: BTCUSDT)
4657
- --exchange <string> Exchange name (default: first registered)
4658
- --commit <string> Commit type to fire: signal-open, signal-close, partial-profit,
4659
- partial-loss, average-buy, trailing-stop, trailing-take, breakeven
4660
- (default: signal-open)
4661
-
4662
- Loads ./live.module, fetches the last candle for --symbol/--timeframe, and calls
4663
- the selected broker commit with synthetic payload values derived from current price.
4664
-
4665
- Flush flags (--flush):
4666
-
4667
- One or more positional entry points. For each entry point the following
4668
- subdirectories are removed from <entry-dir>/dump/:
4669
-
4670
- report log markdown agent
4671
-
4672
- Init flags (--init):
4673
-
4674
- --output <string> Target directory name (default: backtest-kit-project)
4675
-
4676
- Scaffolds a project and runs scripts/fetch_docs.mjs to download library docs.
4677
-
4678
- Docker flags (--docker):
4679
-
4680
- --output <string> Target directory name (default: backtest-kit-docker)
4681
-
4682
- Scaffolds a Docker workspace: docker-compose.yaml, .env.example, package.json,
4683
- tsconfig.json, and a sample strategy under content/. Run npm install then
4684
- docker compose up to start the container.
4685
-
4686
- Module hooks (loaded automatically by each mode):
4687
-
4688
- modules/backtest.module --backtest Broker adapter for backtest
4689
- modules/walker.module --walker Broker adapter for walker comparison
4690
- modules/paper.module --paper Broker adapter for paper trading
4691
- modules/live.module --live Broker adapter for live trading
4692
- modules/main.module --main Environment setup for a custom entry point
4693
- modules/pine.module --pine Exchange schema for PineScript runs
4694
- modules/editor.module --editor Exchange schema for the visual Pine editor
4695
- modules/dump.module --dump Exchange schema for candle dumps
4696
- modules/pnldebug.module --pnldebug Exchange schema for PnL debug runs
4697
- modules/brokerdebug.module --brokerdebug Broker adapter used for broker commit testing
4698
-
4699
- --flush has no associated module. It only removes dump subdirectories.
4700
-
4701
- Extensions .ts, .mjs, .cjs are tried automatically. Missing module = soft warning.
4702
-
4703
- Environment variables:
4704
-
4705
- CC_TELEGRAM_TOKEN Telegram bot token (required for --telegram)
4706
- CC_TELEGRAM_CHANNEL Telegram channel or chat ID (required for --telegram)
4707
- CC_WWWROOT_HOST UI server bind address (default: 0.0.0.0)
4708
- CC_WWWROOT_PORT UI server port (default: 60050)
4709
-
4710
- Examples:
4711
-
4712
- node ${ENTRY_PATH} --backtest ./content/feb_2026.strategy.ts
4713
- node ${ENTRY_PATH} --backtest --symbol BTCUSDT --noCache --noFlush --ui ./content/feb_2026.strategy.ts
4714
- node ${ENTRY_PATH} --walker ./content/feb_2026_v1.strategy.ts ./content/feb_2026_v2.strategy.ts ./content/feb_2026_v3.strategy.ts
4715
- node ${ENTRY_PATH} --walker --symbol BTCUSDT --noCache --noFlush --markdown ./content/feb_2026_v1.ts ./content/feb_2026_v2.ts
4716
- node ${ENTRY_PATH} --paper --symbol ETHUSDT ./content/feb_2026.strategy.ts
4717
- node ${ENTRY_PATH} --live --ui --telegram ./content/feb_2026.strategy.ts
4718
- node ${ENTRY_PATH} --main ./tools/fetch_fear_and_greed.ts
4719
- node ${ENTRY_PATH} --pine ./math/feb_2026.pine --timeframe 15m --limit 500 --jsonl
4720
- node ${ENTRY_PATH} --editor
4721
- node ${ENTRY_PATH} --dump --symbol BTCUSDT --timeframe 15m --limit 500 --jsonl
4722
- node ${ENTRY_PATH} --pnldebug --symbol BTCUSDT --priceopen 64069.50 --direction short --when "2025-02-25" --minutes 120
4723
- node ${ENTRY_PATH} --pnldebug --priceopen 67956.73 --direction long --when 1772064000000 --minutes 60 --markdown
4724
- node ${ENTRY_PATH} --brokerdebug --commit signal-open --symbol BTCUSDT
4725
- node ${ENTRY_PATH} --brokerdebug --commit partial-profit --symbol ETHUSDT
4726
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts
4727
- node ${ENTRY_PATH} --flush ./content/feb_2026.strategy/feb_2026.strategy.ts ./content/feb_2026.strategy/feb_2026.test.ts
4728
- node ${ENTRY_PATH} --init --output my-trading-bot
4729
- node ${ENTRY_PATH} --docker --output my-docker-workspace
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 ${"14.1.0"}\n\n`);
4852
+ process.stdout.write(`@backtest-kit/cli ${"15.1.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 ${"14.1.0"}\n`);
4866
+ process.stdout.write(`@backtest-kit/cli ${"15.1.0"}\n`);
4754
4867
  process.exit(0);
4755
4868
  };
4756
4869
  main();