backtest-kit 6.9.0 → 6.11.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.mjs CHANGED
@@ -4127,53 +4127,6 @@ const toProfitLossDto = (signal, priceClose) => {
4127
4127
  };
4128
4128
  };
4129
4129
 
4130
- /**
4131
- * Converts markdown content to plain text with minimal formatting
4132
- * @param content - Markdown string to convert
4133
- * @returns Plain text representation
4134
- */
4135
- const toPlainString = (content) => {
4136
- if (!content) {
4137
- return "";
4138
- }
4139
- let text = content;
4140
- // Remove code blocks
4141
- text = text.replace(/```[\s\S]*?```/g, "");
4142
- text = text.replace(/`([^`]+)`/g, "$1");
4143
- // Remove images
4144
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
4145
- // Convert links to text only (keep link text, remove URL)
4146
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
4147
- // Remove headers (convert to plain text)
4148
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
4149
- // Remove bold and italic markers
4150
- text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
4151
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4152
- text = text.replace(/\*(.+?)\*/g, "$1");
4153
- text = text.replace(/___(.+?)___/g, "$1");
4154
- text = text.replace(/__(.+?)__/g, "$1");
4155
- text = text.replace(/_(.+?)_/g, "$1");
4156
- // Remove strikethrough
4157
- text = text.replace(/~~(.+?)~~/g, "$1");
4158
- // Convert lists to plain text with bullets
4159
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
4160
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
4161
- // Remove blockquotes
4162
- text = text.replace(/^\s*>\s+/gm, "");
4163
- // Remove horizontal rules
4164
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
4165
- // Remove HTML tags
4166
- text = text.replace(/<[^>]+>/g, "");
4167
- // Remove excessive whitespace and normalize line breaks
4168
- text = text.replace(/\n[\s\n]*\n/g, "\n");
4169
- text = text.replace(/[ \t]+/g, " ");
4170
- // Remove all newline characters
4171
- text = text.replace(/\n/g, " ");
4172
- // Remove excessive spaces after newline removal
4173
- text = text.replace(/\s+/g, " ");
4174
- return text.trim();
4175
- };
4176
-
4177
4130
  /**
4178
4131
  * Returns the total closed state of a position using costBasisAtClose snapshots.
4179
4132
  *
@@ -4808,6 +4761,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4808
4761
  originalPriceOpen: publicSignal.originalPriceOpen,
4809
4762
  scheduledAt: publicSignal.scheduledAt,
4810
4763
  pendingAt: publicSignal.pendingAt,
4764
+ note: publicSignal.note,
4811
4765
  });
4812
4766
  continue;
4813
4767
  }
@@ -4835,6 +4789,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4835
4789
  originalPriceOpen: publicSignal.originalPriceOpen,
4836
4790
  scheduledAt: publicSignal.scheduledAt,
4837
4791
  pendingAt: publicSignal.pendingAt,
4792
+ note: publicSignal.note,
4838
4793
  });
4839
4794
  continue;
4840
4795
  }
@@ -4861,6 +4816,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4861
4816
  originalPriceOpen: publicSignal.originalPriceOpen,
4862
4817
  scheduledAt: publicSignal.scheduledAt,
4863
4818
  pendingAt: publicSignal.pendingAt,
4819
+ note: publicSignal.note,
4864
4820
  });
4865
4821
  continue;
4866
4822
  }
@@ -4888,6 +4844,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4888
4844
  originalPriceOpen: publicSignal.originalPriceOpen,
4889
4845
  scheduledAt: publicSignal.scheduledAt,
4890
4846
  pendingAt: publicSignal.pendingAt,
4847
+ note: publicSignal.note,
4891
4848
  });
4892
4849
  continue;
4893
4850
  }
@@ -4915,6 +4872,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4915
4872
  originalPriceOpen: publicSignal.originalPriceOpen,
4916
4873
  scheduledAt: publicSignal.scheduledAt,
4917
4874
  pendingAt: publicSignal.pendingAt,
4875
+ note: publicSignal.note,
4918
4876
  });
4919
4877
  continue;
4920
4878
  }
@@ -4944,6 +4902,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4944
4902
  originalPriceOpen: publicSignal.originalPriceOpen,
4945
4903
  scheduledAt: publicSignal.scheduledAt,
4946
4904
  pendingAt: publicSignal.pendingAt,
4905
+ note: publicSignal.note,
4947
4906
  });
4948
4907
  continue;
4949
4908
  }
@@ -5042,7 +5001,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5042
5001
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
5043
5002
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
5044
5003
  const signal = await Promise.race([
5045
- self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
5004
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
5046
5005
  sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
5047
5006
  ]);
5048
5007
  if (typeof signal === "symbol") {
@@ -5072,7 +5031,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5072
5031
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5073
5032
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
5074
5033
  position: signal.position,
5075
- note: toPlainString(signal.note),
5034
+ note: signal.note || "",
5076
5035
  priceTakeProfit: signal.priceTakeProfit,
5077
5036
  priceStopLoss: signal.priceStopLoss,
5078
5037
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5098,7 +5057,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5098
5057
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5099
5058
  priceOpen: signal.priceOpen,
5100
5059
  position: signal.position,
5101
- note: toPlainString(signal.note),
5060
+ note: signal.note || "",
5102
5061
  priceTakeProfit: signal.priceTakeProfit,
5103
5062
  priceStopLoss: signal.priceStopLoss,
5104
5063
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5123,7 +5082,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5123
5082
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5124
5083
  priceOpen: currentPrice,
5125
5084
  ...structuredClone(signal),
5126
- note: toPlainString(signal.note),
5085
+ note: signal.note || "",
5127
5086
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
5128
5087
  symbol: self.params.execution.context.symbol,
5129
5088
  exchangeName: self.params.method.context.exchangeName,
@@ -5870,6 +5829,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5870
5829
  totalPartials: scheduled._partial?.length ?? 0,
5871
5830
  originalPriceOpen: scheduled.priceOpen,
5872
5831
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
5832
+ note: scheduled.note,
5873
5833
  });
5874
5834
  return null;
5875
5835
  }
@@ -6638,6 +6598,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6638
6598
  totalPartials: scheduled._partial?.length ?? 0,
6639
6599
  originalPriceOpen: scheduled.priceOpen,
6640
6600
  pnl: toProfitLossDto(scheduled, averagePrice),
6601
+ note: scheduled.note,
6641
6602
  });
6642
6603
  }
6643
6604
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6716,6 +6677,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
6716
6677
  totalPartials: scheduled._partial?.length ?? 0,
6717
6678
  originalPriceOpen: scheduled.priceOpen,
6718
6679
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
6680
+ note: scheduled.note,
6719
6681
  });
6720
6682
  return false;
6721
6683
  }
@@ -6803,6 +6765,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6803
6765
  totalPartials: closedSignal._partial?.length ?? 0,
6804
6766
  originalPriceOpen: closedSignal.priceOpen,
6805
6767
  pnl: toProfitLossDto(closedSignal, averagePrice),
6768
+ note: closedSignal.note,
6806
6769
  });
6807
6770
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6808
6771
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6903,6 +6866,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6903
6866
  totalPartials: activatedSignal._partial?.length ?? 0,
6904
6867
  originalPriceOpen: activatedSignal.priceOpen,
6905
6868
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6869
+ note: activatedSignal.note,
6906
6870
  });
6907
6871
  return { outcome: "pending" };
6908
6872
  }
@@ -6934,6 +6898,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6934
6898
  pendingAt: publicSignalForCommit.pendingAt,
6935
6899
  totalEntries: publicSignalForCommit.totalEntries,
6936
6900
  totalPartials: publicSignalForCommit.totalPartials,
6901
+ note: publicSignalForCommit.note,
6937
6902
  });
6938
6903
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6939
6904
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -7970,6 +7935,90 @@ class ClientStrategy {
7970
7935
  }
7971
7936
  return this._pendingSignal._fall.pnlCost;
7972
7937
  }
7938
+ /**
7939
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
7940
+ *
7941
+ * Measures how much PnL% the position has given back from its best point.
7942
+ * Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
7943
+ * Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
7944
+ *
7945
+ * Returns null if no pending signal exists.
7946
+ *
7947
+ * @param symbol - Trading pair symbol
7948
+ * @param currentPrice - Current market price
7949
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
7950
+ */
7951
+ async getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice) {
7952
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlPercentage", { symbol, currentPrice });
7953
+ if (!this._pendingSignal) {
7954
+ return null;
7955
+ }
7956
+ const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
7957
+ return Math.max(0, this._pendingSignal._peak.pnlPercentage - currentPnl.pnlPercentage);
7958
+ }
7959
+ /**
7960
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
7961
+ *
7962
+ * Measures how much PnL cost the position has given back from its best point.
7963
+ * Computed as: max(0, peakPnlCost - currentPnlCost).
7964
+ * Zero when called at the exact moment the peak was set, or when current PnL >= peak PnL.
7965
+ *
7966
+ * Returns null if no pending signal exists.
7967
+ *
7968
+ * @param symbol - Trading pair symbol
7969
+ * @param currentPrice - Current market price
7970
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
7971
+ */
7972
+ async getPositionHighestProfitDistancePnlCost(symbol, currentPrice) {
7973
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitDistancePnlCost", { symbol, currentPrice });
7974
+ if (!this._pendingSignal) {
7975
+ return null;
7976
+ }
7977
+ const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
7978
+ return Math.max(0, this._pendingSignal._peak.pnlCost - currentPnl.pnlCost);
7979
+ }
7980
+ /**
7981
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
7982
+ *
7983
+ * Measures how much the position has recovered from its deepest loss point.
7984
+ * Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
7985
+ * Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
7986
+ *
7987
+ * Returns null if no pending signal exists.
7988
+ *
7989
+ * @param symbol - Trading pair symbol
7990
+ * @param currentPrice - Current market price
7991
+ * @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
7992
+ */
7993
+ async getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice) {
7994
+ this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlPercentage", { symbol, currentPrice });
7995
+ if (!this._pendingSignal) {
7996
+ return null;
7997
+ }
7998
+ const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
7999
+ return Math.max(0, currentPnl.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
8000
+ }
8001
+ /**
8002
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
8003
+ *
8004
+ * Measures how much the position has recovered from its deepest loss point.
8005
+ * Computed as: max(0, currentPnlCost - fallPnlCost).
8006
+ * Zero when called at the exact moment the trough was set, or when current PnL <= trough PnL.
8007
+ *
8008
+ * Returns null if no pending signal exists.
8009
+ *
8010
+ * @param symbol - Trading pair symbol
8011
+ * @param currentPrice - Current market price
8012
+ * @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
8013
+ */
8014
+ async getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice) {
8015
+ this.params.logger.debug("ClientStrategy getPositionHighestMaxDrawdownPnlCost", { symbol, currentPrice });
8016
+ if (!this._pendingSignal) {
8017
+ return null;
8018
+ }
8019
+ const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
8020
+ return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
8021
+ }
7973
8022
  /**
7974
8023
  * Performs a single tick of strategy execution.
7975
8024
  *
@@ -8034,6 +8083,7 @@ class ClientStrategy {
8034
8083
  totalPartials: cancelledSignal._partial?.length ?? 0,
8035
8084
  originalPriceOpen: cancelledSignal.priceOpen,
8036
8085
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8086
+ note: cancelledSignal.note,
8037
8087
  });
8038
8088
  // Call onCancel callback
8039
8089
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8087,6 +8137,7 @@ class ClientStrategy {
8087
8137
  totalPartials: closedSignal._partial?.length ?? 0,
8088
8138
  originalPriceOpen: closedSignal.priceOpen,
8089
8139
  pnl: toProfitLossDto(closedSignal, currentPrice),
8140
+ note: closedSignal.note,
8090
8141
  });
8091
8142
  // Call onClose callback
8092
8143
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8168,6 +8219,7 @@ class ClientStrategy {
8168
8219
  totalPartials: activatedSignal._partial?.length ?? 0,
8169
8220
  originalPriceOpen: activatedSignal.priceOpen,
8170
8221
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8222
+ note: activatedSignal.note,
8171
8223
  });
8172
8224
  return await RETURN_IDLE_FN(this, currentPrice);
8173
8225
  }
@@ -8198,6 +8250,7 @@ class ClientStrategy {
8198
8250
  pendingAt: publicSignalForCommit.pendingAt,
8199
8251
  totalEntries: publicSignalForCommit.totalEntries,
8200
8252
  totalPartials: publicSignalForCommit.totalPartials,
8253
+ note: publicSignalForCommit.note,
8201
8254
  });
8202
8255
  // Call onOpen callback
8203
8256
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8333,6 +8386,7 @@ class ClientStrategy {
8333
8386
  totalPartials: cancelledSignal._partial?.length ?? 0,
8334
8387
  originalPriceOpen: cancelledSignal.priceOpen,
8335
8388
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8389
+ note: cancelledSignal.note,
8336
8390
  });
8337
8391
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8338
8392
  const cancelledResult = {
@@ -8387,6 +8441,7 @@ class ClientStrategy {
8387
8441
  totalPartials: closedSignal._partial?.length ?? 0,
8388
8442
  originalPriceOpen: closedSignal.priceOpen,
8389
8443
  pnl: toProfitLossDto(closedSignal, currentPrice),
8444
+ note: closedSignal.note,
8390
8445
  });
8391
8446
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8392
8447
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -9936,6 +9991,8 @@ class MergeRisk {
9936
9991
  }
9937
9992
  }
9938
9993
 
9994
+ /** Default interval for strategies that do not specify one */
9995
+ const STRATEGY_DEFAULT_INTERVAL = "1m";
9939
9996
  /**
9940
9997
  * If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
9941
9998
  * to the exchange (e.g. limit order failed to fill).
@@ -10321,7 +10378,7 @@ class StrategyConnectionService {
10321
10378
  * @returns Configured ClientStrategy instance
10322
10379
  */
10323
10380
  this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10324
- const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10381
+ const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10325
10382
  return new ClientStrategy({
10326
10383
  symbol,
10327
10384
  interval,
@@ -11077,6 +11134,90 @@ class StrategyConnectionService {
11077
11134
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11078
11135
  return await strategy.getPositionMaxDrawdownPnlCost(symbol);
11079
11136
  };
11137
+ /**
11138
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
11139
+ *
11140
+ * Resolves current price via priceMetaService and delegates to
11141
+ * ClientStrategy.getPositionHighestProfitDistancePnlPercentage().
11142
+ * Returns null if no pending signal exists.
11143
+ *
11144
+ * @param backtest - Whether running in backtest mode
11145
+ * @param symbol - Trading pair symbol
11146
+ * @param context - Execution context with strategyName, exchangeName, frameName
11147
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
11148
+ */
11149
+ this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
11150
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlPercentage", {
11151
+ symbol,
11152
+ context,
11153
+ });
11154
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11155
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11156
+ return await strategy.getPositionHighestProfitDistancePnlPercentage(symbol, currentPrice);
11157
+ };
11158
+ /**
11159
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
11160
+ *
11161
+ * Resolves current price via priceMetaService and delegates to
11162
+ * ClientStrategy.getPositionHighestProfitDistancePnlCost().
11163
+ * Returns null if no pending signal exists.
11164
+ *
11165
+ * @param backtest - Whether running in backtest mode
11166
+ * @param symbol - Trading pair symbol
11167
+ * @param context - Execution context with strategyName, exchangeName, frameName
11168
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
11169
+ */
11170
+ this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
11171
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitDistancePnlCost", {
11172
+ symbol,
11173
+ context,
11174
+ });
11175
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11176
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11177
+ return await strategy.getPositionHighestProfitDistancePnlCost(symbol, currentPrice);
11178
+ };
11179
+ /**
11180
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
11181
+ *
11182
+ * Resolves current price via priceMetaService and delegates to
11183
+ * ClientStrategy.getPositionHighestMaxDrawdownPnlPercentage().
11184
+ * Returns null if no pending signal exists.
11185
+ *
11186
+ * @param backtest - Whether running in backtest mode
11187
+ * @param symbol - Trading pair symbol
11188
+ * @param context - Execution context with strategyName, exchangeName, frameName
11189
+ * @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
11190
+ */
11191
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
11192
+ this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlPercentage", {
11193
+ symbol,
11194
+ context,
11195
+ });
11196
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11197
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11198
+ return await strategy.getPositionHighestMaxDrawdownPnlPercentage(symbol, currentPrice);
11199
+ };
11200
+ /**
11201
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
11202
+ *
11203
+ * Resolves current price via priceMetaService and delegates to
11204
+ * ClientStrategy.getPositionHighestMaxDrawdownPnlCost().
11205
+ * Returns null if no pending signal exists.
11206
+ *
11207
+ * @param backtest - Whether running in backtest mode
11208
+ * @param symbol - Trading pair symbol
11209
+ * @param context - Execution context with strategyName, exchangeName, frameName
11210
+ * @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
11211
+ */
11212
+ this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
11213
+ this.loggerService.log("strategyConnectionService getPositionHighestMaxDrawdownPnlCost", {
11214
+ symbol,
11215
+ context,
11216
+ });
11217
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11218
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11219
+ return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
11220
+ };
11080
11221
  /**
11081
11222
  * Disposes the ClientStrategy instance for the given context.
11082
11223
  *
@@ -15238,6 +15379,82 @@ class StrategyCoreService {
15238
15379
  await this.validate(context);
15239
15380
  return await this.strategyConnectionService.getPositionMaxDrawdownPnlCost(backtest, symbol, context);
15240
15381
  };
15382
+ /**
15383
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
15384
+ *
15385
+ * Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlPercentage().
15386
+ * Returns null if no pending signal exists.
15387
+ *
15388
+ * @param backtest - Whether running in backtest mode
15389
+ * @param symbol - Trading pair symbol
15390
+ * @param context - Execution context with strategyName, exchangeName, frameName
15391
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
15392
+ */
15393
+ this.getPositionHighestProfitDistancePnlPercentage = async (backtest, symbol, context) => {
15394
+ this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlPercentage", {
15395
+ symbol,
15396
+ context,
15397
+ });
15398
+ await this.validate(context);
15399
+ return await this.strategyConnectionService.getPositionHighestProfitDistancePnlPercentage(backtest, symbol, context);
15400
+ };
15401
+ /**
15402
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
15403
+ *
15404
+ * Delegates to StrategyConnectionService.getPositionHighestProfitDistancePnlCost().
15405
+ * Returns null if no pending signal exists.
15406
+ *
15407
+ * @param backtest - Whether running in backtest mode
15408
+ * @param symbol - Trading pair symbol
15409
+ * @param context - Execution context with strategyName, exchangeName, frameName
15410
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
15411
+ */
15412
+ this.getPositionHighestProfitDistancePnlCost = async (backtest, symbol, context) => {
15413
+ this.loggerService.log("strategyCoreService getPositionHighestProfitDistancePnlCost", {
15414
+ symbol,
15415
+ context,
15416
+ });
15417
+ await this.validate(context);
15418
+ return await this.strategyConnectionService.getPositionHighestProfitDistancePnlCost(backtest, symbol, context);
15419
+ };
15420
+ /**
15421
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
15422
+ *
15423
+ * Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage().
15424
+ * Returns null if no pending signal exists.
15425
+ *
15426
+ * @param backtest - Whether running in backtest mode
15427
+ * @param symbol - Trading pair symbol
15428
+ * @param context - Execution context with strategyName, exchangeName, frameName
15429
+ * @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
15430
+ */
15431
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (backtest, symbol, context) => {
15432
+ this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlPercentage", {
15433
+ symbol,
15434
+ context,
15435
+ });
15436
+ await this.validate(context);
15437
+ return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlPercentage(backtest, symbol, context);
15438
+ };
15439
+ /**
15440
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
15441
+ *
15442
+ * Delegates to StrategyConnectionService.getPositionHighestMaxDrawdownPnlCost().
15443
+ * Returns null if no pending signal exists.
15444
+ *
15445
+ * @param backtest - Whether running in backtest mode
15446
+ * @param symbol - Trading pair symbol
15447
+ * @param context - Execution context with strategyName, exchangeName, frameName
15448
+ * @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
15449
+ */
15450
+ this.getPositionHighestMaxDrawdownPnlCost = async (backtest, symbol, context) => {
15451
+ this.loggerService.log("strategyCoreService getPositionHighestMaxDrawdownPnlCost", {
15452
+ symbol,
15453
+ context,
15454
+ });
15455
+ await this.validate(context);
15456
+ return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
15457
+ };
15241
15458
  }
15242
15459
  }
15243
15460
 
@@ -15880,8 +16097,8 @@ class StrategySchemaService {
15880
16097
  if (strategySchema.actions?.some((value) => typeof value !== "string")) {
15881
16098
  throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
15882
16099
  }
15883
- if (typeof strategySchema.interval !== "string") {
15884
- throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
16100
+ if (strategySchema.interval && typeof strategySchema.interval !== "string") {
16101
+ throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
15885
16102
  }
15886
16103
  if (typeof strategySchema.getSignal !== "function") {
15887
16104
  throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
@@ -17827,6 +18044,53 @@ class WalkerCommandService {
17827
18044
  }
17828
18045
  }
17829
18046
 
18047
+ /**
18048
+ * Converts markdown content to plain text with minimal formatting
18049
+ * @param content - Markdown string to convert
18050
+ * @returns Plain text representation
18051
+ */
18052
+ const toPlainString = (content) => {
18053
+ if (!content) {
18054
+ return "";
18055
+ }
18056
+ let text = content;
18057
+ // Remove code blocks
18058
+ text = text.replace(/```[\s\S]*?```/g, "");
18059
+ text = text.replace(/`([^`]+)`/g, "$1");
18060
+ // Remove images
18061
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
18062
+ // Convert links to text only (keep link text, remove URL)
18063
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18064
+ // Remove headers (convert to plain text)
18065
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
18066
+ // Remove bold and italic markers
18067
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
18068
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
18069
+ text = text.replace(/\*(.+?)\*/g, "$1");
18070
+ text = text.replace(/___(.+?)___/g, "$1");
18071
+ text = text.replace(/__(.+?)__/g, "$1");
18072
+ text = text.replace(/_(.+?)_/g, "$1");
18073
+ // Remove strikethrough
18074
+ text = text.replace(/~~(.+?)~~/g, "$1");
18075
+ // Convert lists to plain text with bullets
18076
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
18077
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
18078
+ // Remove blockquotes
18079
+ text = text.replace(/^\s*>\s+/gm, "");
18080
+ // Remove horizontal rules
18081
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
18082
+ // Remove HTML tags
18083
+ text = text.replace(/<[^>]+>/g, "");
18084
+ // Remove excessive whitespace and normalize line breaks
18085
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
18086
+ text = text.replace(/[ \t]+/g, " ");
18087
+ // Remove all newline characters
18088
+ text = text.replace(/\n/g, " ");
18089
+ // Remove excessive spaces after newline removal
18090
+ text = text.replace(/\s+/g, " ");
18091
+ return text.trim();
18092
+ };
18093
+
17830
18094
  /**
17831
18095
  * Column configuration for backtest markdown reports.
17832
18096
  *
@@ -18502,7 +18766,7 @@ const partial_columns = [
18502
18766
  {
18503
18767
  key: "note",
18504
18768
  label: "Note",
18505
- format: (data) => data.note || "",
18769
+ format: (data) => toPlainString(data.note ?? "N/A"),
18506
18770
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18507
18771
  },
18508
18772
  {
@@ -18662,7 +18926,7 @@ const breakeven_columns = [
18662
18926
  {
18663
18927
  key: "note",
18664
18928
  label: "Note",
18665
- format: (data) => data.note || "",
18929
+ format: (data) => toPlainString(data.note ?? "N/A"),
18666
18930
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18667
18931
  },
18668
18932
  {
@@ -34893,6 +35157,10 @@ const GET_POSITION_MAX_DRAWDOWN_PRICE_METHOD_NAME = "strategy.getPositionMaxDraw
34893
35157
  const GET_POSITION_MAX_DRAWDOWN_TIMESTAMP_METHOD_NAME = "strategy.getPositionMaxDrawdownTimestamp";
34894
35158
  const GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlPercentage";
34895
35159
  const GET_POSITION_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionMaxDrawdownPnlCost";
35160
+ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlPercentage";
35161
+ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
35162
+ const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
35163
+ const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
34896
35164
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
34897
35165
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
34898
35166
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
@@ -36527,6 +36795,122 @@ async function getPositionMaxDrawdownPnlCost(symbol) {
36527
36795
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36528
36796
  return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36529
36797
  }
36798
+ /**
36799
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
36800
+ *
36801
+ * Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
36802
+ * Returns null if no pending signal exists.
36803
+ *
36804
+ * @param symbol - Trading pair symbol
36805
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
36806
+ *
36807
+ * @example
36808
+ * ```typescript
36809
+ * import { getPositionHighestProfitDistancePnlPercentage } from "backtest-kit";
36810
+ *
36811
+ * const dist = await getPositionHighestProfitDistancePnlPercentage("BTCUSDT");
36812
+ * // e.g. 1.5 (gave back 1.5% from peak)
36813
+ * ```
36814
+ */
36815
+ async function getPositionHighestProfitDistancePnlPercentage(symbol) {
36816
+ backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
36817
+ if (!ExecutionContextService.hasContext()) {
36818
+ throw new Error("getPositionHighestProfitDistancePnlPercentage requires an execution context");
36819
+ }
36820
+ if (!MethodContextService.hasContext()) {
36821
+ throw new Error("getPositionHighestProfitDistancePnlPercentage requires a method context");
36822
+ }
36823
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36824
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36825
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
36826
+ }
36827
+ /**
36828
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
36829
+ *
36830
+ * Computed as: max(0, peakPnlCost - currentPnlCost).
36831
+ * Returns null if no pending signal exists.
36832
+ *
36833
+ * @param symbol - Trading pair symbol
36834
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
36835
+ *
36836
+ * @example
36837
+ * ```typescript
36838
+ * import { getPositionHighestProfitDistancePnlCost } from "backtest-kit";
36839
+ *
36840
+ * const dist = await getPositionHighestProfitDistancePnlCost("BTCUSDT");
36841
+ * // e.g. 3.2 (gave back $3.2 from peak)
36842
+ * ```
36843
+ */
36844
+ async function getPositionHighestProfitDistancePnlCost(symbol) {
36845
+ backtest.loggerService.info(GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
36846
+ if (!ExecutionContextService.hasContext()) {
36847
+ throw new Error("getPositionHighestProfitDistancePnlCost requires an execution context");
36848
+ }
36849
+ if (!MethodContextService.hasContext()) {
36850
+ throw new Error("getPositionHighestProfitDistancePnlCost requires a method context");
36851
+ }
36852
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36853
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36854
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36855
+ }
36856
+ /**
36857
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
36858
+ *
36859
+ * Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
36860
+ * Returns null if no pending signal exists.
36861
+ *
36862
+ * @param symbol - Trading pair symbol
36863
+ * @returns Promise resolving to recovery distance in PnL% (≥ 0) or null
36864
+ *
36865
+ * @example
36866
+ * ```typescript
36867
+ * import { getPositionHighestMaxDrawdownPnlPercentage } from "backtest-kit";
36868
+ *
36869
+ * const dist = await getPositionHighestMaxDrawdownPnlPercentage("BTCUSDT");
36870
+ * // e.g. 2.1 (recovered 2.1% from trough)
36871
+ * ```
36872
+ */
36873
+ async function getPositionHighestMaxDrawdownPnlPercentage(symbol) {
36874
+ backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME, { symbol });
36875
+ if (!ExecutionContextService.hasContext()) {
36876
+ throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires an execution context");
36877
+ }
36878
+ if (!MethodContextService.hasContext()) {
36879
+ throw new Error("getPositionHighestMaxDrawdownPnlPercentage requires a method context");
36880
+ }
36881
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36882
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36883
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
36884
+ }
36885
+ /**
36886
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
36887
+ *
36888
+ * Computed as: max(0, currentPnlCost - fallPnlCost).
36889
+ * Returns null if no pending signal exists.
36890
+ *
36891
+ * @param symbol - Trading pair symbol
36892
+ * @returns Promise resolving to recovery distance in PnL cost (≥ 0) or null
36893
+ *
36894
+ * @example
36895
+ * ```typescript
36896
+ * import { getPositionHighestMaxDrawdownPnlCost } from "backtest-kit";
36897
+ *
36898
+ * const dist = await getPositionHighestMaxDrawdownPnlCost("BTCUSDT");
36899
+ * // e.g. 4.8 (recovered $4.8 from trough)
36900
+ * ```
36901
+ */
36902
+ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
36903
+ backtest.loggerService.info(GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME, { symbol });
36904
+ if (!ExecutionContextService.hasContext()) {
36905
+ throw new Error("getPositionHighestMaxDrawdownPnlCost requires an execution context");
36906
+ }
36907
+ if (!MethodContextService.hasContext()) {
36908
+ throw new Error("getPositionHighestMaxDrawdownPnlCost requires a method context");
36909
+ }
36910
+ const { backtest: isBacktest } = backtest.executionContextService.context;
36911
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36912
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36913
+ }
36530
36914
  /**
36531
36915
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
36532
36916
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38196,6 +38580,10 @@ const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "BacktestUtils.getP
38196
38580
  const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "BacktestUtils.getPositionMaxDrawdownTimestamp";
38197
38581
  const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionMaxDrawdownPnlPercentage";
38198
38582
  const BACKTEST_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionMaxDrawdownPnlCost";
38583
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestProfitDistancePnlPercentage";
38584
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
38585
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
38586
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
38199
38587
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
38200
38588
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
38201
38589
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -39465,6 +39853,118 @@ class BacktestUtils {
39465
39853
  }
39466
39854
  return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(true, symbol, context);
39467
39855
  };
39856
+ /**
39857
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
39858
+ *
39859
+ * Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
39860
+ * Returns null if no pending signal exists.
39861
+ *
39862
+ * @param symbol - Trading pair symbol
39863
+ * @param context - Execution context with strategyName, exchangeName, and frameName
39864
+ * @returns drawdown distance in PnL% (≥ 0) or null if no active position
39865
+ */
39866
+ this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
39867
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
39868
+ symbol,
39869
+ context,
39870
+ });
39871
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
39872
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
39873
+ {
39874
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
39875
+ riskName &&
39876
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
39877
+ riskList &&
39878
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
39879
+ actions &&
39880
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
39881
+ }
39882
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(true, symbol, context);
39883
+ };
39884
+ /**
39885
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
39886
+ *
39887
+ * Computed as: max(0, peakPnlCost - currentPnlCost).
39888
+ * Returns null if no pending signal exists.
39889
+ *
39890
+ * @param symbol - Trading pair symbol
39891
+ * @param context - Execution context with strategyName, exchangeName, and frameName
39892
+ * @returns drawdown distance in PnL cost (≥ 0) or null if no active position
39893
+ */
39894
+ this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
39895
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
39896
+ symbol,
39897
+ context,
39898
+ });
39899
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
39900
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
39901
+ {
39902
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
39903
+ riskName &&
39904
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
39905
+ riskList &&
39906
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
39907
+ actions &&
39908
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
39909
+ }
39910
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(true, symbol, context);
39911
+ };
39912
+ /**
39913
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
39914
+ *
39915
+ * Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
39916
+ * Returns null if no pending signal exists.
39917
+ *
39918
+ * @param symbol - Trading pair symbol
39919
+ * @param context - Execution context with strategyName, exchangeName, and frameName
39920
+ * @returns recovery distance in PnL% (≥ 0) or null if no active position
39921
+ */
39922
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
39923
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
39924
+ symbol,
39925
+ context,
39926
+ });
39927
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
39928
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
39929
+ {
39930
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
39931
+ riskName &&
39932
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
39933
+ riskList &&
39934
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
39935
+ actions &&
39936
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
39937
+ }
39938
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(true, symbol, context);
39939
+ };
39940
+ /**
39941
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
39942
+ *
39943
+ * Computed as: max(0, currentPnlCost - fallPnlCost).
39944
+ * Returns null if no pending signal exists.
39945
+ *
39946
+ * @param symbol - Trading pair symbol
39947
+ * @param context - Execution context with strategyName, exchangeName, and frameName
39948
+ * @returns recovery distance in PnL cost (≥ 0) or null if no active position
39949
+ */
39950
+ this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
39951
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
39952
+ symbol,
39953
+ context,
39954
+ });
39955
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
39956
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
39957
+ {
39958
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
39959
+ riskName &&
39960
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
39961
+ riskList &&
39962
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
39963
+ actions &&
39964
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
39965
+ }
39966
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
39967
+ };
39468
39968
  /**
39469
39969
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
39470
39970
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -40590,6 +41090,10 @@ const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "LiveUtils.getPositionM
40590
41090
  const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "LiveUtils.getPositionMaxDrawdownTimestamp";
40591
41091
  const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionMaxDrawdownPnlPercentage";
40592
41092
  const LIVE_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionMaxDrawdownPnlCost";
41093
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getPositionHighestProfitDistancePnlPercentage";
41094
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
41095
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
41096
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
40593
41097
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
40594
41098
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
40595
41099
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -41986,6 +42490,134 @@ class LiveUtils {
41986
42490
  frameName: "",
41987
42491
  });
41988
42492
  };
42493
+ /**
42494
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
42495
+ *
42496
+ * Computed as: max(0, peakPnlPercentage - currentPnlPercentage).
42497
+ * Returns null if no pending signal exists.
42498
+ *
42499
+ * @param symbol - Trading pair symbol
42500
+ * @param context - Execution context with strategyName and exchangeName
42501
+ * @returns drawdown distance in PnL% (≥ 0) or null if no active position
42502
+ */
42503
+ this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context) => {
42504
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, {
42505
+ symbol,
42506
+ context,
42507
+ });
42508
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
42509
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
42510
+ {
42511
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42512
+ riskName &&
42513
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
42514
+ riskList &&
42515
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
42516
+ actions &&
42517
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
42518
+ }
42519
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(false, symbol, {
42520
+ strategyName: context.strategyName,
42521
+ exchangeName: context.exchangeName,
42522
+ frameName: "",
42523
+ });
42524
+ };
42525
+ /**
42526
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
42527
+ *
42528
+ * Computed as: max(0, peakPnlCost - currentPnlCost).
42529
+ * Returns null if no pending signal exists.
42530
+ *
42531
+ * @param symbol - Trading pair symbol
42532
+ * @param context - Execution context with strategyName and exchangeName
42533
+ * @returns drawdown distance in PnL cost (≥ 0) or null if no active position
42534
+ */
42535
+ this.getPositionHighestProfitDistancePnlCost = async (symbol, context) => {
42536
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, {
42537
+ symbol,
42538
+ context,
42539
+ });
42540
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
42541
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
42542
+ {
42543
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42544
+ riskName &&
42545
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
42546
+ riskList &&
42547
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
42548
+ actions &&
42549
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
42550
+ }
42551
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(false, symbol, {
42552
+ strategyName: context.strategyName,
42553
+ exchangeName: context.exchangeName,
42554
+ frameName: "",
42555
+ });
42556
+ };
42557
+ /**
42558
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
42559
+ *
42560
+ * Computed as: max(0, currentPnlPercentage - fallPnlPercentage).
42561
+ * Returns null if no pending signal exists.
42562
+ *
42563
+ * @param symbol - Trading pair symbol
42564
+ * @param context - Execution context with strategyName and exchangeName
42565
+ * @returns recovery distance in PnL% (≥ 0) or null if no active position
42566
+ */
42567
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context) => {
42568
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, {
42569
+ symbol,
42570
+ context,
42571
+ });
42572
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
42573
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
42574
+ {
42575
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42576
+ riskName &&
42577
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
42578
+ riskList &&
42579
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
42580
+ actions &&
42581
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
42582
+ }
42583
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(false, symbol, {
42584
+ strategyName: context.strategyName,
42585
+ exchangeName: context.exchangeName,
42586
+ frameName: "",
42587
+ });
42588
+ };
42589
+ /**
42590
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
42591
+ *
42592
+ * Computed as: max(0, currentPnlCost - fallPnlCost).
42593
+ * Returns null if no pending signal exists.
42594
+ *
42595
+ * @param symbol - Trading pair symbol
42596
+ * @param context - Execution context with strategyName and exchangeName
42597
+ * @returns recovery distance in PnL cost (≥ 0) or null if no active position
42598
+ */
42599
+ this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context) => {
42600
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, {
42601
+ symbol,
42602
+ context,
42603
+ });
42604
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
42605
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
42606
+ {
42607
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42608
+ riskName &&
42609
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
42610
+ riskList &&
42611
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
42612
+ actions &&
42613
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
42614
+ }
42615
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(false, symbol, {
42616
+ strategyName: context.strategyName,
42617
+ exchangeName: context.exchangeName,
42618
+ frameName: "",
42619
+ });
42620
+ };
41989
42621
  /**
41990
42622
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
41991
42623
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -51033,6 +51665,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51033
51665
  pnlEntries: data.signal.pnl.pnlEntries,
51034
51666
  scheduledAt: data.signal.scheduledAt,
51035
51667
  currentPrice: data.currentPrice,
51668
+ note: data.signal.note,
51036
51669
  createdAt: data.createdAt,
51037
51670
  };
51038
51671
  }
@@ -51062,6 +51695,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51062
51695
  duration: durationMin,
51063
51696
  scheduledAt: data.signal.scheduledAt,
51064
51697
  pendingAt: data.signal.pendingAt,
51698
+ note: data.signal.note,
51065
51699
  createdAt: data.createdAt,
51066
51700
  };
51067
51701
  }
@@ -51098,6 +51732,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
51098
51732
  pnlPriceClose: data.data.pnl.priceClose,
51099
51733
  pnlCost: data.data.pnl.pnlCost,
51100
51734
  pnlEntries: data.data.pnl.pnlEntries,
51735
+ note: data.data.note,
51101
51736
  scheduledAt: data.data.scheduledAt,
51102
51737
  pendingAt: data.data.pendingAt,
51103
51738
  createdAt: data.timestamp,
@@ -51133,6 +51768,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
51133
51768
  pnlPriceClose: data.data.pnl.priceClose,
51134
51769
  pnlCost: data.data.pnl.pnlCost,
51135
51770
  pnlEntries: data.data.pnl.pnlEntries,
51771
+ note: data.data.note,
51136
51772
  scheduledAt: data.data.scheduledAt,
51137
51773
  pendingAt: data.data.pendingAt,
51138
51774
  createdAt: data.timestamp,
@@ -51167,6 +51803,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
51167
51803
  pnlPriceClose: data.data.pnl.priceClose,
51168
51804
  pnlCost: data.data.pnl.pnlCost,
51169
51805
  pnlEntries: data.data.pnl.pnlEntries,
51806
+ note: data.data.note,
51170
51807
  scheduledAt: data.data.scheduledAt,
51171
51808
  pendingAt: data.data.pendingAt,
51172
51809
  createdAt: data.timestamp,
@@ -51208,6 +51845,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51208
51845
  pnlEntries: data.pnl.pnlEntries,
51209
51846
  scheduledAt: data.scheduledAt,
51210
51847
  pendingAt: data.pendingAt,
51848
+ note: data.note,
51211
51849
  createdAt: data.timestamp,
51212
51850
  };
51213
51851
  }
@@ -51240,6 +51878,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51240
51878
  pnlEntries: data.pnl.pnlEntries,
51241
51879
  scheduledAt: data.scheduledAt,
51242
51880
  pendingAt: data.pendingAt,
51881
+ note: data.note,
51243
51882
  createdAt: data.timestamp,
51244
51883
  };
51245
51884
  }
@@ -51271,6 +51910,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51271
51910
  pnlEntries: data.pnl.pnlEntries,
51272
51911
  scheduledAt: data.scheduledAt,
51273
51912
  pendingAt: data.pendingAt,
51913
+ note: data.note,
51274
51914
  createdAt: data.timestamp,
51275
51915
  };
51276
51916
  }
@@ -51303,6 +51943,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51303
51943
  pnlEntries: data.pnl.pnlEntries,
51304
51944
  scheduledAt: data.scheduledAt,
51305
51945
  pendingAt: data.pendingAt,
51946
+ note: data.note,
51306
51947
  createdAt: data.timestamp,
51307
51948
  };
51308
51949
  }
@@ -51335,6 +51976,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51335
51976
  pnlEntries: data.pnl.pnlEntries,
51336
51977
  scheduledAt: data.scheduledAt,
51337
51978
  pendingAt: data.pendingAt,
51979
+ note: data.note,
51338
51980
  createdAt: data.timestamp,
51339
51981
  };
51340
51982
  }
@@ -51367,6 +52009,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51367
52009
  pnlEntries: data.pnl.pnlEntries,
51368
52010
  scheduledAt: data.scheduledAt,
51369
52011
  pendingAt: data.pendingAt,
52012
+ note: data.note,
51370
52013
  createdAt: data.timestamp,
51371
52014
  };
51372
52015
  }
@@ -51400,6 +52043,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51400
52043
  pnlEntries: data.pnl.pnlEntries,
51401
52044
  scheduledAt: data.scheduledAt,
51402
52045
  pendingAt: data.pendingAt,
52046
+ note: data.note,
51403
52047
  createdAt: data.timestamp,
51404
52048
  };
51405
52049
  }
@@ -51423,6 +52067,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51423
52067
  pnlPriceClose: data.pnl.priceClose,
51424
52068
  pnlCost: data.pnl.pnlCost,
51425
52069
  pnlEntries: data.pnl.pnlEntries,
52070
+ note: data.note,
51426
52071
  createdAt: data.timestamp,
51427
52072
  };
51428
52073
  }
@@ -51446,6 +52091,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51446
52091
  pnlPriceClose: data.pnl.priceClose,
51447
52092
  pnlCost: data.pnl.pnlCost,
51448
52093
  pnlEntries: data.pnl.pnlEntries,
52094
+ note: data.note,
51449
52095
  createdAt: data.timestamp,
51450
52096
  };
51451
52097
  }
@@ -51487,6 +52133,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51487
52133
  totalPartials: data.totalPartials,
51488
52134
  scheduledAt: data.scheduledAt,
51489
52135
  pendingAt: data.pendingAt,
52136
+ note: data.signal.note,
51490
52137
  createdAt: data.timestamp,
51491
52138
  };
51492
52139
  }
@@ -51519,6 +52166,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
51519
52166
  scheduledAt: data.scheduledAt,
51520
52167
  pendingAt: data.pendingAt,
51521
52168
  closeReason: data.closeReason,
52169
+ note: data.signal.note,
51522
52170
  createdAt: data.timestamp,
51523
52171
  };
51524
52172
  }
@@ -53018,6 +53666,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53018
53666
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
53019
53667
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53020
53668
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
53669
+ const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53021
53670
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
53022
53671
  const MS_PER_MINUTE$1 = 60000;
53023
53672
  const INTERVAL_MINUTES$1 = {
@@ -53269,7 +53918,7 @@ class CacheFileInstance {
53269
53918
  /**
53270
53919
  * Clears the index counter.
53271
53920
  */
53272
- static clearCounter() {
53921
+ static resetCounter() {
53273
53922
  CacheFileInstance._indexCounter = 0;
53274
53923
  }
53275
53924
  /**
@@ -53524,11 +54173,17 @@ class CacheUtils {
53524
54173
  */
53525
54174
  this.clear = () => {
53526
54175
  backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
53527
- {
53528
- this._getFnInstance.clear();
53529
- this._getFileInstance.clear();
53530
- }
53531
- CacheFileInstance.clearCounter();
54176
+ this._getFnInstance.clear();
54177
+ this._getFileInstance.clear();
54178
+ };
54179
+ /**
54180
+ * Resets the CacheFileInstance index counter to zero.
54181
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54182
+ * that new CacheFileInstance objects start with index 0 and do not collide with old instances.
54183
+ */
54184
+ this.resetCounter = () => {
54185
+ backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
54186
+ CacheFileInstance.resetCounter();
53532
54187
  };
53533
54188
  }
53534
54189
  }
@@ -53552,10 +54207,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
53552
54207
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
53553
54208
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
53554
54209
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
54210
+ const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
53555
54211
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
53556
54212
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
53557
54213
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
53558
54214
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
54215
+ const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
53559
54216
  const MS_PER_MINUTE = 60000;
53560
54217
  const INTERVAL_MINUTES = {
53561
54218
  "1m": 1,
@@ -53622,45 +54279,51 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
53622
54279
  *
53623
54280
  * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
53624
54281
  *
54282
+ * @template F - Concrete function type
54283
+ *
53625
54284
  * @example
53626
54285
  * ```typescript
53627
54286
  * const instance = new IntervalFnInstance(mySignalFn, "1h");
53628
- * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called)
53629
- * await instance.run("BTCUSDT"); // → null (skipped, same interval)
54287
+ * await instance.run("BTCUSDT"); // → T | null (fn called)
54288
+ * await instance.run("BTCUSDT"); // → null (skipped, same interval)
53630
54289
  * // After 1 hour passes:
53631
- * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called again)
54290
+ * await instance.run("BTCUSDT"); // → T | null (fn called again)
53632
54291
  * ```
53633
54292
  */
53634
54293
  class IntervalFnInstance {
53635
54294
  /**
53636
54295
  * Creates a new IntervalFnInstance.
53637
54296
  *
53638
- * @param fn - Signal function to fire once per interval
54297
+ * @param fn - Function to fire once per interval
53639
54298
  * @param interval - Candle interval that controls the firing boundary
54299
+ * @param key - Optional key generator for argument-based state separation.
54300
+ * Default: `([symbol]) => symbol`
53640
54301
  */
53641
- constructor(fn, interval) {
54302
+ constructor(fn, interval, key = ([symbol]) => symbol) {
53642
54303
  this.fn = fn;
53643
54304
  this.interval = interval;
53644
- /** Stores the last aligned timestamp per context+symbol key. */
54305
+ this.key = key;
54306
+ /** Stores the last aligned timestamp per context+symbol+args key. */
53645
54307
  this._stateMap = new Map();
53646
54308
  /**
53647
54309
  * Execute the signal function with once-per-interval enforcement.
53648
54310
  *
53649
54311
  * Algorithm:
53650
54312
  * 1. Align the current execution context `when` to the interval boundary.
53651
- * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
53652
- * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54313
+ * 2. Build state key from context + key generator result.
54314
+ * 3. If the stored aligned timestamp for this key equals the current one return `null`.
54315
+ * 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
53653
54316
  * the signal. If it returns `null`, leave state unchanged so the next call retries.
53654
54317
  *
53655
54318
  * Requires active method context and execution context.
53656
54319
  *
53657
- * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
53658
- * @returns The signal returned by `fn` on the first non-null fire, `null` on all subsequent calls
54320
+ * @param args - Arguments forwarded to the wrapped function
54321
+ * @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
53659
54322
  * within the same interval or when `fn` itself returned `null`
53660
54323
  * @throws Error if method context, execution context, or interval is missing
53661
54324
  */
53662
- this.run = async (symbol) => {
53663
- backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
54325
+ this.run = async (...args) => {
54326
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
53664
54327
  const step = INTERVAL_MINUTES[this.interval];
53665
54328
  {
53666
54329
  if (!MethodContextService.hasContext()) {
@@ -53674,15 +54337,16 @@ class IntervalFnInstance {
53674
54337
  }
53675
54338
  }
53676
54339
  const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
53677
- const key = `${contextKey}:${symbol}`;
53678
54340
  const currentWhen = backtest.executionContextService.context.when;
53679
54341
  const currentAligned = align(currentWhen.getTime(), this.interval);
53680
- if (this._stateMap.get(key) === currentAligned) {
54342
+ const argKey = this.key(args);
54343
+ const stateKey = `${contextKey}:${argKey}`;
54344
+ if (this._stateMap.get(stateKey) === currentAligned) {
53681
54345
  return null;
53682
54346
  }
53683
- const result = await this.fn(symbol, currentWhen);
54347
+ const result = await this.fn.apply(null, args);
53684
54348
  if (result !== null) {
53685
- this._stateMap.set(key, currentAligned);
54349
+ this._stateMap.set(stateKey, currentAligned);
53686
54350
  }
53687
54351
  return result;
53688
54352
  };
@@ -53703,6 +54367,28 @@ class IntervalFnInstance {
53703
54367
  }
53704
54368
  }
53705
54369
  };
54370
+ /**
54371
+ * Garbage collect expired state entries.
54372
+ *
54373
+ * Removes all entries whose aligned timestamp differs from the current interval boundary.
54374
+ * Call this periodically to free memory from stale state entries.
54375
+ *
54376
+ * Requires active execution context to get current time.
54377
+ *
54378
+ * @returns Number of entries removed
54379
+ */
54380
+ this.gc = () => {
54381
+ const currentWhen = backtest.executionContextService.context.when;
54382
+ const currentAligned = align(currentWhen.getTime(), this.interval);
54383
+ let removed = 0;
54384
+ for (const [key, storedAligned] of this._stateMap.entries()) {
54385
+ if (storedAligned !== currentAligned) {
54386
+ this._stateMap.delete(key);
54387
+ removed++;
54388
+ }
54389
+ }
54390
+ return removed;
54391
+ };
53706
54392
  }
53707
54393
  }
53708
54394
  /**
@@ -53716,13 +54402,13 @@ class IntervalFnInstance {
53716
54402
  *
53717
54403
  * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
53718
54404
  *
53719
- * @template T - Async function type: `(symbol: string, ...args) => Promise<ISignalIntervalDto | null>`
54405
+ * @template F - Concrete async function type
53720
54406
  *
53721
54407
  * @example
53722
54408
  * ```typescript
53723
54409
  * const instance = new IntervalFileInstance(fetchSignal, "1h", "mySignal");
53724
- * await instance.run("BTCUSDT"); // → ISignalIntervalDto | null (fn called, result written to disk)
53725
- * await instance.run("BTCUSDT"); // → null (record exists, already fired)
54410
+ * await instance.run("BTCUSDT"); // → R | null (fn called, result written to disk)
54411
+ * await instance.run("BTCUSDT"); // → null (record exists, already fired)
53726
54412
  * ```
53727
54413
  */
53728
54414
  class IntervalFileInstance {
@@ -53737,7 +54423,7 @@ class IntervalFileInstance {
53737
54423
  * Resets the index counter to zero.
53738
54424
  * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
53739
54425
  */
53740
- static clearCounter() {
54426
+ static resetCounter() {
53741
54427
  IntervalFileInstance._indexCounter = 0;
53742
54428
  }
53743
54429
  /**
@@ -53746,26 +54432,30 @@ class IntervalFileInstance {
53746
54432
  * @param fn - Async signal function to fire once per interval
53747
54433
  * @param interval - Candle interval that controls the firing boundary
53748
54434
  * @param name - Human-readable bucket name used as the directory prefix
54435
+ * @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
54436
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
53749
54437
  */
53750
- constructor(fn, interval, name) {
54438
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
53751
54439
  this.fn = fn;
53752
54440
  this.interval = interval;
53753
54441
  this.name = name;
54442
+ this.key = key;
53754
54443
  /**
53755
- * Execute the async signal function with persistent once-per-interval enforcement.
54444
+ * Execute the async function with persistent once-per-interval enforcement.
53756
54445
  *
53757
54446
  * Algorithm:
53758
54447
  * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
53759
- * 2. Align execution context `when` to interval boundary → `alignedTs`.
53760
- * 3. Build entity key = `${symbol}_${alignedTs}`.
54448
+ * 2. Align execution context `when` to interval boundary → `alignedMs`.
54449
+ * 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
53761
54450
  * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
53762
54451
  * 5. On hit — return `null` (interval already fired).
53763
54452
  * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
53764
54453
  *
53765
54454
  * Requires active method context and execution context.
53766
54455
  *
53767
- * @param args - Arguments forwarded to the wrapped function (first must be `symbol: string`)
53768
- * @returns The signal on the first non-null fire, `null` if already fired this interval
54456
+ * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
54457
+ * @param args - Additional arguments forwarded to the wrapped function
54458
+ * @returns The value on the first non-null fire, `null` if already fired this interval
53769
54459
  * or if `fn` itself returned `null`
53770
54460
  * @throws Error if method context, execution context, or interval is missing
53771
54461
  */
@@ -53783,11 +54473,11 @@ class IntervalFileInstance {
53783
54473
  throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
53784
54474
  }
53785
54475
  }
53786
- const [symbol] = args;
54476
+ const [symbol, ...rest] = args;
53787
54477
  const { when } = backtest.executionContextService.context;
53788
- const alignedTs = align(when.getTime(), this.interval);
54478
+ const alignedMs = align(when.getTime(), this.interval);
53789
54479
  const bucket = `${this.name}_${this.interval}_${this.index}`;
53790
- const entityKey = `${symbol}_${alignedTs}`;
54480
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
53791
54481
  const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
53792
54482
  if (cached !== null) {
53793
54483
  return null;
@@ -53823,8 +54513,8 @@ IntervalFileInstance._indexCounter = 0;
53823
54513
  * import { Interval } from "./classes/Interval";
53824
54514
  *
53825
54515
  * const fireOncePerHour = Interval.fn(mySignalFn, { interval: "1h" });
53826
- * await fireOncePerHour("BTCUSDT", when); // fn called — returns its result
53827
- * await fireOncePerHour("BTCUSDT", when); // returns null (same interval)
54516
+ * await fireOncePerHour("BTCUSDT"); // fn called — returns its result
54517
+ * await fireOncePerHour("BTCUSDT"); // returns null (same interval)
53828
54518
  * ```
53829
54519
  */
53830
54520
  class IntervalUtils {
@@ -53833,12 +54523,12 @@ class IntervalUtils {
53833
54523
  * Memoized factory to get or create an `IntervalFnInstance` for a function.
53834
54524
  * Each function reference gets its own isolated instance.
53835
54525
  */
53836
- this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
54526
+ this._getInstance = memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
53837
54527
  /**
53838
54528
  * Memoized factory to get or create an `IntervalFileInstance` for an async function.
53839
54529
  * Each function reference gets its own isolated persistent instance.
53840
54530
  */
53841
- this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
54531
+ this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
53842
54532
  /**
53843
54533
  * Wrap a signal function with in-memory once-per-interval firing.
53844
54534
  *
@@ -53850,21 +54540,30 @@ class IntervalUtils {
53850
54540
  *
53851
54541
  * @param run - Signal function to wrap
53852
54542
  * @param context.interval - Candle interval that controls the firing boundary
53853
- * @returns Wrapped function with the same signature as `TIntervalFn`, plus a `clear()` method
54543
+ * @param context.key - Optional key generator for argument-based state separation
54544
+ * @returns Wrapped function with the same signature as `F`, plus a `clear()` method
53854
54545
  *
53855
54546
  * @example
53856
54547
  * ```typescript
54548
+ * // Without extra args
53857
54549
  * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
54550
+ * await fireOnce("BTCUSDT"); // → T or null (fn called)
54551
+ * await fireOnce("BTCUSDT"); // → null (same interval, skipped)
53858
54552
  *
53859
- * await fireOnce("BTCUSDT", when); // signal or null (fn called)
53860
- * await fireOnce("BTCUSDT", when); // → null (same interval, skipped)
54553
+ * // With extra args and key
54554
+ * const fireOnce = Interval.fn(mySignalFn, {
54555
+ * interval: "15m",
54556
+ * key: ([symbol, period]) => `${symbol}_${period}`,
54557
+ * });
54558
+ * await fireOnce("BTCUSDT", 14); // → T or null
54559
+ * await fireOnce("BTCUSDT", 28); // → T or null (separate state)
53861
54560
  * ```
53862
54561
  */
53863
54562
  this.fn = (run, context) => {
53864
54563
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
53865
- const wrappedFn = (symbol, _when) => {
53866
- const instance = this._getInstance(run, context.interval);
53867
- return instance.run(symbol);
54564
+ const wrappedFn = (...args) => {
54565
+ const instance = this._getInstance(run, context.interval, context.key);
54566
+ return instance.run(...args);
53868
54567
  };
53869
54568
  wrappedFn.clear = () => {
53870
54569
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
@@ -53878,6 +54577,14 @@ class IntervalUtils {
53878
54577
  }
53879
54578
  this._getInstance.get(run)?.clear();
53880
54579
  };
54580
+ wrappedFn.gc = () => {
54581
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
54582
+ if (!ExecutionContextService.hasContext()) {
54583
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
54584
+ return;
54585
+ }
54586
+ return this._getInstance.get(run)?.gc();
54587
+ };
53881
54588
  return wrappedFn;
53882
54589
  };
53883
54590
  /**
@@ -53890,27 +54597,32 @@ class IntervalUtils {
53890
54597
  * The `run` function reference is used as the memoization key for the underlying
53891
54598
  * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
53892
54599
  *
53893
- * @template T - Async function type to wrap
54600
+ * @template F - Concrete async function type
53894
54601
  * @param run - Async signal function to wrap with persistent once-per-interval firing
53895
54602
  * @param context.interval - Candle interval that controls the firing boundary
53896
54603
  * @param context.name - Human-readable bucket name; becomes the directory prefix
53897
- * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
53898
- * that deletes persisted records from disk and disposes the memoized instance
54604
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
54605
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54606
+ * @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
53899
54607
  *
53900
54608
  * @example
53901
54609
  * ```typescript
53902
54610
  * const fetchSignal = async (symbol: string, period: number) => { ... };
53903
- * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
53904
- * await fireOnce.clear(); // delete disk records so the function fires again next call
54611
+ * const fireOnce = Interval.file(fetchSignal, {
54612
+ * interval: "1h",
54613
+ * name: "fetchSignal",
54614
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
54615
+ * });
54616
+ * await fireOnce("BTCUSDT", 14);
53905
54617
  * ```
53906
54618
  */
53907
54619
  this.file = (run, context) => {
53908
54620
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
53909
54621
  {
53910
- this._getFileInstance(run, context.interval, context.name);
54622
+ this._getFileInstance(run, context.interval, context.name, context.key);
53911
54623
  }
53912
54624
  const wrappedFn = (...args) => {
53913
- const instance = this._getFileInstance(run, context.interval, context.name);
54625
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
53914
54626
  return instance.run(...args);
53915
54627
  };
53916
54628
  wrappedFn.clear = async () => {
@@ -53937,10 +54649,10 @@ class IntervalUtils {
53937
54649
  this.dispose = (run) => {
53938
54650
  backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
53939
54651
  this._getInstance.clear(run);
54652
+ this._getFileInstance.clear(run);
53940
54653
  };
53941
54654
  /**
53942
- * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
53943
- * resets the `IntervalFileInstance` index counter.
54655
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
53944
54656
  * Call this when `process.cwd()` changes between strategy iterations
53945
54657
  * so new instances are created with the updated base path.
53946
54658
  */
@@ -53948,7 +54660,15 @@ class IntervalUtils {
53948
54660
  backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
53949
54661
  this._getInstance.clear();
53950
54662
  this._getFileInstance.clear();
53951
- IntervalFileInstance.clearCounter();
54663
+ };
54664
+ /**
54665
+ * Resets the IntervalFileInstance index counter to zero.
54666
+ * This is useful when process.cwd() changes between strategy iterations to ensure
54667
+ * that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
54668
+ */
54669
+ this.resetCounter = () => {
54670
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
54671
+ IntervalFileInstance.resetCounter();
53952
54672
  };
53953
54673
  }
53954
54674
  }
@@ -55082,4 +55802,4 @@ const validateSignal = (signal, currentPrice) => {
55082
55802
  return !errors.length;
55083
55803
  };
55084
55804
 
55085
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
55805
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };