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.cjs +329 -77
- package/build/index.mjs +329 -77
- package/package.json +1 -1
- package/types.d.ts +20 -3
package/build/index.cjs
CHANGED
|
@@ -38,6 +38,15 @@ const GLOBAL_CONFIG = {
|
|
|
38
38
|
* Default: 1440 minutes (1 day)
|
|
39
39
|
*/
|
|
40
40
|
CC_MAX_SIGNAL_LIFETIME_MINUTES: 1440,
|
|
41
|
+
/**
|
|
42
|
+
* Maximum time allowed for signal generation (in seconds).
|
|
43
|
+
* Prevents long-running or stuck signal generation routines from blocking
|
|
44
|
+
* execution or consuming resources indefinitely. If generation exceeds this
|
|
45
|
+
* threshold the attempt should be aborted, logged and optionally retried.
|
|
46
|
+
*
|
|
47
|
+
* Default: 180 seconds (3 minutes)
|
|
48
|
+
*/
|
|
49
|
+
CC_MAX_SIGNAL_GENERATION_SECONDS: 180,
|
|
41
50
|
/**
|
|
42
51
|
* Number of retries for getCandles function
|
|
43
52
|
* Default: 3 retries
|
|
@@ -1700,6 +1709,53 @@ var emitters = /*#__PURE__*/Object.freeze({
|
|
|
1700
1709
|
walkerStopSubject: walkerStopSubject
|
|
1701
1710
|
});
|
|
1702
1711
|
|
|
1712
|
+
/**
|
|
1713
|
+
* Converts markdown content to plain text with minimal formatting
|
|
1714
|
+
* @param content - Markdown string to convert
|
|
1715
|
+
* @returns Plain text representation
|
|
1716
|
+
*/
|
|
1717
|
+
const toPlainString = (content) => {
|
|
1718
|
+
if (!content) {
|
|
1719
|
+
return "";
|
|
1720
|
+
}
|
|
1721
|
+
let text = content;
|
|
1722
|
+
// Remove code blocks
|
|
1723
|
+
text = text.replace(/```[\s\S]*?```/g, "");
|
|
1724
|
+
text = text.replace(/`([^`]+)`/g, "$1");
|
|
1725
|
+
// Remove images
|
|
1726
|
+
text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
|
|
1727
|
+
// Convert links to text only (keep link text, remove URL)
|
|
1728
|
+
text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
|
|
1729
|
+
// Remove headers (convert to plain text)
|
|
1730
|
+
text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
|
|
1731
|
+
// Remove bold and italic markers
|
|
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
|
+
text = text.replace(/__(.+?)__/g, "$1");
|
|
1737
|
+
text = text.replace(/_(.+?)_/g, "$1");
|
|
1738
|
+
// Remove strikethrough
|
|
1739
|
+
text = text.replace(/~~(.+?)~~/g, "$1");
|
|
1740
|
+
// Convert lists to plain text with bullets
|
|
1741
|
+
text = text.replace(/^\s*[-*+]\s+/gm, "• ");
|
|
1742
|
+
text = text.replace(/^\s*\d+\.\s+/gm, "• ");
|
|
1743
|
+
// Remove blockquotes
|
|
1744
|
+
text = text.replace(/^\s*>\s+/gm, "");
|
|
1745
|
+
// Remove horizontal rules
|
|
1746
|
+
text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
|
|
1747
|
+
// Remove HTML tags
|
|
1748
|
+
text = text.replace(/<[^>]+>/g, "");
|
|
1749
|
+
// Remove excessive whitespace and normalize line breaks
|
|
1750
|
+
text = text.replace(/\n[\s\n]*\n/g, "\n");
|
|
1751
|
+
text = text.replace(/[ \t]+/g, " ");
|
|
1752
|
+
// Remove all newline characters
|
|
1753
|
+
text = text.replace(/\n/g, " ");
|
|
1754
|
+
// Remove excessive spaces after newline removal
|
|
1755
|
+
text = text.replace(/\s+/g, " ");
|
|
1756
|
+
return text.trim();
|
|
1757
|
+
};
|
|
1758
|
+
|
|
1703
1759
|
const INTERVAL_MINUTES$1 = {
|
|
1704
1760
|
"1m": 1,
|
|
1705
1761
|
"3m": 3,
|
|
@@ -1708,6 +1764,7 @@ const INTERVAL_MINUTES$1 = {
|
|
|
1708
1764
|
"30m": 30,
|
|
1709
1765
|
"1h": 60,
|
|
1710
1766
|
};
|
|
1767
|
+
const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
1711
1768
|
const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
|
|
1712
1769
|
const errors = [];
|
|
1713
1770
|
// ПРОВЕРКА ОБЯЗАТЕЛЬНЫХ ПОЛЕЙ ISignalRow
|
|
@@ -1891,7 +1948,14 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1891
1948
|
}))) {
|
|
1892
1949
|
return null;
|
|
1893
1950
|
}
|
|
1894
|
-
const
|
|
1951
|
+
const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
|
|
1952
|
+
const signal = await Promise.race([
|
|
1953
|
+
self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
|
|
1954
|
+
functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
|
|
1955
|
+
]);
|
|
1956
|
+
if (typeof signal === "symbol") {
|
|
1957
|
+
throw new Error(`Timeout for ${self.params.method.context.strategyName} symbol=${self.params.execution.context.symbol}`);
|
|
1958
|
+
}
|
|
1895
1959
|
if (!signal) {
|
|
1896
1960
|
return null;
|
|
1897
1961
|
}
|
|
@@ -1912,7 +1976,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1912
1976
|
id: functoolsKit.randomString(),
|
|
1913
1977
|
priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
|
|
1914
1978
|
position: signal.position,
|
|
1915
|
-
note: signal.note,
|
|
1979
|
+
note: toPlainString(signal.note),
|
|
1916
1980
|
priceTakeProfit: signal.priceTakeProfit,
|
|
1917
1981
|
priceStopLoss: signal.priceStopLoss,
|
|
1918
1982
|
minuteEstimatedTime: signal.minuteEstimatedTime,
|
|
@@ -1932,7 +1996,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1932
1996
|
id: functoolsKit.randomString(),
|
|
1933
1997
|
priceOpen: signal.priceOpen,
|
|
1934
1998
|
position: signal.position,
|
|
1935
|
-
note: signal.note,
|
|
1999
|
+
note: toPlainString(signal.note),
|
|
1936
2000
|
priceTakeProfit: signal.priceTakeProfit,
|
|
1937
2001
|
priceStopLoss: signal.priceStopLoss,
|
|
1938
2002
|
minuteEstimatedTime: signal.minuteEstimatedTime,
|
|
@@ -1951,6 +2015,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
1951
2015
|
id: functoolsKit.randomString(),
|
|
1952
2016
|
priceOpen: currentPrice,
|
|
1953
2017
|
...signal,
|
|
2018
|
+
note: toPlainString(signal.note),
|
|
1954
2019
|
symbol: self.params.execution.context.symbol,
|
|
1955
2020
|
exchangeName: self.params.method.context.exchangeName,
|
|
1956
2021
|
strategyName: self.params.method.context.strategyName,
|
|
@@ -2176,6 +2241,8 @@ const RETURN_SCHEDULED_SIGNAL_ACTIVE_FN = async (self, scheduled, currentPrice)
|
|
|
2176
2241
|
strategyName: self.params.method.context.strategyName,
|
|
2177
2242
|
exchangeName: self.params.method.context.exchangeName,
|
|
2178
2243
|
symbol: self.params.execution.context.symbol,
|
|
2244
|
+
percentTp: 0,
|
|
2245
|
+
percentSl: 0,
|
|
2179
2246
|
};
|
|
2180
2247
|
if (self.params.callbacks?.onTick) {
|
|
2181
2248
|
self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
|
|
@@ -2279,7 +2346,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
2279
2346
|
self.params.callbacks.onClose(self.params.execution.context.symbol, signal, currentPrice, self.params.execution.context.backtest);
|
|
2280
2347
|
}
|
|
2281
2348
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
2282
|
-
await self.params.partial.clear(self.params.execution.context.symbol, signal, currentPrice);
|
|
2349
|
+
await self.params.partial.clear(self.params.execution.context.symbol, signal, currentPrice, self.params.execution.context.backtest);
|
|
2283
2350
|
await self.params.risk.removeSignal(self.params.execution.context.symbol, {
|
|
2284
2351
|
strategyName: self.params.method.context.strategyName,
|
|
2285
2352
|
riskName: self.params.riskName,
|
|
@@ -2302,31 +2369,56 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
|
|
|
2302
2369
|
return result;
|
|
2303
2370
|
};
|
|
2304
2371
|
const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
2305
|
-
|
|
2372
|
+
let percentTp = 0;
|
|
2373
|
+
let percentSl = 0;
|
|
2374
|
+
// Calculate percentage of path to TP/SL for partial fill/loss callbacks
|
|
2306
2375
|
{
|
|
2307
|
-
let revenuePercent = 0;
|
|
2308
2376
|
if (signal.position === "long") {
|
|
2309
|
-
// For long:
|
|
2310
|
-
|
|
2377
|
+
// For long: calculate progress towards TP or SL
|
|
2378
|
+
const currentDistance = currentPrice - signal.priceOpen;
|
|
2379
|
+
if (currentDistance > 0) {
|
|
2380
|
+
// Moving towards TP
|
|
2381
|
+
const tpDistance = signal.priceTakeProfit - signal.priceOpen;
|
|
2382
|
+
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
2383
|
+
percentTp = Math.min(progressPercent, 100);
|
|
2384
|
+
await self.params.partial.profit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest, self.params.execution.context.when);
|
|
2385
|
+
if (self.params.callbacks?.onPartialProfit) {
|
|
2386
|
+
self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
else if (currentDistance < 0) {
|
|
2390
|
+
// Moving towards SL
|
|
2391
|
+
const slDistance = signal.priceOpen - signal.priceStopLoss;
|
|
2392
|
+
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
2393
|
+
percentSl = Math.min(progressPercent, 100);
|
|
2394
|
+
await self.params.partial.loss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest, self.params.execution.context.when);
|
|
2395
|
+
if (self.params.callbacks?.onPartialLoss) {
|
|
2396
|
+
self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2311
2399
|
}
|
|
2312
2400
|
else if (signal.position === "short") {
|
|
2313
|
-
// For short:
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
self.params.callbacks
|
|
2401
|
+
// For short: calculate progress towards TP or SL
|
|
2402
|
+
const currentDistance = signal.priceOpen - currentPrice;
|
|
2403
|
+
if (currentDistance > 0) {
|
|
2404
|
+
// Moving towards TP
|
|
2405
|
+
const tpDistance = signal.priceOpen - signal.priceTakeProfit;
|
|
2406
|
+
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
2407
|
+
percentTp = Math.min(progressPercent, 100);
|
|
2408
|
+
await self.params.partial.profit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest, self.params.execution.context.when);
|
|
2409
|
+
if (self.params.callbacks?.onPartialProfit) {
|
|
2410
|
+
self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, currentPrice, percentTp, self.params.execution.context.backtest);
|
|
2411
|
+
}
|
|
2322
2412
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
self.params.callbacks
|
|
2413
|
+
if (currentDistance < 0) {
|
|
2414
|
+
// Moving towards SL
|
|
2415
|
+
const slDistance = signal.priceStopLoss - signal.priceOpen;
|
|
2416
|
+
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
2417
|
+
percentSl = Math.min(progressPercent, 100);
|
|
2418
|
+
await self.params.partial.loss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest, self.params.execution.context.when);
|
|
2419
|
+
if (self.params.callbacks?.onPartialLoss) {
|
|
2420
|
+
self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, currentPrice, percentSl, self.params.execution.context.backtest);
|
|
2421
|
+
}
|
|
2330
2422
|
}
|
|
2331
2423
|
}
|
|
2332
2424
|
}
|
|
@@ -2337,6 +2429,8 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
2337
2429
|
strategyName: self.params.method.context.strategyName,
|
|
2338
2430
|
exchangeName: self.params.method.context.exchangeName,
|
|
2339
2431
|
symbol: self.params.execution.context.symbol,
|
|
2432
|
+
percentTp,
|
|
2433
|
+
percentSl,
|
|
2340
2434
|
};
|
|
2341
2435
|
if (self.params.callbacks?.onTick) {
|
|
2342
2436
|
self.params.callbacks.onTick(self.params.execution.context.symbol, result, self.params.execution.context.backtest);
|
|
@@ -2458,7 +2552,7 @@ const CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, signal, averagePrice, c
|
|
|
2458
2552
|
self.params.callbacks.onClose(self.params.execution.context.symbol, signal, averagePrice, self.params.execution.context.backtest);
|
|
2459
2553
|
}
|
|
2460
2554
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
2461
|
-
await self.params.partial.clear(self.params.execution.context.symbol, signal, averagePrice);
|
|
2555
|
+
await self.params.partial.clear(self.params.execution.context.symbol, signal, averagePrice, self.params.execution.context.backtest);
|
|
2462
2556
|
await self.params.risk.removeSignal(self.params.execution.context.symbol, {
|
|
2463
2557
|
strategyName: self.params.method.context.strategyName,
|
|
2464
2558
|
riskName: self.params.riskName,
|
|
@@ -2601,29 +2695,50 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
2601
2695
|
return await CLOSE_PENDING_SIGNAL_IN_BACKTEST_FN(self, signal, closePrice, closeReason, currentCandleTimestamp);
|
|
2602
2696
|
}
|
|
2603
2697
|
// Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
|
|
2604
|
-
// Calculate
|
|
2698
|
+
// Calculate percentage of path to TP/SL
|
|
2605
2699
|
{
|
|
2606
|
-
let revenuePercent = 0;
|
|
2607
2700
|
if (signal.position === "long") {
|
|
2608
|
-
|
|
2701
|
+
// For long: calculate progress towards TP or SL
|
|
2702
|
+
const currentDistance = averagePrice - signal.priceOpen;
|
|
2703
|
+
if (currentDistance > 0) {
|
|
2704
|
+
// Moving towards TP
|
|
2705
|
+
const tpDistance = signal.priceTakeProfit - signal.priceOpen;
|
|
2706
|
+
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
2707
|
+
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);
|
|
2708
|
+
if (self.params.callbacks?.onPartialProfit) {
|
|
2709
|
+
self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
|
|
2710
|
+
}
|
|
2711
|
+
}
|
|
2712
|
+
else if (currentDistance < 0) {
|
|
2713
|
+
// Moving towards SL
|
|
2714
|
+
const slDistance = signal.priceOpen - signal.priceStopLoss;
|
|
2715
|
+
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
2716
|
+
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);
|
|
2717
|
+
if (self.params.callbacks?.onPartialLoss) {
|
|
2718
|
+
self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2609
2721
|
}
|
|
2610
2722
|
else if (signal.position === "short") {
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
self.params.callbacks
|
|
2723
|
+
// For short: calculate progress towards TP or SL
|
|
2724
|
+
const currentDistance = signal.priceOpen - averagePrice;
|
|
2725
|
+
if (currentDistance > 0) {
|
|
2726
|
+
// Moving towards TP
|
|
2727
|
+
const tpDistance = signal.priceOpen - signal.priceTakeProfit;
|
|
2728
|
+
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
2729
|
+
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);
|
|
2730
|
+
if (self.params.callbacks?.onPartialProfit) {
|
|
2731
|
+
self.params.callbacks.onPartialProfit(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
|
|
2732
|
+
}
|
|
2619
2733
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2734
|
+
if (currentDistance < 0) {
|
|
2735
|
+
// Moving towards SL
|
|
2736
|
+
const slDistance = signal.priceStopLoss - signal.priceOpen;
|
|
2737
|
+
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
2738
|
+
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);
|
|
2739
|
+
if (self.params.callbacks?.onPartialLoss) {
|
|
2740
|
+
self.params.callbacks.onPartialLoss(self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), self.params.execution.context.backtest);
|
|
2741
|
+
}
|
|
2627
2742
|
}
|
|
2628
2743
|
}
|
|
2629
2744
|
}
|
|
@@ -2912,6 +3027,8 @@ class ClientStrategy {
|
|
|
2912
3027
|
action: "active",
|
|
2913
3028
|
signal: scheduled,
|
|
2914
3029
|
currentPrice: lastPrice,
|
|
3030
|
+
percentSl: 0,
|
|
3031
|
+
percentTp: 0,
|
|
2915
3032
|
strategyName: this.params.method.context.strategyName,
|
|
2916
3033
|
exchangeName: this.params.method.context.exchangeName,
|
|
2917
3034
|
symbol: this.params.execution.context.symbol,
|
|
@@ -5613,7 +5730,7 @@ const columns$4 = [
|
|
|
5613
5730
|
{
|
|
5614
5731
|
key: "note",
|
|
5615
5732
|
label: "Note",
|
|
5616
|
-
format: (data) => data.signal.note ?? "N/A",
|
|
5733
|
+
format: (data) => toPlainString(data.signal.note ?? "N/A"),
|
|
5617
5734
|
},
|
|
5618
5735
|
{
|
|
5619
5736
|
key: "openPrice",
|
|
@@ -5760,14 +5877,33 @@ let ReportStorage$4 = class ReportStorage {
|
|
|
5760
5877
|
async getReport(strategyName) {
|
|
5761
5878
|
const stats = await this.getData();
|
|
5762
5879
|
if (stats.totalSignals === 0) {
|
|
5763
|
-
return
|
|
5880
|
+
return [
|
|
5881
|
+
`# Backtest Report: ${strategyName}`,
|
|
5882
|
+
"",
|
|
5883
|
+
"No signals closed yet."
|
|
5884
|
+
].join("\n");
|
|
5764
5885
|
}
|
|
5765
5886
|
const header = columns$4.map((col) => col.label);
|
|
5766
5887
|
const separator = columns$4.map(() => "---");
|
|
5767
5888
|
const rows = this._signalList.map((closedSignal) => columns$4.map((col) => col.format(closedSignal)));
|
|
5768
5889
|
const tableData = [header, separator, ...rows];
|
|
5769
|
-
const table =
|
|
5770
|
-
return
|
|
5890
|
+
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
5891
|
+
return [
|
|
5892
|
+
`# Backtest Report: ${strategyName}`,
|
|
5893
|
+
"",
|
|
5894
|
+
table,
|
|
5895
|
+
"",
|
|
5896
|
+
`**Total signals:** ${stats.totalSignals}`,
|
|
5897
|
+
`**Closed signals:** ${stats.totalSignals}`,
|
|
5898
|
+
`**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`,
|
|
5899
|
+
`**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`,
|
|
5900
|
+
`**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`,
|
|
5901
|
+
`**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`,
|
|
5902
|
+
`**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
5903
|
+
`**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
5904
|
+
`**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
|
|
5905
|
+
`**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
|
|
5906
|
+
].join("\n");
|
|
5771
5907
|
}
|
|
5772
5908
|
/**
|
|
5773
5909
|
* Saves strategy report to disk.
|
|
@@ -6025,7 +6161,7 @@ const columns$3 = [
|
|
|
6025
6161
|
{
|
|
6026
6162
|
key: "note",
|
|
6027
6163
|
label: "Note",
|
|
6028
|
-
format: (data) => data.note ?? "N/A",
|
|
6164
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
6029
6165
|
},
|
|
6030
6166
|
{
|
|
6031
6167
|
key: "currentPrice",
|
|
@@ -6049,6 +6185,16 @@ const columns$3 = [
|
|
|
6049
6185
|
label: "Stop Loss",
|
|
6050
6186
|
format: (data) => data.stopLoss !== undefined ? `${data.stopLoss.toFixed(8)} USD` : "N/A",
|
|
6051
6187
|
},
|
|
6188
|
+
{
|
|
6189
|
+
key: "percentTp",
|
|
6190
|
+
label: "% to TP",
|
|
6191
|
+
format: (data) => data.percentTp !== undefined ? `${data.percentTp.toFixed(2)}%` : "N/A",
|
|
6192
|
+
},
|
|
6193
|
+
{
|
|
6194
|
+
key: "percentSl",
|
|
6195
|
+
label: "% to SL",
|
|
6196
|
+
format: (data) => data.percentSl !== undefined ? `${data.percentSl.toFixed(2)}%` : "N/A",
|
|
6197
|
+
},
|
|
6052
6198
|
{
|
|
6053
6199
|
key: "pnl",
|
|
6054
6200
|
label: "PNL (net)",
|
|
@@ -6151,6 +6297,8 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6151
6297
|
openPrice: data.signal.priceOpen,
|
|
6152
6298
|
takeProfit: data.signal.priceTakeProfit,
|
|
6153
6299
|
stopLoss: data.signal.priceStopLoss,
|
|
6300
|
+
percentTp: data.percentTp,
|
|
6301
|
+
percentSl: data.percentSl,
|
|
6154
6302
|
};
|
|
6155
6303
|
// Replace existing event or add new one
|
|
6156
6304
|
if (existingIndex !== -1) {
|
|
@@ -6292,14 +6440,33 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
6292
6440
|
async getReport(strategyName) {
|
|
6293
6441
|
const stats = await this.getData();
|
|
6294
6442
|
if (stats.totalEvents === 0) {
|
|
6295
|
-
return
|
|
6443
|
+
return [
|
|
6444
|
+
`# Live Trading Report: ${strategyName}`,
|
|
6445
|
+
"",
|
|
6446
|
+
"No events recorded yet."
|
|
6447
|
+
].join("\n");
|
|
6296
6448
|
}
|
|
6297
6449
|
const header = columns$3.map((col) => col.label);
|
|
6298
6450
|
const separator = columns$3.map(() => "---");
|
|
6299
6451
|
const rows = this._eventList.map((event) => columns$3.map((col) => col.format(event)));
|
|
6300
6452
|
const tableData = [header, separator, ...rows];
|
|
6301
|
-
const table =
|
|
6302
|
-
return
|
|
6453
|
+
const table = tableData.map(row => `| ${row.join(" | ")} |`).join("\n");
|
|
6454
|
+
return [
|
|
6455
|
+
`# Live Trading Report: ${strategyName}`,
|
|
6456
|
+
"",
|
|
6457
|
+
table,
|
|
6458
|
+
"",
|
|
6459
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
6460
|
+
`**Closed signals:** ${stats.totalClosed}`,
|
|
6461
|
+
`**Win rate:** ${stats.winRate === null ? "N/A" : `${stats.winRate.toFixed(2)}% (${stats.winCount}W / ${stats.lossCount}L) (higher is better)`}`,
|
|
6462
|
+
`**Average PNL:** ${stats.avgPnl === null ? "N/A" : `${stats.avgPnl > 0 ? "+" : ""}${stats.avgPnl.toFixed(2)}% (higher is better)`}`,
|
|
6463
|
+
`**Total PNL:** ${stats.totalPnl === null ? "N/A" : `${stats.totalPnl > 0 ? "+" : ""}${stats.totalPnl.toFixed(2)}% (higher is better)`}`,
|
|
6464
|
+
`**Standard Deviation:** ${stats.stdDev === null ? "N/A" : `${stats.stdDev.toFixed(3)}% (lower is better)`}`,
|
|
6465
|
+
`**Sharpe Ratio:** ${stats.sharpeRatio === null ? "N/A" : `${stats.sharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
6466
|
+
`**Annualized Sharpe Ratio:** ${stats.annualizedSharpeRatio === null ? "N/A" : `${stats.annualizedSharpeRatio.toFixed(3)} (higher is better)`}`,
|
|
6467
|
+
`**Certainty Ratio:** ${stats.certaintyRatio === null ? "N/A" : `${stats.certaintyRatio.toFixed(3)} (higher is better)`}`,
|
|
6468
|
+
`**Expected Yearly Returns:** ${stats.expectedYearlyReturns === null ? "N/A" : `${stats.expectedYearlyReturns > 0 ? "+" : ""}${stats.expectedYearlyReturns.toFixed(2)}% (higher is better)`}`,
|
|
6469
|
+
].join("\n");
|
|
6303
6470
|
}
|
|
6304
6471
|
/**
|
|
6305
6472
|
* Saves strategy report to disk.
|
|
@@ -6552,7 +6719,7 @@ const columns$2 = [
|
|
|
6552
6719
|
{
|
|
6553
6720
|
key: "note",
|
|
6554
6721
|
label: "Note",
|
|
6555
|
-
format: (data) => data.note ?? "N/A",
|
|
6722
|
+
format: (data) => toPlainString(data.note ?? "N/A"),
|
|
6556
6723
|
},
|
|
6557
6724
|
{
|
|
6558
6725
|
key: "currentPrice",
|
|
@@ -6696,14 +6863,28 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
6696
6863
|
async getReport(strategyName) {
|
|
6697
6864
|
const stats = await this.getData();
|
|
6698
6865
|
if (stats.totalEvents === 0) {
|
|
6699
|
-
return
|
|
6866
|
+
return [
|
|
6867
|
+
`# Scheduled Signals Report: ${strategyName}`,
|
|
6868
|
+
"",
|
|
6869
|
+
"No scheduled signals recorded yet."
|
|
6870
|
+
].join("\n");
|
|
6700
6871
|
}
|
|
6701
6872
|
const header = columns$2.map((col) => col.label);
|
|
6702
6873
|
const separator = columns$2.map(() => "---");
|
|
6703
6874
|
const rows = this._eventList.map((event) => columns$2.map((col) => col.format(event)));
|
|
6704
6875
|
const tableData = [header, separator, ...rows];
|
|
6705
|
-
const table =
|
|
6706
|
-
return
|
|
6876
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
6877
|
+
return [
|
|
6878
|
+
`# Scheduled Signals Report: ${strategyName}`,
|
|
6879
|
+
"",
|
|
6880
|
+
table,
|
|
6881
|
+
"",
|
|
6882
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
6883
|
+
`**Scheduled signals:** ${stats.totalScheduled}`,
|
|
6884
|
+
`**Cancelled signals:** ${stats.totalCancelled}`,
|
|
6885
|
+
`**Cancellation rate:** ${stats.cancellationRate === null ? "N/A" : `${stats.cancellationRate.toFixed(2)}% (lower is better)`}`,
|
|
6886
|
+
`**Average wait time (cancelled):** ${stats.avgWaitTime === null ? "N/A" : `${stats.avgWaitTime.toFixed(2)} minutes`}`
|
|
6887
|
+
].join("\n");
|
|
6707
6888
|
}
|
|
6708
6889
|
/**
|
|
6709
6890
|
* Saves strategy report to disk.
|
|
@@ -7021,7 +7202,11 @@ class PerformanceStorage {
|
|
|
7021
7202
|
async getReport(strategyName) {
|
|
7022
7203
|
const stats = await this.getData(strategyName);
|
|
7023
7204
|
if (stats.totalEvents === 0) {
|
|
7024
|
-
return
|
|
7205
|
+
return [
|
|
7206
|
+
`# Performance Report: ${strategyName}`,
|
|
7207
|
+
"",
|
|
7208
|
+
"No performance metrics recorded yet."
|
|
7209
|
+
].join("\n");
|
|
7025
7210
|
}
|
|
7026
7211
|
// Sort metrics by total duration (descending) to show bottlenecks first
|
|
7027
7212
|
const sortedMetrics = Object.values(stats.metricStats).sort((a, b) => b.totalDuration - a.totalDuration);
|
|
@@ -7058,13 +7243,29 @@ class PerformanceStorage {
|
|
|
7058
7243
|
metric.maxWaitTime.toFixed(2),
|
|
7059
7244
|
]);
|
|
7060
7245
|
const summaryTableData = [summaryHeader, summarySeparator, ...summaryRows];
|
|
7061
|
-
const summaryTable =
|
|
7246
|
+
const summaryTable = summaryTableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7062
7247
|
// Calculate percentage of total time for each metric
|
|
7063
7248
|
const percentages = sortedMetrics.map((metric) => {
|
|
7064
7249
|
const pct = (metric.totalDuration / stats.totalDuration) * 100;
|
|
7065
7250
|
return `- **${metric.metricType}**: ${pct.toFixed(1)}% (${metric.totalDuration.toFixed(2)}ms total)`;
|
|
7066
7251
|
});
|
|
7067
|
-
return
|
|
7252
|
+
return [
|
|
7253
|
+
`# Performance Report: ${strategyName}`,
|
|
7254
|
+
"",
|
|
7255
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
7256
|
+
`**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
|
|
7257
|
+
`**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
|
|
7258
|
+
"",
|
|
7259
|
+
"## Time Distribution",
|
|
7260
|
+
"",
|
|
7261
|
+
percentages.join("\n"),
|
|
7262
|
+
"",
|
|
7263
|
+
"## Detailed Metrics",
|
|
7264
|
+
"",
|
|
7265
|
+
summaryTable,
|
|
7266
|
+
"",
|
|
7267
|
+
"**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."
|
|
7268
|
+
].join("\n");
|
|
7068
7269
|
}
|
|
7069
7270
|
/**
|
|
7070
7271
|
* Saves performance report to disk.
|
|
@@ -7468,7 +7669,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7468
7669
|
// Build table rows
|
|
7469
7670
|
const rows = topStrategies.map((result, index) => columns.map((col) => col.format(result, index)));
|
|
7470
7671
|
const tableData = [header, separator, ...rows];
|
|
7471
|
-
return
|
|
7672
|
+
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7472
7673
|
}
|
|
7473
7674
|
/**
|
|
7474
7675
|
* Generates PNL table showing all closed signals across all strategies (View).
|
|
@@ -7505,7 +7706,7 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7505
7706
|
// Build table rows
|
|
7506
7707
|
const rows = allSignals.map((signal) => pnlColumns.map((col) => col.format(signal)));
|
|
7507
7708
|
const tableData = [header, separator, ...rows];
|
|
7508
|
-
return
|
|
7709
|
+
return tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
7509
7710
|
}
|
|
7510
7711
|
/**
|
|
7511
7712
|
* Generates markdown report with all strategy results (View).
|
|
@@ -7520,7 +7721,30 @@ let ReportStorage$1 = class ReportStorage {
|
|
|
7520
7721
|
const results = await this.getData(symbol, metric, context);
|
|
7521
7722
|
// Get total signals for best strategy
|
|
7522
7723
|
const bestStrategySignals = results.bestStats?.totalSignals ?? 0;
|
|
7523
|
-
return
|
|
7724
|
+
return [
|
|
7725
|
+
`# Walker Comparison Report: ${results.walkerName}`,
|
|
7726
|
+
"",
|
|
7727
|
+
`**Symbol:** ${results.symbol}`,
|
|
7728
|
+
`**Exchange:** ${results.exchangeName}`,
|
|
7729
|
+
`**Frame:** ${results.frameName}`,
|
|
7730
|
+
`**Optimization Metric:** ${results.metric}`,
|
|
7731
|
+
`**Strategies Tested:** ${results.totalStrategies}`,
|
|
7732
|
+
"",
|
|
7733
|
+
`## Best Strategy: ${results.bestStrategy}`,
|
|
7734
|
+
"",
|
|
7735
|
+
`**Best ${results.metric}:** ${formatMetric(results.bestMetric)}`,
|
|
7736
|
+
`**Total Signals:** ${bestStrategySignals}`,
|
|
7737
|
+
"",
|
|
7738
|
+
"## Top Strategies Comparison",
|
|
7739
|
+
"",
|
|
7740
|
+
this.getComparisonTable(metric, 10),
|
|
7741
|
+
"",
|
|
7742
|
+
"## All Signals (PNL Table)",
|
|
7743
|
+
"",
|
|
7744
|
+
this.getPnlTable(),
|
|
7745
|
+
"",
|
|
7746
|
+
"**Note:** Higher values are better for all metrics except Standard Deviation (lower is better)."
|
|
7747
|
+
].join("\n");
|
|
7524
7748
|
}
|
|
7525
7749
|
/**
|
|
7526
7750
|
* Saves walker report to disk.
|
|
@@ -8034,14 +8258,24 @@ class HeatmapStorage {
|
|
|
8034
8258
|
async getReport(strategyName) {
|
|
8035
8259
|
const data = await this.getData();
|
|
8036
8260
|
if (data.symbols.length === 0) {
|
|
8037
|
-
return
|
|
8261
|
+
return [
|
|
8262
|
+
`# Portfolio Heatmap: ${strategyName}`,
|
|
8263
|
+
"",
|
|
8264
|
+
"*No data available*"
|
|
8265
|
+
].join("\n");
|
|
8038
8266
|
}
|
|
8039
8267
|
const header = columns$1.map((col) => col.label);
|
|
8040
8268
|
const separator = columns$1.map(() => "---");
|
|
8041
8269
|
const rows = data.symbols.map((row) => columns$1.map((col) => col.format(row)));
|
|
8042
8270
|
const tableData = [header, separator, ...rows];
|
|
8043
|
-
const table =
|
|
8044
|
-
return
|
|
8271
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
8272
|
+
return [
|
|
8273
|
+
`# Portfolio Heatmap: ${strategyName}`,
|
|
8274
|
+
"",
|
|
8275
|
+
`**Total Symbols:** ${data.totalSymbols} | **Portfolio PNL:** ${data.portfolioTotalPnl !== null ? functoolsKit.str(data.portfolioTotalPnl, "%+.2f%%") : "N/A"} | **Portfolio Sharpe:** ${data.portfolioSharpeRatio !== null ? functoolsKit.str(data.portfolioSharpeRatio, "%.2f") : "N/A"} | **Total Trades:** ${data.portfolioTotalTrades}`,
|
|
8276
|
+
"",
|
|
8277
|
+
table
|
|
8278
|
+
].join("\n");
|
|
8045
8279
|
}
|
|
8046
8280
|
/**
|
|
8047
8281
|
* Saves heatmap report to disk.
|
|
@@ -8764,6 +8998,8 @@ class OptimizerTemplateService {
|
|
|
8764
8998
|
interval,
|
|
8765
8999
|
prompt,
|
|
8766
9000
|
});
|
|
9001
|
+
// Convert prompt to plain text first
|
|
9002
|
+
const plainPrompt = toPlainString(prompt);
|
|
8767
9003
|
// Escape special characters to prevent code injection
|
|
8768
9004
|
const escapedStrategyName = String(strategyName)
|
|
8769
9005
|
.replace(/\\/g, '\\\\')
|
|
@@ -8771,7 +9007,7 @@ class OptimizerTemplateService {
|
|
|
8771
9007
|
const escapedInterval = String(interval)
|
|
8772
9008
|
.replace(/\\/g, '\\\\')
|
|
8773
9009
|
.replace(/"/g, '\\"');
|
|
8774
|
-
const escapedPrompt = String(
|
|
9010
|
+
const escapedPrompt = String(plainPrompt)
|
|
8775
9011
|
.replace(/\\/g, '\\\\')
|
|
8776
9012
|
.replace(/`/g, '\\`')
|
|
8777
9013
|
.replace(/\$/g, '\\$');
|
|
@@ -9952,7 +10188,7 @@ const HANDLE_PROFIT_FN = async (symbol, data, currentPrice, revenuePercent, back
|
|
|
9952
10188
|
}
|
|
9953
10189
|
}
|
|
9954
10190
|
if (shouldPersist) {
|
|
9955
|
-
await self._persistState(symbol);
|
|
10191
|
+
await self._persistState(symbol, backtest);
|
|
9956
10192
|
}
|
|
9957
10193
|
};
|
|
9958
10194
|
/**
|
|
@@ -9999,7 +10235,7 @@ const HANDLE_LOSS_FN = async (symbol, data, currentPrice, lossPercent, backtest,
|
|
|
9999
10235
|
}
|
|
10000
10236
|
}
|
|
10001
10237
|
if (shouldPersist) {
|
|
10002
|
-
await self._persistState(symbol);
|
|
10238
|
+
await self._persistState(symbol, backtest);
|
|
10003
10239
|
}
|
|
10004
10240
|
};
|
|
10005
10241
|
/**
|
|
@@ -10135,7 +10371,10 @@ class ClientPartial {
|
|
|
10135
10371
|
* @param symbol - Trading pair symbol
|
|
10136
10372
|
* @returns Promise that resolves when persistence is complete
|
|
10137
10373
|
*/
|
|
10138
|
-
async _persistState(symbol) {
|
|
10374
|
+
async _persistState(symbol, backtest) {
|
|
10375
|
+
if (backtest) {
|
|
10376
|
+
return;
|
|
10377
|
+
}
|
|
10139
10378
|
this.params.logger.debug("ClientPartial persistState", { symbol });
|
|
10140
10379
|
if (this._states === NEED_FETCH) {
|
|
10141
10380
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
@@ -10258,7 +10497,7 @@ class ClientPartial {
|
|
|
10258
10497
|
* // Cleanup: PartialConnectionService.getPartial.clear(signal.id)
|
|
10259
10498
|
* ```
|
|
10260
10499
|
*/
|
|
10261
|
-
async clear(symbol, data, priceClose) {
|
|
10500
|
+
async clear(symbol, data, priceClose, backtest) {
|
|
10262
10501
|
this.params.logger.log("ClientPartial clear", {
|
|
10263
10502
|
symbol,
|
|
10264
10503
|
data,
|
|
@@ -10268,7 +10507,7 @@ class ClientPartial {
|
|
|
10268
10507
|
throw new Error("ClientPartial not initialized. Call waitForInit() before using.");
|
|
10269
10508
|
}
|
|
10270
10509
|
this._states.delete(data.id);
|
|
10271
|
-
await this._persistState(symbol);
|
|
10510
|
+
await this._persistState(symbol, backtest);
|
|
10272
10511
|
}
|
|
10273
10512
|
}
|
|
10274
10513
|
|
|
@@ -10439,7 +10678,7 @@ class PartialConnectionService {
|
|
|
10439
10678
|
* @param priceClose - Final closing price
|
|
10440
10679
|
* @returns Promise that resolves when clear is complete
|
|
10441
10680
|
*/
|
|
10442
|
-
this.clear = async (symbol, data, priceClose) => {
|
|
10681
|
+
this.clear = async (symbol, data, priceClose, backtest) => {
|
|
10443
10682
|
this.loggerService.log("partialConnectionService profit", {
|
|
10444
10683
|
symbol,
|
|
10445
10684
|
data,
|
|
@@ -10447,7 +10686,7 @@ class PartialConnectionService {
|
|
|
10447
10686
|
});
|
|
10448
10687
|
const partial = this.getPartial(data.id);
|
|
10449
10688
|
await partial.waitForInit(symbol);
|
|
10450
|
-
await partial.clear(symbol, data, priceClose);
|
|
10689
|
+
await partial.clear(symbol, data, priceClose, backtest);
|
|
10451
10690
|
this.getPartial.clear(data.id);
|
|
10452
10691
|
};
|
|
10453
10692
|
}
|
|
@@ -10594,14 +10833,26 @@ class ReportStorage {
|
|
|
10594
10833
|
async getReport(symbol, strategyName) {
|
|
10595
10834
|
const stats = await this.getData();
|
|
10596
10835
|
if (stats.totalEvents === 0) {
|
|
10597
|
-
return
|
|
10836
|
+
return [
|
|
10837
|
+
`# Partial Profit/Loss Report: ${symbol}:${strategyName}`,
|
|
10838
|
+
"",
|
|
10839
|
+
"No partial profit/loss events recorded yet."
|
|
10840
|
+
].join("\n");
|
|
10598
10841
|
}
|
|
10599
10842
|
const header = columns.map((col) => col.label);
|
|
10600
10843
|
const separator = columns.map(() => "---");
|
|
10601
10844
|
const rows = this._eventList.map((event) => columns.map((col) => col.format(event)));
|
|
10602
10845
|
const tableData = [header, separator, ...rows];
|
|
10603
|
-
const table =
|
|
10604
|
-
return
|
|
10846
|
+
const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
|
|
10847
|
+
return [
|
|
10848
|
+
`# Partial Profit/Loss Report: ${symbol}:${strategyName}`,
|
|
10849
|
+
"",
|
|
10850
|
+
table,
|
|
10851
|
+
"",
|
|
10852
|
+
`**Total events:** ${stats.totalEvents}`,
|
|
10853
|
+
`**Profit events:** ${stats.totalProfit}`,
|
|
10854
|
+
`**Loss events:** ${stats.totalLoss}`
|
|
10855
|
+
].join("\n");
|
|
10605
10856
|
}
|
|
10606
10857
|
/**
|
|
10607
10858
|
* Saves symbol-strategy report to disk.
|
|
@@ -10916,13 +11167,14 @@ class PartialGlobalService {
|
|
|
10916
11167
|
* @param priceClose - Final closing price
|
|
10917
11168
|
* @returns Promise that resolves when clear is complete
|
|
10918
11169
|
*/
|
|
10919
|
-
this.clear = async (symbol, data, priceClose) => {
|
|
11170
|
+
this.clear = async (symbol, data, priceClose, backtest) => {
|
|
10920
11171
|
this.loggerService.log("partialGlobalService profit", {
|
|
10921
11172
|
symbol,
|
|
10922
11173
|
data,
|
|
10923
11174
|
priceClose,
|
|
11175
|
+
backtest,
|
|
10924
11176
|
});
|
|
10925
|
-
return await this.partialConnectionService.clear(symbol, data, priceClose);
|
|
11177
|
+
return await this.partialConnectionService.clear(symbol, data, priceClose, backtest);
|
|
10926
11178
|
};
|
|
10927
11179
|
}
|
|
10928
11180
|
}
|