backtest-kit 1.4.15 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -36,6 +36,15 @@ const GLOBAL_CONFIG = {
36
36
  * Default: 1440 minutes (1 day)
37
37
  */
38
38
  CC_MAX_SIGNAL_LIFETIME_MINUTES: 1440,
39
+ /**
40
+ * Maximum time allowed for signal generation (in seconds).
41
+ * Prevents long-running or stuck signal generation routines from blocking
42
+ * execution or consuming resources indefinitely. If generation exceeds this
43
+ * threshold the attempt should be aborted, logged and optionally retried.
44
+ *
45
+ * Default: 180 seconds (3 minutes)
46
+ */
47
+ CC_MAX_SIGNAL_GENERATION_SECONDS: 180,
39
48
  /**
40
49
  * Number of retries for getCandles function
41
50
  * Default: 3 retries
@@ -1698,6 +1707,53 @@ var emitters = /*#__PURE__*/Object.freeze({
1698
1707
  walkerStopSubject: walkerStopSubject
1699
1708
  });
1700
1709
 
1710
+ /**
1711
+ * Converts markdown content to plain text with minimal formatting
1712
+ * @param content - Markdown string to convert
1713
+ * @returns Plain text representation
1714
+ */
1715
+ const toPlainString = (content) => {
1716
+ if (!content) {
1717
+ return "";
1718
+ }
1719
+ let text = content;
1720
+ // Remove code blocks
1721
+ text = text.replace(/```[\s\S]*?```/g, "");
1722
+ text = text.replace(/`([^`]+)`/g, "$1");
1723
+ // Remove images
1724
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
1725
+ // Convert links to text only (keep link text, remove URL)
1726
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
1727
+ // Remove headers (convert to plain text)
1728
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
1729
+ // Remove bold and italic markers
1730
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
1731
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
1732
+ text = text.replace(/\*(.+?)\*/g, "$1");
1733
+ text = text.replace(/___(.+?)___/g, "$1");
1734
+ text = text.replace(/__(.+?)__/g, "$1");
1735
+ text = text.replace(/_(.+?)_/g, "$1");
1736
+ // Remove strikethrough
1737
+ text = text.replace(/~~(.+?)~~/g, "$1");
1738
+ // Convert lists to plain text with bullets
1739
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
1740
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
1741
+ // Remove blockquotes
1742
+ text = text.replace(/^\s*>\s+/gm, "");
1743
+ // Remove horizontal rules
1744
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
1745
+ // Remove HTML tags
1746
+ text = text.replace(/<[^>]+>/g, "");
1747
+ // Remove excessive whitespace and normalize line breaks
1748
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
1749
+ text = text.replace(/[ \t]+/g, " ");
1750
+ // Remove all newline characters
1751
+ text = text.replace(/\n/g, " ");
1752
+ // Remove excessive spaces after newline removal
1753
+ text = text.replace(/\s+/g, " ");
1754
+ return text.trim();
1755
+ };
1756
+
1701
1757
  const INTERVAL_MINUTES$1 = {
1702
1758
  "1m": 1,
1703
1759
  "3m": 3,
@@ -1706,6 +1762,7 @@ const INTERVAL_MINUTES$1 = {
1706
1762
  "30m": 30,
1707
1763
  "1h": 60,
1708
1764
  };
1765
+ const TIMEOUT_SYMBOL = Symbol('timeout');
1709
1766
  const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
1710
1767
  const errors = [];
1711
1768
  // ПРОВЕРКА ОБЯЗАТЕЛЬНЫХ ПОЛЕЙ ISignalRow
@@ -1889,7 +1946,14 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1889
1946
  }))) {
1890
1947
  return null;
1891
1948
  }
1892
- const signal = await self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when);
1949
+ const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
1950
+ const signal = await Promise.race([
1951
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
1952
+ sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
1953
+ ]);
1954
+ if (typeof signal === "symbol") {
1955
+ throw new Error(`Timeout for ${self.params.method.context.strategyName} symbol=${self.params.execution.context.symbol}`);
1956
+ }
1893
1957
  if (!signal) {
1894
1958
  return null;
1895
1959
  }
@@ -1910,7 +1974,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1910
1974
  id: randomString(),
1911
1975
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
1912
1976
  position: signal.position,
1913
- note: signal.note,
1977
+ note: toPlainString(signal.note),
1914
1978
  priceTakeProfit: signal.priceTakeProfit,
1915
1979
  priceStopLoss: signal.priceStopLoss,
1916
1980
  minuteEstimatedTime: signal.minuteEstimatedTime,
@@ -1930,7 +1994,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1930
1994
  id: randomString(),
1931
1995
  priceOpen: signal.priceOpen,
1932
1996
  position: signal.position,
1933
- note: signal.note,
1997
+ note: toPlainString(signal.note),
1934
1998
  priceTakeProfit: signal.priceTakeProfit,
1935
1999
  priceStopLoss: signal.priceStopLoss,
1936
2000
  minuteEstimatedTime: signal.minuteEstimatedTime,
@@ -1949,6 +2013,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
1949
2013
  id: randomString(),
1950
2014
  priceOpen: currentPrice,
1951
2015
  ...signal,
2016
+ note: toPlainString(signal.note),
1952
2017
  symbol: self.params.execution.context.symbol,
1953
2018
  exchangeName: self.params.method.context.exchangeName,
1954
2019
  strategyName: self.params.method.context.strategyName,
@@ -2174,6 +2239,8 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
2174
2239
  strategyName: self.params.method.context.strategyName,
2175
2240
  exchangeName: self.params.method.context.exchangeName,
2176
2241
  symbol: self.params.execution.context.symbol,
2242
+ percentTp: 0,
2243
+ percentSl: 0,
2177
2244
  };
2178
2245
  if (self.params.callbacks?.onTick) {
2179
2246
  self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
@@ -2277,7 +2344,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
2277
2344
  self.params.callbacks.onClose(self.params.execution.context.symbol, signal, currentPrice, self.params.execution.context.backtest);
2278
2345
  }
2279
2346
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
2280
- await self.params.partial.clear(self.params.execution.context.symbol, signal, currentPrice);
2347
+ await self.params.partial.clear(self.params.execution.context.symbol, signal, currentPrice, self.params.execution.context.backtest);
2281
2348
  await self.params.risk.removeSignal(self.params.execution.context.symbol, {
2282
2349
  strategyName: self.params.method.context.strategyName,
2283
2350
  riskName: self.params.riskName,
@@ -2300,31 +2367,56 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
2300
2367
  return result;
2301
2368
  };
2302
2369
  const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
2303
- // Calculate revenue percentage for partial fill/loss callbacks
2370
+ let percentTp = 0;
2371
+ let percentSl = 0;
2372
+ // Calculate percentage of path to TP/SL for partial fill/loss callbacks
2304
2373
  {
2305
- let revenuePercent = 0;
2306
2374
  if (signal.position === "long") {
2307
- // For long: positive if current > open, negative if current < open
2308
- revenuePercent = ((currentPrice - signal.priceOpen) / signal.priceOpen) * 100;
2375
+ // For long: calculate progress towards TP or SL
2376
+ const currentDistance = currentPrice - signal.priceOpen;
2377
+ if (currentDistance > 0) {
2378
+ // Moving towards TP
2379
+ const tpDistance = signal.priceTakeProfit - signal.priceOpen;
2380
+ const progressPercent = (currentDistance / tpDistance) * 100;
2381
+ percentTp = Math.min(progressPercent, 100);
2382
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest, self.params.execution.context.when);
2383
+ if (self.params.callbacks?.onPartialProfit) {
2384
+ self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest);
2385
+ }
2386
+ }
2387
+ else if (currentDistance < 0) {
2388
+ // Moving towards SL
2389
+ const slDistance = signal.priceOpen - signal.priceStopLoss;
2390
+ const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2391
+ percentSl = Math.min(progressPercent, 100);
2392
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest, self.params.execution.context.when);
2393
+ if (self.params.callbacks?.onPartialLoss) {
2394
+ self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest);
2395
+ }
2396
+ }
2309
2397
  }
2310
2398
  else if (signal.position === "short") {
2311
- // For short: positive if current < open, negative if current > open
2312
- revenuePercent = ((signal.priceOpen - currentPrice) / signal.priceOpen) * 100;
2313
- }
2314
- // Call onPartialProfit if revenue is positive (but not reached TP yet)
2315
- if (revenuePercent > 0) {
2316
- // КРИТИЧНО: Вызываем ClientPartial для отслеживания уровней
2317
- await self.params.partial.profit(self.params.execution.context.symbol, signal, currentPrice, revenuePercent, self.params.execution.context.backtest, self.params.execution.context.when);
2318
- if (self.params.callbacks?.onPartialProfit) {
2319
- self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, currentPrice, revenuePercent, self.params.execution.context.backtest);
2399
+ // For short: calculate progress towards TP or SL
2400
+ const currentDistance = signal.priceOpen - currentPrice;
2401
+ if (currentDistance > 0) {
2402
+ // Moving towards TP
2403
+ const tpDistance = signal.priceOpen - signal.priceTakeProfit;
2404
+ const progressPercent = (currentDistance / tpDistance) * 100;
2405
+ percentTp = Math.min(progressPercent, 100);
2406
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest, self.params.execution.context.when);
2407
+ if (self.params.callbacks?.onPartialProfit) {
2408
+ self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest);
2409
+ }
2320
2410
  }
2321
- }
2322
- // Call onPartialLoss if revenue is negative (but not hit SL yet)
2323
- if (revenuePercent < 0) {
2324
- // КРИТИЧНО: Вызываем ClientPartial для отслеживания уровней
2325
- await self.params.partial.loss(self.params.execution.context.symbol, signal, currentPrice, revenuePercent, self.params.execution.context.backtest, self.params.execution.context.when);
2326
- if (self.params.callbacks?.onPartialLoss) {
2327
- self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, currentPrice, revenuePercent, self.params.execution.context.backtest);
2411
+ if (currentDistance < 0) {
2412
+ // Moving towards SL
2413
+ const slDistance = signal.priceStopLoss - signal.priceOpen;
2414
+ const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2415
+ percentSl = Math.min(progressPercent, 100);
2416
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest, self.params.execution.context.when);
2417
+ if (self.params.callbacks?.onPartialLoss) {
2418
+ self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest);
2419
+ }
2328
2420
  }
2329
2421
  }
2330
2422
  }
@@ -2335,6 +2427,8 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
2335
2427
  strategyName: self.params.method.context.strategyName,
2336
2428
  exchangeName: self.params.method.context.exchangeName,
2337
2429
  symbol: self.params.execution.context.symbol,
2430
+ percentTp,
2431
+ percentSl,
2338
2432
  };
2339
2433
  if (self.params.callbacks?.onTick) {
2340
2434
  self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
@@ -2456,7 +2550,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
2456
2550
  self.params.callbacks.onClose(self.params.execution.context.symbol, signal, averagePrice, self.params.execution.context.backtest);
2457
2551
  }
2458
2552
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
2459
- await self.params.partial.clear(self.params.execution.context.symbol, signal, averagePrice);
2553
+ await self.params.partial.clear(self.params.execution.context.symbol, signal, averagePrice, self.params.execution.context.backtest);
2460
2554
  await self.params.risk.removeSignal(self.params.execution.context.symbol, {
2461
2555
  strategyName: self.params.method.context.strategyName,
2462
2556
  riskName: self.params.riskName,
@@ -2599,29 +2693,50 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
2599
2693
  return await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(self, signal, closePrice, closeReason, currentCandleTimestamp);
2600
2694
  }
2601
2695
  // Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
2602
- // Calculate revenue percentage
2696
+ // Calculate percentage of path to TP/SL
2603
2697
  {
2604
- let revenuePercent = 0;
2605
2698
  if (signal.position === "long") {
2606
- revenuePercent = ((averagePrice - signal.priceOpen) / signal.priceOpen) * 100;
2699
+ // For long: calculate progress towards TP or SL
2700
+ const currentDistance = averagePrice - signal.priceOpen;
2701
+ if (currentDistance > 0) {
2702
+ // Moving towards TP
2703
+ const tpDistance = signal.priceTakeProfit - signal.priceOpen;
2704
+ const progressPercent = (currentDistance / tpDistance) * 100;
2705
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2706
+ if (self.params.callbacks?.onPartialProfit) {
2707
+ self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2708
+ }
2709
+ }
2710
+ else if (currentDistance < 0) {
2711
+ // Moving towards SL
2712
+ const slDistance = signal.priceOpen - signal.priceStopLoss;
2713
+ const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2714
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2715
+ if (self.params.callbacks?.onPartialLoss) {
2716
+ self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2717
+ }
2718
+ }
2607
2719
  }
2608
2720
  else if (signal.position === "short") {
2609
- revenuePercent = ((signal.priceOpen - averagePrice) / signal.priceOpen) * 100;
2610
- }
2611
- // Call onPartialProfit if revenue is positive (but not reached TP yet)
2612
- if (revenuePercent > 0) {
2613
- // КРИТИЧНО: Вызываем ClientPartial для отслеживания уровней
2614
- await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, revenuePercent, self.params.execution.context.backtest, self.params.execution.context.when);
2615
- if (self.params.callbacks?.onPartialProfit) {
2616
- self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, revenuePercent, self.params.execution.context.backtest);
2721
+ // For short: calculate progress towards TP or SL
2722
+ const currentDistance = signal.priceOpen - averagePrice;
2723
+ if (currentDistance > 0) {
2724
+ // Moving towards TP
2725
+ const tpDistance = signal.priceOpen - signal.priceTakeProfit;
2726
+ const progressPercent = (currentDistance / tpDistance) * 100;
2727
+ await self.params.partial.profit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2728
+ if (self.params.callbacks?.onPartialProfit) {
2729
+ self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2730
+ }
2617
2731
  }
2618
- }
2619
- // Call onPartialLoss if revenue is negative (but not hit SL yet)
2620
- if (revenuePercent < 0) {
2621
- // КРИТИЧНО: Вызываем ClientPartial для отслеживания уровней
2622
- await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, revenuePercent, self.params.execution.context.backtest, self.params.execution.context.when);
2623
- if (self.params.callbacks?.onPartialLoss) {
2624
- self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, revenuePercent, self.params.execution.context.backtest);
2732
+ if (currentDistance < 0) {
2733
+ // Moving towards SL
2734
+ const slDistance = signal.priceStopLoss - signal.priceOpen;
2735
+ const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
2736
+ await self.params.partial.loss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest, self.params.execution.context.when);
2737
+ if (self.params.callbacks?.onPartialLoss) {
2738
+ self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
2739
+ }
2625
2740
  }
2626
2741
  }
2627
2742
  }
@@ -2910,6 +3025,8 @@ class ClientStrategy {
2910
3025
  action: "active",
2911
3026
  signal: scheduled,
2912
3027
  currentPrice: lastPrice,
3028
+ percentSl: 0,
3029
+ percentTp: 0,
2913
3030
  strategyName: this.params.method.context.strategyName,
2914
3031
  exchangeName: this.params.method.context.exchangeName,
2915
3032
  symbol: this.params.execution.context.symbol,
@@ -5611,7 +5728,7 @@ const columns$4 = [
5611
5728
  {
5612
5729
  key: "note",
5613
5730
  label: "Note",
5614
- format: (data) => data.signal.note ?? "N/A",
5731
+ format: (data) => toPlainString(data.signal.note ?? "N/A"),
5615
5732
  },
5616
5733
  {
5617
5734
  key: "openPrice",
@@ -5758,14 +5875,33 @@ let ReportStorage$4 = class ReportStorage {
5758
5875
  async getReport(strategyName) {
5759
5876
  const stats = await this.getData();
5760
5877
  if (stats.totalSignals === 0) {
5761
- return str.newline(`# Backtest Report: ${strategyName}`, "", "No signals closed yet.");
5878
+ return [
5879
+ `# Backtest Report: ${strategyName}`,
5880
+ "",
5881
+ "No signals closed yet."
5882
+ ].join("\n");
5762
5883
  }
5763
5884
  const header = columns$4.map((col) => col.label);
5764
5885
  const separator = columns$4.map(() => "---");
5765
5886
  const rows = this._signalList.map((closedSignal) => columns$4.map((col) => col.format(closedSignal)));
5766
5887
  const tableData = [header, separator, ...rows];
5767
- const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
5768
- return str.newline(`# Backtest Report: ${strategyName}`, "", table, "", `**Total signals:** ${stats.totalSignals}`, `**Closed signals:** ${stats.totalSignals}`, `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`, `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`, `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`, `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`, `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`, `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`, `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`, `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`);
5888
+ const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
5889
+ return [
5890
+ `# Backtest Report: ${strategyName}`,
5891
+ "",
5892
+ table,
5893
+ "",
5894
+ `**Total signals:** ${stats.totalSignals}`,
5895
+ `**Closed signals:** ${stats.totalSignals}`,
5896
+ `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`,
5897
+ `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`,
5898
+ `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`,
5899
+ `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`,
5900
+ `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`,
5901
+ `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
5902
+ `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
5903
+ `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
5904
+ ].join("\n");
5769
5905
  }
5770
5906
  /**
5771
5907
  * Saves strategy report to disk.
@@ -6023,7 +6159,7 @@ const columns$3 = [
6023
6159
  {
6024
6160
  key: "note",
6025
6161
  label: "Note",
6026
- format: (data) => data.note ?? "N/A",
6162
+ format: (data) => toPlainString(data.note ?? "N/A"),
6027
6163
  },
6028
6164
  {
6029
6165
  key: "currentPrice",
@@ -6047,6 +6183,16 @@ const columns$3 = [
6047
6183
  label: "Stop Loss",
6048
6184
  format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
6049
6185
  },
6186
+ {
6187
+ key: "percentTp",
6188
+ label: "% to TP",
6189
+ format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
6190
+ },
6191
+ {
6192
+ key: "percentSl",
6193
+ label: "% to SL",
6194
+ format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
6195
+ },
6050
6196
  {
6051
6197
  key: "pnl",
6052
6198
  label: "PNL (net)",
@@ -6149,6 +6295,8 @@ let ReportStorage$3 = class ReportStorage {
6149
6295
  openPrice: data.signal.priceOpen,
6150
6296
  takeProfit: data.signal.priceTakeProfit,
6151
6297
  stopLoss: data.signal.priceStopLoss,
6298
+ percentTp: data.percentTp,
6299
+ percentSl: data.percentSl,
6152
6300
  };
6153
6301
  // Replace existing event or add new one
6154
6302
  if (existingIndex !== -1) {
@@ -6290,14 +6438,33 @@ let ReportStorage$3 = class ReportStorage {
6290
6438
  async getReport(strategyName) {
6291
6439
  const stats = await this.getData();
6292
6440
  if (stats.totalEvents === 0) {
6293
- return str.newline(`# Live Trading Report: ${strategyName}`, "", "No events recorded yet.");
6441
+ return [
6442
+ `# Live Trading Report: ${strategyName}`,
6443
+ "",
6444
+ "No events recorded yet."
6445
+ ].join("\n");
6294
6446
  }
6295
6447
  const header = columns$3.map((col) => col.label);
6296
6448
  const separator = columns$3.map(() => "---");
6297
6449
  const rows = this._eventList.map((event) => columns$3.map((col) => col.format(event)));
6298
6450
  const tableData = [header, separator, ...rows];
6299
- const table = str.newline(tableData.map(row => `| ${row.join(" | ")} |`));
6300
- return str.newline(`# Live Trading Report: ${strategyName}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Closed signals:** ${stats.totalClosed}`, `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`, `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`, `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`, `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`, `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`, `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`, `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`, `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`);
6451
+ const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
6452
+ return [
6453
+ `# Live Trading Report: ${strategyName}`,
6454
+ "",
6455
+ table,
6456
+ "",
6457
+ `**Total events:** ${stats.totalEvents}`,
6458
+ `**Closed signals:** ${stats.totalClosed}`,
6459
+ `**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`,
6460
+ `**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`,
6461
+ `**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`,
6462
+ `**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`,
6463
+ `**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`,
6464
+ `**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
6465
+ `**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
6466
+ `**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
6467
+ ].join("\n");
6301
6468
  }
6302
6469
  /**
6303
6470
  * Saves strategy report to disk.
@@ -6550,7 +6717,7 @@ const columns$2 = [
6550
6717
  {
6551
6718
  key: "note",
6552
6719
  label: "Note",
6553
- format: (data) => data.note ?? "N/A",
6720
+ format: (data) => toPlainString(data.note ?? "N/A"),
6554
6721
  },
6555
6722
  {
6556
6723
  key: "currentPrice",
@@ -6694,14 +6861,28 @@ let ReportStorage$2 = class ReportStorage {
6694
6861
  async getReport(strategyName) {
6695
6862
  const stats = await this.getData();
6696
6863
  if (stats.totalEvents === 0) {
6697
- return str.newline(`# Scheduled Signals Report: ${strategyName}`, "", "No scheduled signals recorded yet.");
6864
+ return [
6865
+ `# Scheduled Signals Report: ${strategyName}`,
6866
+ "",
6867
+ "No scheduled signals recorded yet."
6868
+ ].join("\n");
6698
6869
  }
6699
6870
  const header = columns$2.map((col) => col.label);
6700
6871
  const separator = columns$2.map(() => "---");
6701
6872
  const rows = this._eventList.map((event) => columns$2.map((col) => col.format(event)));
6702
6873
  const tableData = [header, separator, ...rows];
6703
- const table = str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
6704
- return str.newline(`# Scheduled Signals Report: ${strategyName}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Scheduled signals:** ${stats.totalScheduled}`, `**Cancelled signals:** ${stats.totalCancelled}`, `**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`, `**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`);
6874
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
6875
+ return [
6876
+ `# Scheduled Signals Report: ${strategyName}`,
6877
+ "",
6878
+ table,
6879
+ "",
6880
+ `**Total events:** ${stats.totalEvents}`,
6881
+ `**Scheduled signals:** ${stats.totalScheduled}`,
6882
+ `**Cancelled signals:** ${stats.totalCancelled}`,
6883
+ `**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`,
6884
+ `**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`
6885
+ ].join("\n");
6705
6886
  }
6706
6887
  /**
6707
6888
  * Saves strategy report to disk.
@@ -7019,7 +7200,11 @@ class PerformanceStorage {
7019
7200
  async getReport(strategyName) {
7020
7201
  const stats = await this.getData(strategyName);
7021
7202
  if (stats.totalEvents === 0) {
7022
- return str.newline(`# Performance Report: ${strategyName}`, "", "No performance metrics recorded yet.");
7203
+ return [
7204
+ `# Performance Report: ${strategyName}`,
7205
+ "",
7206
+ "No performance metrics recorded yet."
7207
+ ].join("\n");
7023
7208
  }
7024
7209
  // Sort metrics by total duration (descending) to show bottlenecks first
7025
7210
  const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
@@ -7056,13 +7241,29 @@ class PerformanceStorage {
7056
7241
  metric.maxWaitTime.toFixed(2),
7057
7242
  ]);
7058
7243
  const summaryTableData = [summaryHeader, summarySeparator, ...summaryRows];
7059
- const summaryTable = str.newline(summaryTableData.map((row) => `| ${row.join(" | ")} |`));
7244
+ const summaryTable = summaryTableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7060
7245
  // Calculate percentage of total time for each metric
7061
7246
  const percentages = sortedMetrics.map((metric) => {
7062
7247
  const pct = (metric.totalDuration / stats.totalDuration) * 100;
7063
7248
  return `- **${metric.metricType}**: ${pct.toFixed(1)}% (${metric.totalDuration.toFixed(2)}ms total)`;
7064
7249
  });
7065
- return str.newline(`# Performance Report: ${strategyName}`, "", `**Total events:** ${stats.totalEvents}`, `**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`, `**Number of metric types:** ${Object.keys(stats.metricStats).length}`, "", "## Time Distribution", "", str.newline(percentages), "", "## Detailed Metrics", "", summaryTable, "", "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type.");
7250
+ return [
7251
+ `# Performance Report: ${strategyName}`,
7252
+ "",
7253
+ `**Total events:** ${stats.totalEvents}`,
7254
+ `**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
7255
+ `**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
7256
+ "",
7257
+ "## Time Distribution",
7258
+ "",
7259
+ percentages.join("\n"),
7260
+ "",
7261
+ "## Detailed Metrics",
7262
+ "",
7263
+ summaryTable,
7264
+ "",
7265
+ "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type."
7266
+ ].join("\n");
7066
7267
  }
7067
7268
  /**
7068
7269
  * Saves performance report to disk.
@@ -7466,7 +7667,7 @@ let ReportStorage$1 = class ReportStorage {
7466
7667
  // Build table rows
7467
7668
  const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
7468
7669
  const tableData = [header, separator, ...rows];
7469
- return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7670
+ return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7470
7671
  }
7471
7672
  /**
7472
7673
  * Generates PNL table showing all closed signals across all strategies (View).
@@ -7503,7 +7704,7 @@ let ReportStorage$1 = class ReportStorage {
7503
7704
  // Build table rows
7504
7705
  const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
7505
7706
  const tableData = [header, separator, ...rows];
7506
- return str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
7707
+ return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
7507
7708
  }
7508
7709
  /**
7509
7710
  * Generates markdown report with all strategy results (View).
@@ -7518,7 +7719,30 @@ let ReportStorage$1 = class ReportStorage {
7518
7719
  const results = await this.getData(symbol, metric, context);
7519
7720
  // Get total signals for best strategy
7520
7721
  const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
7521
- return str.newline(`# Walker Comparison Report: ${results.walkerName}`, "", `**Symbol:** ${results.symbol}`, `**Exchange:** ${results.exchangeName}`, `**Frame:** ${results.frameName}`, `**Optimization Metric:** ${results.metric}`, `**Strategies Tested:** ${results.totalStrategies}`, "", `## Best Strategy: ${results.bestStrategy}`, "", `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`, `**Total Signals:** ${bestStrategySignals}`, "", "## Top Strategies Comparison", "", this.getComparisonTable(metric, 10), "", "## All Signals (PNL Table)", "", this.getPnlTable(), "", "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better).");
7722
+ return [
7723
+ `# Walker Comparison Report: ${results.walkerName}`,
7724
+ "",
7725
+ `**Symbol:** ${results.symbol}`,
7726
+ `**Exchange:** ${results.exchangeName}`,
7727
+ `**Frame:** ${results.frameName}`,
7728
+ `**Optimization Metric:** ${results.metric}`,
7729
+ `**Strategies Tested:** ${results.totalStrategies}`,
7730
+ "",
7731
+ `## Best Strategy: ${results.bestStrategy}`,
7732
+ "",
7733
+ `**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`,
7734
+ `**Total Signals:** ${bestStrategySignals}`,
7735
+ "",
7736
+ "## Top Strategies Comparison",
7737
+ "",
7738
+ this.getComparisonTable(metric, 10),
7739
+ "",
7740
+ "## All Signals (PNL Table)",
7741
+ "",
7742
+ this.getPnlTable(),
7743
+ "",
7744
+ "**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
7745
+ ].join("\n");
7522
7746
  }
7523
7747
  /**
7524
7748
  * Saves walker report to disk.
@@ -8032,14 +8256,24 @@ class HeatmapStorage {
8032
8256
  async getReport(strategyName) {
8033
8257
  const data = await this.getData();
8034
8258
  if (data.symbols.length === 0) {
8035
- return str.newline(`# Portfolio Heatmap: ${strategyName}`, "", "*No data available*");
8259
+ return [
8260
+ `# Portfolio Heatmap: ${strategyName}`,
8261
+ "",
8262
+ "*No data available*"
8263
+ ].join("\n");
8036
8264
  }
8037
8265
  const header = columns$1.map((col) => col.label);
8038
8266
  const separator = columns$1.map(() => "---");
8039
8267
  const rows = data.symbols.map((row) => columns$1.map((col) => col.format(row)));
8040
8268
  const tableData = [header, separator, ...rows];
8041
- const table = str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
8042
- return str.newline(`# Portfolio Heatmap: ${strategyName}`, "", `**Total Symbols:** ${data.totalSymbols} | **Portfolio PNL:** ${data.portfolioTotalPnl !== null ? str(data.portfolioTotalPnl, "%+.2f%%") : "N/A"} | **Portfolio Sharpe:** ${data.portfolioSharpeRatio !== null ? str(data.portfolioSharpeRatio, "%.2f") : "N/A"} | **Total Trades:** ${data.portfolioTotalTrades}`, "", table);
8269
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
8270
+ return [
8271
+ `# Portfolio Heatmap: ${strategyName}`,
8272
+ "",
8273
+ `**Total Symbols:** ${data.totalSymbols} | **Portfolio PNL:** ${data.portfolioTotalPnl !== null ? str(data.portfolioTotalPnl, "%+.2f%%") : "N/A"} | **Portfolio Sharpe:** ${data.portfolioSharpeRatio !== null ? str(data.portfolioSharpeRatio, "%.2f") : "N/A"} | **Total Trades:** ${data.portfolioTotalTrades}`,
8274
+ "",
8275
+ table
8276
+ ].join("\n");
8043
8277
  }
8044
8278
  /**
8045
8279
  * Saves heatmap report to disk.
@@ -8762,6 +8996,8 @@ class OptimizerTemplateService {
8762
8996
  interval,
8763
8997
  prompt,
8764
8998
  });
8999
+ // Convert prompt to plain text first
9000
+ const plainPrompt = toPlainString(prompt);
8765
9001
  // Escape special characters to prevent code injection
8766
9002
  const escapedStrategyName = String(strategyName)
8767
9003
  .replace(/\\/g, '\\\\')
@@ -8769,7 +9005,7 @@ class OptimizerTemplateService {
8769
9005
  const escapedInterval = String(interval)
8770
9006
  .replace(/\\/g, '\\\\')
8771
9007
  .replace(/"/g, '\\"');
8772
- const escapedPrompt = String(prompt)
9008
+ const escapedPrompt = String(plainPrompt)
8773
9009
  .replace(/\\/g, '\\\\')
8774
9010
  .replace(/`/g, '\\`')
8775
9011
  .replace(/\$/g, '\\$');
@@ -9950,7 +10186,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
9950
10186
  }
9951
10187
  }
9952
10188
  if (shouldPersist) {
9953
- await self._persistState(symbol);
10189
+ await self._persistState(symbol, backtest);
9954
10190
  }
9955
10191
  };
9956
10192
  /**
@@ -9997,7 +10233,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
9997
10233
  }
9998
10234
  }
9999
10235
  if (shouldPersist) {
10000
- await self._persistState(symbol);
10236
+ await self._persistState(symbol, backtest);
10001
10237
  }
10002
10238
  };
10003
10239
  /**
@@ -10133,7 +10369,10 @@ class ClientPartial {
10133
10369
  * @param symbol - Trading pair symbol
10134
10370
  * @returns Promise that resolves when persistence is complete
10135
10371
  */
10136
- async _persistState(symbol) {
10372
+ async _persistState(symbol, backtest) {
10373
+ if (backtest) {
10374
+ return;
10375
+ }
10137
10376
  this.params.logger.debug("ClientPartial persistState", { symbol });
10138
10377
  if (this._states === NEED_FETCH) {
10139
10378
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
@@ -10256,7 +10495,7 @@ class ClientPartial {
10256
10495
  * // Cleanup: PartialConnectionService.getPartial.clear(signal.id)
10257
10496
  * ```
10258
10497
  */
10259
- async clear(symbol, data, priceClose) {
10498
+ async clear(symbol, data, priceClose, backtest) {
10260
10499
  this.params.logger.log("ClientPartial clear", {
10261
10500
  symbol,
10262
10501
  data,
@@ -10266,7 +10505,7 @@ class ClientPartial {
10266
10505
  throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
10267
10506
  }
10268
10507
  this._states.delete(data.id);
10269
- await this._persistState(symbol);
10508
+ await this._persistState(symbol, backtest);
10270
10509
  }
10271
10510
  }
10272
10511
 
@@ -10437,7 +10676,7 @@ class PartialConnectionService {
10437
10676
  * @param priceClose - Final closing price
10438
10677
  * @returns Promise that resolves when clear is complete
10439
10678
  */
10440
- this.clear = async (symbol, data, priceClose) => {
10679
+ this.clear = async (symbol, data, priceClose, backtest) => {
10441
10680
  this.loggerService.log("partialConnectionService profit", {
10442
10681
  symbol,
10443
10682
  data,
@@ -10445,7 +10684,7 @@ class PartialConnectionService {
10445
10684
  });
10446
10685
  const partial = this.getPartial(data.id);
10447
10686
  await partial.waitForInit(symbol);
10448
- await partial.clear(symbol, data, priceClose);
10687
+ await partial.clear(symbol, data, priceClose, backtest);
10449
10688
  this.getPartial.clear(data.id);
10450
10689
  };
10451
10690
  }
@@ -10592,14 +10831,26 @@ class ReportStorage {
10592
10831
  async getReport(symbol, strategyName) {
10593
10832
  const stats = await this.getData();
10594
10833
  if (stats.totalEvents === 0) {
10595
- return str.newline(`# Partial Profit/Loss Report: ${symbol}:${strategyName}`, "", "No partial profit/loss events recorded yet.");
10834
+ return [
10835
+ `# Partial Profit/Loss Report: ${symbol}:${strategyName}`,
10836
+ "",
10837
+ "No partial profit/loss events recorded yet."
10838
+ ].join("\n");
10596
10839
  }
10597
10840
  const header = columns.map((col) => col.label);
10598
10841
  const separator = columns.map(() => "---");
10599
10842
  const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
10600
10843
  const tableData = [header, separator, ...rows];
10601
- const table = str.newline(tableData.map((row) => `| ${row.join(" | ")} |`));
10602
- return str.newline(`# Partial Profit/Loss Report: ${symbol}:${strategyName}`, "", table, "", `**Total events:** ${stats.totalEvents}`, `**Profit events:** ${stats.totalProfit}`, `**Loss events:** ${stats.totalLoss}`);
10844
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
10845
+ return [
10846
+ `# Partial Profit/Loss Report: ${symbol}:${strategyName}`,
10847
+ "",
10848
+ table,
10849
+ "",
10850
+ `**Total events:** ${stats.totalEvents}`,
10851
+ `**Profit events:** ${stats.totalProfit}`,
10852
+ `**Loss events:** ${stats.totalLoss}`
10853
+ ].join("\n");
10603
10854
  }
10604
10855
  /**
10605
10856
  * Saves symbol-strategy report to disk.
@@ -10914,13 +11165,14 @@ class PartialGlobalService {
10914
11165
  * @param priceClose - Final closing price
10915
11166
  * @returns Promise that resolves when clear is complete
10916
11167
  */
10917
- this.clear = async (symbol, data, priceClose) => {
11168
+ this.clear = async (symbol, data, priceClose, backtest) => {
10918
11169
  this.loggerService.log("partialGlobalService profit", {
10919
11170
  symbol,
10920
11171
  data,
10921
11172
  priceClose,
11173
+ backtest,
10922
11174
  });
10923
- return await this.partialConnectionService.clear(symbol, data, priceClose);
11175
+ return await this.partialConnectionService.clear(symbol, data, priceClose, backtest);
10924
11176
  };
10925
11177
  }
10926
11178
  }