backtest-kit 6.10.0 → 6.12.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.
Files changed (4) hide show
  1. package/build/index.cjs +1356 -191
  2. package/build/index.mjs +1354 -192
  3. package/package.json +2 -2
  4. package/types.d.ts +953 -118
package/build/index.cjs CHANGED
@@ -4147,53 +4147,6 @@ const toProfitLossDto = (signal, priceClose) => {
4147
4147
  };
4148
4148
  };
4149
4149
 
4150
- /**
4151
- * Converts markdown content to plain text with minimal formatting
4152
- * @param content - Markdown string to convert
4153
- * @returns Plain text representation
4154
- */
4155
- const toPlainString = (content) => {
4156
- if (!content) {
4157
- return "";
4158
- }
4159
- let text = content;
4160
- // Remove code blocks
4161
- text = text.replace(/```[\s\S]*?```/g, "");
4162
- text = text.replace(/`([^`]+)`/g, "$1");
4163
- // Remove images
4164
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
4165
- // Convert links to text only (keep link text, remove URL)
4166
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
4167
- // Remove headers (convert to plain text)
4168
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
4169
- // Remove bold and italic markers
4170
- text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
4171
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4172
- text = text.replace(/\*(.+?)\*/g, "$1");
4173
- text = text.replace(/___(.+?)___/g, "$1");
4174
- text = text.replace(/__(.+?)__/g, "$1");
4175
- text = text.replace(/_(.+?)_/g, "$1");
4176
- // Remove strikethrough
4177
- text = text.replace(/~~(.+?)~~/g, "$1");
4178
- // Convert lists to plain text with bullets
4179
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
4180
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
4181
- // Remove blockquotes
4182
- text = text.replace(/^\s*>\s+/gm, "");
4183
- // Remove horizontal rules
4184
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
4185
- // Remove HTML tags
4186
- text = text.replace(/<[^>]+>/g, "");
4187
- // Remove excessive whitespace and normalize line breaks
4188
- text = text.replace(/\n[\s\n]*\n/g, "\n");
4189
- text = text.replace(/[ \t]+/g, " ");
4190
- // Remove all newline characters
4191
- text = text.replace(/\n/g, " ");
4192
- // Remove excessive spaces after newline removal
4193
- text = text.replace(/\s+/g, " ");
4194
- return text.trim();
4195
- };
4196
-
4197
4150
  /**
4198
4151
  * Returns the total closed state of a position using costBasisAtClose snapshots.
4199
4152
  *
@@ -4828,6 +4781,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4828
4781
  originalPriceOpen: publicSignal.originalPriceOpen,
4829
4782
  scheduledAt: publicSignal.scheduledAt,
4830
4783
  pendingAt: publicSignal.pendingAt,
4784
+ note: publicSignal.note,
4831
4785
  });
4832
4786
  continue;
4833
4787
  }
@@ -4855,6 +4809,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4855
4809
  originalPriceOpen: publicSignal.originalPriceOpen,
4856
4810
  scheduledAt: publicSignal.scheduledAt,
4857
4811
  pendingAt: publicSignal.pendingAt,
4812
+ note: publicSignal.note,
4858
4813
  });
4859
4814
  continue;
4860
4815
  }
@@ -4881,6 +4836,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4881
4836
  originalPriceOpen: publicSignal.originalPriceOpen,
4882
4837
  scheduledAt: publicSignal.scheduledAt,
4883
4838
  pendingAt: publicSignal.pendingAt,
4839
+ note: publicSignal.note,
4884
4840
  });
4885
4841
  continue;
4886
4842
  }
@@ -4908,6 +4864,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4908
4864
  originalPriceOpen: publicSignal.originalPriceOpen,
4909
4865
  scheduledAt: publicSignal.scheduledAt,
4910
4866
  pendingAt: publicSignal.pendingAt,
4867
+ note: publicSignal.note,
4911
4868
  });
4912
4869
  continue;
4913
4870
  }
@@ -4935,6 +4892,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4935
4892
  originalPriceOpen: publicSignal.originalPriceOpen,
4936
4893
  scheduledAt: publicSignal.scheduledAt,
4937
4894
  pendingAt: publicSignal.pendingAt,
4895
+ note: publicSignal.note,
4938
4896
  });
4939
4897
  continue;
4940
4898
  }
@@ -4964,6 +4922,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4964
4922
  originalPriceOpen: publicSignal.originalPriceOpen,
4965
4923
  scheduledAt: publicSignal.scheduledAt,
4966
4924
  pendingAt: publicSignal.pendingAt,
4925
+ note: publicSignal.note,
4967
4926
  });
4968
4927
  continue;
4969
4928
  }
@@ -5062,7 +5021,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5062
5021
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
5063
5022
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
5064
5023
  const signal = await Promise.race([
5065
- self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
5024
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
5066
5025
  functoolsKit.sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
5067
5026
  ]);
5068
5027
  if (typeof signal === "symbol") {
@@ -5092,7 +5051,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5092
5051
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5093
5052
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
5094
5053
  position: signal.position,
5095
- note: toPlainString(signal.note),
5054
+ note: signal.note || "",
5096
5055
  priceTakeProfit: signal.priceTakeProfit,
5097
5056
  priceStopLoss: signal.priceStopLoss,
5098
5057
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5118,7 +5077,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5118
5077
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5119
5078
  priceOpen: signal.priceOpen,
5120
5079
  position: signal.position,
5121
- note: toPlainString(signal.note),
5080
+ note: signal.note || "",
5122
5081
  priceTakeProfit: signal.priceTakeProfit,
5123
5082
  priceStopLoss: signal.priceStopLoss,
5124
5083
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5143,7 +5102,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
5143
5102
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5144
5103
  priceOpen: currentPrice,
5145
5104
  ...structuredClone(signal),
5146
- note: toPlainString(signal.note),
5105
+ note: signal.note || "",
5147
5106
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
5148
5107
  symbol: self.params.execution.context.symbol,
5149
5108
  exchangeName: self.params.method.context.exchangeName,
@@ -5890,6 +5849,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5890
5849
  totalPartials: scheduled._partial?.length ?? 0,
5891
5850
  originalPriceOpen: scheduled.priceOpen,
5892
5851
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
5852
+ note: scheduled.note,
5893
5853
  });
5894
5854
  return null;
5895
5855
  }
@@ -6633,7 +6593,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
6633
6593
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
6634
6594
  return result;
6635
6595
  };
6636
- const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
6596
+ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
6637
6597
  self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
6638
6598
  symbol: self.params.execution.context.symbol,
6639
6599
  signalId: scheduled.id,
@@ -6658,6 +6618,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6658
6618
  totalPartials: scheduled._partial?.length ?? 0,
6659
6619
  originalPriceOpen: scheduled.priceOpen,
6660
6620
  pnl: toProfitLossDto(scheduled, averagePrice),
6621
+ note: cancelNote ?? scheduled.note,
6661
6622
  });
6662
6623
  }
6663
6624
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6736,6 +6697,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
6736
6697
  totalPartials: scheduled._partial?.length ?? 0,
6737
6698
  originalPriceOpen: scheduled.priceOpen,
6738
6699
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
6700
+ note: scheduled.note,
6739
6701
  });
6740
6702
  return false;
6741
6703
  }
@@ -6823,6 +6785,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6823
6785
  totalPartials: closedSignal._partial?.length ?? 0,
6824
6786
  originalPriceOpen: closedSignal.priceOpen,
6825
6787
  pnl: toProfitLossDto(closedSignal, averagePrice),
6788
+ note: closedSignal.closeNote ?? closedSignal.note,
6826
6789
  });
6827
6790
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6828
6791
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6869,7 +6832,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6869
6832
  if (self._cancelledSignal) {
6870
6833
  // Сигнал был отменен через cancel() в onSchedulePing
6871
6834
  const cancelId = self._cancelledSignal.cancelId;
6872
- const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
6835
+ const cancelNote = self._cancelledSignal.cancelNote;
6836
+ const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
6873
6837
  return { outcome: "cancelled", result };
6874
6838
  }
6875
6839
  // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
@@ -6923,6 +6887,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6923
6887
  totalPartials: activatedSignal._partial?.length ?? 0,
6924
6888
  originalPriceOpen: activatedSignal.priceOpen,
6925
6889
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6890
+ note: activatedSignal.activateNote ?? activatedSignal.note,
6926
6891
  });
6927
6892
  return { outcome: "pending" };
6928
6893
  }
@@ -6954,6 +6919,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6954
6919
  pendingAt: publicSignalForCommit.pendingAt,
6955
6920
  totalEntries: publicSignalForCommit.totalEntries,
6956
6921
  totalPartials: publicSignalForCommit.totalPartials,
6922
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
6957
6923
  });
6958
6924
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6959
6925
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -8074,6 +8040,44 @@ class ClientStrategy {
8074
8040
  const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
8075
8041
  return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
8076
8042
  }
8043
+ /**
8044
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
8045
+ *
8046
+ * Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
8047
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
8048
+ *
8049
+ * Returns null if no pending signal exists.
8050
+ *
8051
+ * @param symbol - Trading pair symbol
8052
+ * @param currentPrice - Current market price
8053
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
8054
+ */
8055
+ async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
8056
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
8057
+ if (!this._pendingSignal) {
8058
+ return null;
8059
+ }
8060
+ return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
8061
+ }
8062
+ /**
8063
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
8064
+ *
8065
+ * Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
8066
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
8067
+ *
8068
+ * Returns null if no pending signal exists.
8069
+ *
8070
+ * @param symbol - Trading pair symbol
8071
+ * @param currentPrice - Current market price
8072
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
8073
+ */
8074
+ async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
8075
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
8076
+ if (!this._pendingSignal) {
8077
+ return null;
8078
+ }
8079
+ return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
8080
+ }
8077
8081
  /**
8078
8082
  * Performs a single tick of strategy execution.
8079
8083
  *
@@ -8138,6 +8142,7 @@ class ClientStrategy {
8138
8142
  totalPartials: cancelledSignal._partial?.length ?? 0,
8139
8143
  originalPriceOpen: cancelledSignal.priceOpen,
8140
8144
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8145
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8141
8146
  });
8142
8147
  // Call onCancel callback
8143
8148
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8191,6 +8196,7 @@ class ClientStrategy {
8191
8196
  totalPartials: closedSignal._partial?.length ?? 0,
8192
8197
  originalPriceOpen: closedSignal.priceOpen,
8193
8198
  pnl: toProfitLossDto(closedSignal, currentPrice),
8199
+ note: closedSignal.closeNote ?? closedSignal.note,
8194
8200
  });
8195
8201
  // Call onClose callback
8196
8202
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8272,6 +8278,7 @@ class ClientStrategy {
8272
8278
  totalPartials: activatedSignal._partial?.length ?? 0,
8273
8279
  originalPriceOpen: activatedSignal.priceOpen,
8274
8280
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8281
+ note: activatedSignal.activateNote ?? activatedSignal.note,
8275
8282
  });
8276
8283
  return await RETURN_IDLE_FN(this, currentPrice);
8277
8284
  }
@@ -8302,6 +8309,7 @@ class ClientStrategy {
8302
8309
  pendingAt: publicSignalForCommit.pendingAt,
8303
8310
  totalEntries: publicSignalForCommit.totalEntries,
8304
8311
  totalPartials: publicSignalForCommit.totalPartials,
8312
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
8305
8313
  });
8306
8314
  // Call onOpen callback
8307
8315
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8437,6 +8445,7 @@ class ClientStrategy {
8437
8445
  totalPartials: cancelledSignal._partial?.length ?? 0,
8438
8446
  originalPriceOpen: cancelledSignal.priceOpen,
8439
8447
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8448
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8440
8449
  });
8441
8450
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8442
8451
  const cancelledResult = {
@@ -8491,6 +8500,7 @@ class ClientStrategy {
8491
8500
  totalPartials: closedSignal._partial?.length ?? 0,
8492
8501
  originalPriceOpen: closedSignal.priceOpen,
8493
8502
  pnl: toProfitLossDto(closedSignal, currentPrice),
8503
+ note: closedSignal.closeNote ?? closedSignal.note,
8494
8504
  });
8495
8505
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8496
8506
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -8674,7 +8684,8 @@ class ClientStrategy {
8674
8684
  * // Strategy continues, can generate new signals
8675
8685
  * ```
8676
8686
  */
8677
- async cancelScheduled(symbol, backtest, cancelId) {
8687
+ async cancelScheduled(symbol, backtest, payload) {
8688
+ const cancelId = payload.id;
8678
8689
  this.params.logger.debug("ClientStrategy cancelScheduled", {
8679
8690
  symbol,
8680
8691
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8686,6 +8697,7 @@ class ClientStrategy {
8686
8697
  if (this._scheduledSignal) {
8687
8698
  this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
8688
8699
  cancelId,
8700
+ cancelNote: payload.note,
8689
8701
  });
8690
8702
  this._scheduledSignal = null;
8691
8703
  }
@@ -8717,7 +8729,8 @@ class ClientStrategy {
8717
8729
  * // Scheduled signal becomes pending signal immediately
8718
8730
  * ```
8719
8731
  */
8720
- async activateScheduled(symbol, backtest, activateId) {
8732
+ async activateScheduled(symbol, backtest, payload) {
8733
+ const activateId = payload.id;
8721
8734
  this.params.logger.debug("ClientStrategy activateScheduled", {
8722
8735
  symbol,
8723
8736
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8735,6 +8748,7 @@ class ClientStrategy {
8735
8748
  if (this._scheduledSignal) {
8736
8749
  this._activatedSignal = Object.assign({}, this._scheduledSignal, {
8737
8750
  activateId,
8751
+ activateNote: payload.note,
8738
8752
  });
8739
8753
  this._scheduledSignal = null;
8740
8754
  }
@@ -8766,7 +8780,8 @@ class ClientStrategy {
8766
8780
  * // Strategy continues, can generate new signals
8767
8781
  * ```
8768
8782
  */
8769
- async closePending(symbol, backtest, closeId) {
8783
+ async closePending(symbol, backtest, payload) {
8784
+ const closeId = payload.id;
8770
8785
  this.params.logger.debug("ClientStrategy closePending", {
8771
8786
  symbol,
8772
8787
  hasPendingSignal: this._pendingSignal !== null,
@@ -8777,6 +8792,7 @@ class ClientStrategy {
8777
8792
  if (this._pendingSignal) {
8778
8793
  this._closedSignal = Object.assign({}, this._pendingSignal, {
8779
8794
  closeId,
8795
+ closeNote: payload.note,
8780
8796
  });
8781
8797
  this._pendingSignal = null;
8782
8798
  }
@@ -10040,6 +10056,8 @@ class MergeRisk {
10040
10056
  }
10041
10057
  }
10042
10058
 
10059
+ /** Default interval for strategies that do not specify one */
10060
+ const STRATEGY_DEFAULT_INTERVAL = "1m";
10043
10061
  /**
10044
10062
  * If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
10045
10063
  * to the exchange (e.g. limit order failed to fill).
@@ -10425,7 +10443,7 @@ class StrategyConnectionService {
10425
10443
  * @returns Configured ClientStrategy instance
10426
10444
  */
10427
10445
  this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10428
- const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10446
+ const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10429
10447
  return new ClientStrategy({
10430
10448
  symbol,
10431
10449
  interval,
@@ -11265,6 +11283,48 @@ class StrategyConnectionService {
11265
11283
  const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11266
11284
  return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
11267
11285
  };
11286
+ /**
11287
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
11288
+ *
11289
+ * Resolves current price via priceMetaService and delegates to
11290
+ * ClientStrategy.getMaxDrawdownDistancePnlPercentage().
11291
+ * Returns null if no pending signal exists.
11292
+ *
11293
+ * @param backtest - Whether running in backtest mode
11294
+ * @param symbol - Trading pair symbol
11295
+ * @param context - Execution context with strategyName, exchangeName, frameName
11296
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
11297
+ */
11298
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
11299
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
11300
+ symbol,
11301
+ context,
11302
+ });
11303
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11304
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11305
+ return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
11306
+ };
11307
+ /**
11308
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
11309
+ *
11310
+ * Resolves current price via priceMetaService and delegates to
11311
+ * ClientStrategy.getMaxDrawdownDistancePnlCost().
11312
+ * Returns null if no pending signal exists.
11313
+ *
11314
+ * @param backtest - Whether running in backtest mode
11315
+ * @param symbol - Trading pair symbol
11316
+ * @param context - Execution context with strategyName, exchangeName, frameName
11317
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
11318
+ */
11319
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
11320
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
11321
+ symbol,
11322
+ context,
11323
+ });
11324
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11325
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11326
+ return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
11327
+ };
11268
11328
  /**
11269
11329
  * Disposes the ClientStrategy instance for the given context.
11270
11330
  *
@@ -11324,17 +11384,17 @@ class StrategyConnectionService {
11324
11384
  * @param backtest - Whether running in backtest mode
11325
11385
  * @param symbol - Trading pair symbol
11326
11386
  * @param ctx - Context with strategyName, exchangeName, frameName
11327
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
11387
+ * @param payload - Optional commit payload with id and note
11328
11388
  * @returns Promise that resolves when scheduled signal is cancelled
11329
11389
  */
11330
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
11390
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
11331
11391
  this.loggerService.log("strategyConnectionService cancelScheduled", {
11332
11392
  symbol,
11333
11393
  context,
11334
- cancelId,
11394
+ payload,
11335
11395
  });
11336
11396
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11337
- await strategy.cancelScheduled(symbol, backtest, cancelId);
11397
+ await strategy.cancelScheduled(symbol, backtest, payload);
11338
11398
  };
11339
11399
  /**
11340
11400
  * Closes the pending signal without stopping the strategy.
@@ -11349,17 +11409,17 @@ class StrategyConnectionService {
11349
11409
  * @param backtest - Whether running in backtest mode
11350
11410
  * @param symbol - Trading pair symbol
11351
11411
  * @param context - Context with strategyName, exchangeName, frameName
11352
- * @param closeId - Optional close ID for user-initiated closes
11412
+ * @param payload - Optional commit payload with id and note
11353
11413
  * @returns Promise that resolves when pending signal is closed
11354
11414
  */
11355
- this.closePending = async (backtest, symbol, context, closeId) => {
11415
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
11356
11416
  this.loggerService.log("strategyConnectionService closePending", {
11357
11417
  symbol,
11358
11418
  context,
11359
- closeId,
11419
+ payload,
11360
11420
  });
11361
11421
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11362
- await strategy.closePending(symbol, backtest, closeId);
11422
+ await strategy.closePending(symbol, backtest, payload);
11363
11423
  };
11364
11424
  /**
11365
11425
  * Checks whether `partialProfit` would succeed without executing it.
@@ -11638,7 +11698,7 @@ class StrategyConnectionService {
11638
11698
  * @param backtest - Whether running in backtest mode
11639
11699
  * @param symbol - Trading pair symbol
11640
11700
  * @param context - Execution context with strategyName, exchangeName, frameName
11641
- * @param activateId - Optional identifier for the activation reason
11701
+ * @param payload - Optional commit payload with id and note
11642
11702
  * @returns Promise that resolves when activation flag is set
11643
11703
  *
11644
11704
  * @example
@@ -11648,19 +11708,19 @@ class StrategyConnectionService {
11648
11708
  * false,
11649
11709
  * "BTCUSDT",
11650
11710
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
11651
- * "manual-activation"
11711
+ * { id: "manual-activation" }
11652
11712
  * );
11653
11713
  * ```
11654
11714
  */
11655
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
11715
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
11656
11716
  this.loggerService.log("strategyConnectionService activateScheduled", {
11657
11717
  symbol,
11658
11718
  context,
11659
11719
  backtest,
11660
- activateId,
11720
+ payload,
11661
11721
  });
11662
11722
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11663
- return await strategy.activateScheduled(symbol, backtest, activateId);
11723
+ return await strategy.activateScheduled(symbol, backtest, payload);
11664
11724
  };
11665
11725
  /**
11666
11726
  * Checks whether `averageBuy` would succeed without executing it.
@@ -14695,18 +14755,18 @@ class StrategyCoreService {
14695
14755
  * @param backtest - Whether running in backtest mode
14696
14756
  * @param symbol - Trading pair symbol
14697
14757
  * @param ctx - Context with strategyName, exchangeName, frameName
14698
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
14758
+ * @param payload - Optional commit payload with id and note
14699
14759
  * @returns Promise that resolves when scheduled signal is cancelled
14700
14760
  */
14701
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
14761
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
14702
14762
  this.loggerService.log("strategyCoreService cancelScheduled", {
14703
14763
  symbol,
14704
14764
  context,
14705
14765
  backtest,
14706
- cancelId,
14766
+ payload,
14707
14767
  });
14708
14768
  await this.validate(context);
14709
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
14769
+ return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
14710
14770
  };
14711
14771
  /**
14712
14772
  * Closes the pending signal without stopping the strategy.
@@ -14722,18 +14782,18 @@ class StrategyCoreService {
14722
14782
  * @param backtest - Whether running in backtest mode
14723
14783
  * @param symbol - Trading pair symbol
14724
14784
  * @param context - Context with strategyName, exchangeName, frameName
14725
- * @param closeId - Optional close ID for user-initiated closes
14785
+ * @param payload - Optional commit payload with id and note
14726
14786
  * @returns Promise that resolves when pending signal is closed
14727
14787
  */
14728
- this.closePending = async (backtest, symbol, context, closeId) => {
14788
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
14729
14789
  this.loggerService.log("strategyCoreService closePending", {
14730
14790
  symbol,
14731
14791
  context,
14732
14792
  backtest,
14733
- closeId,
14793
+ payload,
14734
14794
  });
14735
14795
  await this.validate(context);
14736
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
14796
+ return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
14737
14797
  };
14738
14798
  /**
14739
14799
  * Disposes the ClientStrategy instance for the given context.
@@ -15078,7 +15138,7 @@ class StrategyCoreService {
15078
15138
  * @param backtest - Whether running in backtest mode
15079
15139
  * @param symbol - Trading pair symbol
15080
15140
  * @param context - Execution context with strategyName, exchangeName, frameName
15081
- * @param activateId - Optional identifier for the activation reason
15141
+ * @param payload - Optional commit payload with id and note
15082
15142
  * @returns Promise that resolves when activation flag is set
15083
15143
  *
15084
15144
  * @example
@@ -15088,19 +15148,19 @@ class StrategyCoreService {
15088
15148
  * false,
15089
15149
  * "BTCUSDT",
15090
15150
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
15091
- * "manual-activation"
15151
+ * { id: "manual-activation" }
15092
15152
  * );
15093
15153
  * ```
15094
15154
  */
15095
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
15155
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
15096
15156
  this.loggerService.log("strategyCoreService activateScheduled", {
15097
15157
  symbol,
15098
15158
  context,
15099
15159
  backtest,
15100
- activateId,
15160
+ payload,
15101
15161
  });
15102
15162
  await this.validate(context);
15103
- return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
15163
+ return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
15104
15164
  };
15105
15165
  /**
15106
15166
  * Checks whether `averageBuy` would succeed without executing it.
@@ -15502,6 +15562,44 @@ class StrategyCoreService {
15502
15562
  await this.validate(context);
15503
15563
  return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
15504
15564
  };
15565
+ /**
15566
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
15567
+ *
15568
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
15569
+ * Returns null if no pending signal exists.
15570
+ *
15571
+ * @param backtest - Whether running in backtest mode
15572
+ * @param symbol - Trading pair symbol
15573
+ * @param context - Execution context with strategyName, exchangeName, frameName
15574
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
15575
+ */
15576
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
15577
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
15578
+ symbol,
15579
+ context,
15580
+ });
15581
+ await this.validate(context);
15582
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
15583
+ };
15584
+ /**
15585
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
15586
+ *
15587
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
15588
+ * Returns null if no pending signal exists.
15589
+ *
15590
+ * @param backtest - Whether running in backtest mode
15591
+ * @param symbol - Trading pair symbol
15592
+ * @param context - Execution context with strategyName, exchangeName, frameName
15593
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
15594
+ */
15595
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
15596
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
15597
+ symbol,
15598
+ context,
15599
+ });
15600
+ await this.validate(context);
15601
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
15602
+ };
15505
15603
  }
15506
15604
  }
15507
15605
 
@@ -16144,8 +16242,8 @@ class StrategySchemaService {
16144
16242
  if (strategySchema.actions?.some((value) => typeof value !== "string")) {
16145
16243
  throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
16146
16244
  }
16147
- if (typeof strategySchema.interval !== "string") {
16148
- throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
16245
+ if (strategySchema.interval && typeof strategySchema.interval !== "string") {
16246
+ throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
16149
16247
  }
16150
16248
  if (typeof strategySchema.getSignal !== "function") {
16151
16249
  throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
@@ -18091,6 +18189,53 @@ class WalkerCommandService {
18091
18189
  }
18092
18190
  }
18093
18191
 
18192
+ /**
18193
+ * Converts markdown content to plain text with minimal formatting
18194
+ * @param content - Markdown string to convert
18195
+ * @returns Plain text representation
18196
+ */
18197
+ const toPlainString = (content) => {
18198
+ if (!content) {
18199
+ return "";
18200
+ }
18201
+ let text = content;
18202
+ // Remove code blocks
18203
+ text = text.replace(/```[\s\S]*?```/g, "");
18204
+ text = text.replace(/`([^`]+)`/g, "$1");
18205
+ // Remove images
18206
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
18207
+ // Convert links to text only (keep link text, remove URL)
18208
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18209
+ // Remove headers (convert to plain text)
18210
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
18211
+ // Remove bold and italic markers
18212
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
18213
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
18214
+ text = text.replace(/\*(.+?)\*/g, "$1");
18215
+ text = text.replace(/___(.+?)___/g, "$1");
18216
+ text = text.replace(/__(.+?)__/g, "$1");
18217
+ text = text.replace(/_(.+?)_/g, "$1");
18218
+ // Remove strikethrough
18219
+ text = text.replace(/~~(.+?)~~/g, "$1");
18220
+ // Convert lists to plain text with bullets
18221
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
18222
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
18223
+ // Remove blockquotes
18224
+ text = text.replace(/^\s*>\s+/gm, "");
18225
+ // Remove horizontal rules
18226
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
18227
+ // Remove HTML tags
18228
+ text = text.replace(/<[^>]+>/g, "");
18229
+ // Remove excessive whitespace and normalize line breaks
18230
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
18231
+ text = text.replace(/[ \t]+/g, " ");
18232
+ // Remove all newline characters
18233
+ text = text.replace(/\n/g, " ");
18234
+ // Remove excessive spaces after newline removal
18235
+ text = text.replace(/\s+/g, " ");
18236
+ return text.trim();
18237
+ };
18238
+
18094
18239
  /**
18095
18240
  * Column configuration for backtest markdown reports.
18096
18241
  *
@@ -18766,7 +18911,7 @@ const partial_columns = [
18766
18911
  {
18767
18912
  key: "note",
18768
18913
  label: "Note",
18769
- format: (data) => data.note || "",
18914
+ format: (data) => toPlainString(data.note ?? "N/A"),
18770
18915
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18771
18916
  },
18772
18917
  {
@@ -18926,7 +19071,7 @@ const breakeven_columns = [
18926
19071
  {
18927
19072
  key: "note",
18928
19073
  label: "Note",
18929
- format: (data) => data.note || "",
19074
+ format: (data) => toPlainString(data.note ?? "N/A"),
18930
19075
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18931
19076
  },
18932
19077
  {
@@ -29054,7 +29199,7 @@ class StrategyReportService {
29054
29199
  /**
29055
29200
  * Logs a cancel-scheduled event when a scheduled signal is cancelled.
29056
29201
  */
29057
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
29202
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
29058
29203
  this.loggerService.log("strategyReportService cancelScheduled", {
29059
29204
  symbol,
29060
29205
  isBacktest,
@@ -29067,6 +29212,7 @@ class StrategyReportService {
29067
29212
  await ReportWriter.writeData("strategy", {
29068
29213
  action: "cancel-scheduled",
29069
29214
  cancelId,
29215
+ note,
29070
29216
  symbol,
29071
29217
  timestamp,
29072
29218
  createdAt,
@@ -29088,7 +29234,7 @@ class StrategyReportService {
29088
29234
  /**
29089
29235
  * Logs a close-pending event when a pending signal is closed.
29090
29236
  */
29091
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
29237
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
29092
29238
  this.loggerService.log("strategyReportService closePending", {
29093
29239
  symbol,
29094
29240
  isBacktest,
@@ -29101,6 +29247,7 @@ class StrategyReportService {
29101
29247
  await ReportWriter.writeData("strategy", {
29102
29248
  action: "close-pending",
29103
29249
  closeId,
29250
+ note,
29104
29251
  symbol,
29105
29252
  timestamp,
29106
29253
  createdAt,
@@ -29350,7 +29497,7 @@ class StrategyReportService {
29350
29497
  /**
29351
29498
  * Logs an activate-scheduled event when a scheduled signal is activated early.
29352
29499
  */
29353
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
29500
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
29354
29501
  this.loggerService.log("strategyReportService activateScheduled", {
29355
29502
  symbol,
29356
29503
  currentPrice,
@@ -29364,6 +29511,7 @@ class StrategyReportService {
29364
29511
  await ReportWriter.writeData("strategy", {
29365
29512
  action: "activate-scheduled",
29366
29513
  activateId,
29514
+ note,
29367
29515
  currentPrice,
29368
29516
  symbol,
29369
29517
  timestamp,
@@ -29457,14 +29605,14 @@ class StrategyReportService {
29457
29605
  exchangeName: event.exchangeName,
29458
29606
  frameName: event.frameName,
29459
29607
  strategyName: event.strategyName,
29460
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
29608
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
29461
29609
  const unClosePending = strategyCommitSubject
29462
29610
  .filter(({ action }) => action === "close-pending")
29463
29611
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
29464
29612
  exchangeName: event.exchangeName,
29465
29613
  frameName: event.frameName,
29466
29614
  strategyName: event.strategyName,
29467
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
29615
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
29468
29616
  const unPartialProfit = strategyCommitSubject
29469
29617
  .filter(({ action }) => action === "partial-profit")
29470
29618
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -29506,7 +29654,7 @@ class StrategyReportService {
29506
29654
  exchangeName: event.exchangeName,
29507
29655
  frameName: event.frameName,
29508
29656
  strategyName: event.strategyName,
29509
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
29657
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
29510
29658
  const unAverageBuy = strategyCommitSubject
29511
29659
  .filter(({ action }) => action === "average-buy")
29512
29660
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -30045,8 +30193,9 @@ class StrategyMarkdownService {
30045
30193
  * @param context - Strategy context with strategyName, exchangeName, frameName
30046
30194
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30047
30195
  * @param cancelId - Optional identifier for the cancellation reason
30196
+ * @param note - Optional note from commit payload
30048
30197
  */
30049
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
30198
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
30050
30199
  this.loggerService.log("strategyMarkdownService cancelScheduled", {
30051
30200
  symbol,
30052
30201
  isBacktest,
@@ -30067,6 +30216,7 @@ class StrategyMarkdownService {
30067
30216
  action: "cancel-scheduled",
30068
30217
  pnl,
30069
30218
  cancelId,
30219
+ note,
30070
30220
  createdAt,
30071
30221
  backtest: isBacktest,
30072
30222
  });
@@ -30079,8 +30229,9 @@ class StrategyMarkdownService {
30079
30229
  * @param context - Strategy context with strategyName, exchangeName, frameName
30080
30230
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30081
30231
  * @param closeId - Optional identifier for the close reason
30232
+ * @param note - Optional note from commit payload
30082
30233
  */
30083
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
30234
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
30084
30235
  this.loggerService.log("strategyMarkdownService closePending", {
30085
30236
  symbol,
30086
30237
  isBacktest,
@@ -30101,6 +30252,7 @@ class StrategyMarkdownService {
30101
30252
  action: "close-pending",
30102
30253
  pnl,
30103
30254
  closeId,
30255
+ note,
30104
30256
  createdAt,
30105
30257
  backtest: isBacktest,
30106
30258
  });
@@ -30399,8 +30551,9 @@ class StrategyMarkdownService {
30399
30551
  * @param scheduledAt - Signal creation timestamp in milliseconds
30400
30552
  * @param pendingAt - Pending timestamp in milliseconds
30401
30553
  * @param activateId - Optional identifier for the activation reason
30554
+ * @param note - Optional note from commit payload
30402
30555
  */
30403
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
30556
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
30404
30557
  this.loggerService.log("strategyMarkdownService activateScheduled", {
30405
30558
  symbol,
30406
30559
  currentPrice,
@@ -30423,6 +30576,7 @@ class StrategyMarkdownService {
30423
30576
  pnl,
30424
30577
  totalPartials,
30425
30578
  activateId,
30579
+ note,
30426
30580
  currentPrice,
30427
30581
  createdAt,
30428
30582
  backtest: isBacktest,
@@ -30627,14 +30781,14 @@ class StrategyMarkdownService {
30627
30781
  exchangeName: event.exchangeName,
30628
30782
  frameName: event.frameName,
30629
30783
  strategyName: event.strategyName,
30630
- }, event.timestamp, event.signalId, event.pnl, event.cancelId));
30784
+ }, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
30631
30785
  const unClosePending = strategyCommitSubject
30632
30786
  .filter(({ action }) => action === "close-pending")
30633
30787
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
30634
30788
  exchangeName: event.exchangeName,
30635
30789
  frameName: event.frameName,
30636
30790
  strategyName: event.strategyName,
30637
- }, event.timestamp, event.signalId, event.pnl, event.closeId));
30791
+ }, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
30638
30792
  const unPartialProfit = strategyCommitSubject
30639
30793
  .filter(({ action }) => action === "partial-profit")
30640
30794
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -30676,7 +30830,7 @@ class StrategyMarkdownService {
30676
30830
  exchangeName: event.exchangeName,
30677
30831
  frameName: event.frameName,
30678
30832
  strategyName: event.strategyName,
30679
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
30833
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
30680
30834
  const unAverageBuy = strategyCommitSubject
30681
30835
  .filter(({ action }) => action === "average-buy")
30682
30836
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -35161,6 +35315,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
35161
35315
  const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
35162
35316
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
35163
35317
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
35318
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
35319
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
35164
35320
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
35165
35321
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35166
35322
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
@@ -35176,7 +35332,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35176
35332
  *
35177
35333
  * @param symbol - Trading pair symbol
35178
35334
  * @param strategyName - Strategy name
35179
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
35335
+ * @param payload - Optional commit payload with id and note
35180
35336
  * @returns Promise that resolves when scheduled signal is cancelled
35181
35337
  *
35182
35338
  * @example
@@ -35184,13 +35340,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35184
35340
  * import { commitCancelScheduled } from "backtest-kit";
35185
35341
  *
35186
35342
  * // Cancel scheduled signal with custom ID
35187
- * await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
35343
+ * await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
35188
35344
  * ```
35189
35345
  */
35190
- async function commitCancelScheduled(symbol, cancelId) {
35346
+ async function commitCancelScheduled(symbol, payload = {}) {
35191
35347
  backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
35192
35348
  symbol,
35193
- cancelId,
35349
+ payload,
35194
35350
  });
35195
35351
  if (!ExecutionContextService.hasContext()) {
35196
35352
  throw new Error("commitCancelScheduled requires an execution context");
@@ -35200,7 +35356,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35200
35356
  }
35201
35357
  const { backtest: isBacktest } = backtest.executionContextService.context;
35202
35358
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35203
- await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, cancelId);
35359
+ await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35204
35360
  }
35205
35361
  /**
35206
35362
  * Closes the pending signal without stopping the strategy.
@@ -35212,7 +35368,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35212
35368
  * Automatically detects backtest/live mode from execution context.
35213
35369
  *
35214
35370
  * @param symbol - Trading pair symbol
35215
- * @param closeId - Optional close ID for tracking user-initiated closes
35371
+ * @param payload - Optional commit payload with id and note
35216
35372
  * @returns Promise that resolves when pending signal is closed
35217
35373
  *
35218
35374
  * @example
@@ -35220,13 +35376,13 @@ async function commitCancelScheduled(symbol, cancelId) {
35220
35376
  * import { commitClosePending } from "backtest-kit";
35221
35377
  *
35222
35378
  * // Close pending signal with custom ID
35223
- * await commitClosePending("BTCUSDT", "manual-close-001");
35379
+ * await commitClosePending("BTCUSDT", { id: "manual-close-001" });
35224
35380
  * ```
35225
35381
  */
35226
- async function commitClosePending(symbol, closeId) {
35382
+ async function commitClosePending(symbol, payload = {}) {
35227
35383
  backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
35228
35384
  symbol,
35229
- closeId,
35385
+ payload,
35230
35386
  });
35231
35387
  if (!ExecutionContextService.hasContext()) {
35232
35388
  throw new Error("commitClosePending requires an execution context");
@@ -35236,7 +35392,7 @@ async function commitClosePending(symbol, closeId) {
35236
35392
  }
35237
35393
  const { backtest: isBacktest } = backtest.executionContextService.context;
35238
35394
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35239
- await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, closeId);
35395
+ await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35240
35396
  }
35241
35397
  /**
35242
35398
  * Executes partial close at profit level (moving toward TP).
@@ -35699,7 +35855,7 @@ async function commitBreakeven(symbol) {
35699
35855
  * Automatically detects backtest/live mode from execution context.
35700
35856
  *
35701
35857
  * @param symbol - Trading pair symbol
35702
- * @param activateId - Optional activation ID for tracking user-initiated activations
35858
+ * @param payload - Optional commit payload with id and note
35703
35859
  * @returns Promise that resolves when activation flag is set
35704
35860
  *
35705
35861
  * @example
@@ -35707,13 +35863,13 @@ async function commitBreakeven(symbol) {
35707
35863
  * import { commitActivateScheduled } from "backtest-kit";
35708
35864
  *
35709
35865
  * // Activate scheduled signal early with custom ID
35710
- * await commitActivateScheduled("BTCUSDT", "manual-activate-001");
35866
+ * await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
35711
35867
  * ```
35712
35868
  */
35713
- async function commitActivateScheduled(symbol, activateId) {
35869
+ async function commitActivateScheduled(symbol, payload = {}) {
35714
35870
  backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
35715
35871
  symbol,
35716
- activateId,
35872
+ payload,
35717
35873
  });
35718
35874
  if (!ExecutionContextService.hasContext()) {
35719
35875
  throw new Error("commitActivateScheduled requires an execution context");
@@ -35723,7 +35879,7 @@ async function commitActivateScheduled(symbol, activateId) {
35723
35879
  }
35724
35880
  const { backtest: isBacktest } = backtest.executionContextService.context;
35725
35881
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35726
- await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
35882
+ await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35727
35883
  }
35728
35884
  /**
35729
35885
  * Adds a new DCA entry to the active pending signal.
@@ -36911,6 +37067,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
36911
37067
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36912
37068
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36913
37069
  }
37070
+ /**
37071
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
37072
+ *
37073
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
37074
+ * Returns null if no pending signal exists.
37075
+ *
37076
+ * @param symbol - Trading pair symbol
37077
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
37078
+ *
37079
+ * @example
37080
+ * ```typescript
37081
+ * import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
37082
+ *
37083
+ * const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
37084
+ * // e.g. 3.5 (peak was +3.5% above trough)
37085
+ * ```
37086
+ */
37087
+ async function getMaxDrawdownDistancePnlPercentage(symbol) {
37088
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
37089
+ if (!ExecutionContextService.hasContext()) {
37090
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
37091
+ }
37092
+ if (!MethodContextService.hasContext()) {
37093
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
37094
+ }
37095
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37096
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37097
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
37098
+ }
37099
+ /**
37100
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
37101
+ *
37102
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
37103
+ * Returns null if no pending signal exists.
37104
+ *
37105
+ * @param symbol - Trading pair symbol
37106
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
37107
+ *
37108
+ * @example
37109
+ * ```typescript
37110
+ * import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
37111
+ *
37112
+ * const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
37113
+ * // e.g. 7.2 (peak was $7.2 above trough)
37114
+ * ```
37115
+ */
37116
+ async function getMaxDrawdownDistancePnlCost(symbol) {
37117
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
37118
+ if (!ExecutionContextService.hasContext()) {
37119
+ throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
37120
+ }
37121
+ if (!MethodContextService.hasContext()) {
37122
+ throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
37123
+ }
37124
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37125
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37126
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
37127
+ }
36914
37128
  /**
36915
37129
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
36916
37130
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38584,6 +38798,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
38584
38798
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
38585
38799
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
38586
38800
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
38801
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
38802
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
38587
38803
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
38588
38804
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
38589
38805
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -39965,6 +40181,62 @@ class BacktestUtils {
39965
40181
  }
39966
40182
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
39967
40183
  };
40184
+ /**
40185
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
40186
+ *
40187
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
40188
+ * Returns null if no pending signal exists.
40189
+ *
40190
+ * @param symbol - Trading pair symbol
40191
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40192
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
40193
+ */
40194
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
40195
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
40196
+ symbol,
40197
+ context,
40198
+ });
40199
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40200
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40201
+ {
40202
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40203
+ riskName &&
40204
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40205
+ riskList &&
40206
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40207
+ actions &&
40208
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40209
+ }
40210
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
40211
+ };
40212
+ /**
40213
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
40214
+ *
40215
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
40216
+ * Returns null if no pending signal exists.
40217
+ *
40218
+ * @param symbol - Trading pair symbol
40219
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40220
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
40221
+ */
40222
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
40223
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
40224
+ symbol,
40225
+ context,
40226
+ });
40227
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40228
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40229
+ {
40230
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40231
+ riskName &&
40232
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40233
+ riskList &&
40234
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40235
+ actions &&
40236
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40237
+ }
40238
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
40239
+ };
39968
40240
  /**
39969
40241
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
39970
40242
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -40099,7 +40371,7 @@ class BacktestUtils {
40099
40371
  * @param symbol - Trading pair symbol
40100
40372
  * @param strategyName - Strategy name
40101
40373
  * @param context - Execution context with exchangeName and frameName
40102
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
40374
+ * @param payload - Optional commit payload with id and note
40103
40375
  * @returns Promise that resolves when scheduled signal is cancelled
40104
40376
  *
40105
40377
  * @example
@@ -40109,14 +40381,14 @@ class BacktestUtils {
40109
40381
  * exchangeName: "binance",
40110
40382
  * frameName: "frame1",
40111
40383
  * strategyName: "my-strategy"
40112
- * }, "manual-cancel-001");
40384
+ * }, { id: "manual-cancel-001" });
40113
40385
  * ```
40114
40386
  */
40115
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
40387
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
40116
40388
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
40117
40389
  symbol,
40118
40390
  context,
40119
- cancelId,
40391
+ payload,
40120
40392
  });
40121
40393
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
40122
40394
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
@@ -40129,7 +40401,7 @@ class BacktestUtils {
40129
40401
  actions &&
40130
40402
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
40131
40403
  }
40132
- await backtest.strategyCoreService.cancelScheduled(true, symbol, context, cancelId);
40404
+ await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
40133
40405
  };
40134
40406
  /**
40135
40407
  * Closes the pending signal without stopping the strategy.
@@ -40140,7 +40412,7 @@ class BacktestUtils {
40140
40412
  *
40141
40413
  * @param symbol - Trading pair symbol
40142
40414
  * @param context - Execution context with strategyName, exchangeName, and frameName
40143
- * @param closeId - Optional close ID for user-initiated closes
40415
+ * @param payload - Optional commit payload with id and note
40144
40416
  * @returns Promise that resolves when pending signal is closed
40145
40417
  *
40146
40418
  * @example
@@ -40150,14 +40422,14 @@ class BacktestUtils {
40150
40422
  * exchangeName: "binance",
40151
40423
  * strategyName: "my-strategy",
40152
40424
  * frameName: "1m"
40153
- * }, "manual-close-001");
40425
+ * }, { id: "manual-close-001" });
40154
40426
  * ```
40155
40427
  */
40156
- this.commitClosePending = async (symbol, context, closeId) => {
40428
+ this.commitClosePending = async (symbol, context, payload = {}) => {
40157
40429
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
40158
40430
  symbol,
40159
40431
  context,
40160
- closeId,
40432
+ payload,
40161
40433
  });
40162
40434
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
40163
40435
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
@@ -40170,7 +40442,7 @@ class BacktestUtils {
40170
40442
  actions &&
40171
40443
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
40172
40444
  }
40173
- await backtest.strategyCoreService.closePending(true, symbol, context, closeId);
40445
+ await backtest.strategyCoreService.closePending(true, symbol, context, payload);
40174
40446
  };
40175
40447
  /**
40176
40448
  * Executes partial close at profit level (moving toward TP).
@@ -40806,7 +41078,7 @@ class BacktestUtils {
40806
41078
  *
40807
41079
  * @param symbol - Trading pair symbol
40808
41080
  * @param context - Execution context with strategyName, exchangeName, and frameName
40809
- * @param activateId - Optional activation ID for tracking user-initiated activations
41081
+ * @param payload - Optional commit payload with id and note
40810
41082
  * @returns Promise that resolves when activation flag is set
40811
41083
  *
40812
41084
  * @example
@@ -40816,14 +41088,14 @@ class BacktestUtils {
40816
41088
  * strategyName: "my-strategy",
40817
41089
  * exchangeName: "binance",
40818
41090
  * frameName: "1h"
40819
- * }, "manual-activate-001");
41091
+ * }, { id: "manual-activate-001" });
40820
41092
  * ```
40821
41093
  */
40822
- this.commitActivateScheduled = async (symbol, context, activateId) => {
41094
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
40823
41095
  backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
40824
41096
  symbol,
40825
41097
  context,
40826
- activateId,
41098
+ payload,
40827
41099
  });
40828
41100
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
40829
41101
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -40836,7 +41108,7 @@ class BacktestUtils {
40836
41108
  actions &&
40837
41109
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
40838
41110
  }
40839
- await backtest.strategyCoreService.activateScheduled(true, symbol, context, activateId);
41111
+ await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
40840
41112
  };
40841
41113
  /**
40842
41114
  * Adds a new DCA entry to the active pending signal.
@@ -41094,6 +41366,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
41094
41366
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
41095
41367
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
41096
41368
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
41369
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
41370
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
41097
41371
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
41098
41372
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
41099
41373
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -42618,6 +42892,70 @@ class LiveUtils {
42618
42892
  frameName: "",
42619
42893
  });
42620
42894
  };
42895
+ /**
42896
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
42897
+ *
42898
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
42899
+ * Returns null if no pending signal exists.
42900
+ *
42901
+ * @param symbol - Trading pair symbol
42902
+ * @param context - Execution context with strategyName and exchangeName
42903
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
42904
+ */
42905
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
42906
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
42907
+ symbol,
42908
+ context,
42909
+ });
42910
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42911
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42912
+ {
42913
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42914
+ riskName &&
42915
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42916
+ riskList &&
42917
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
42918
+ actions &&
42919
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
42920
+ }
42921
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
42922
+ strategyName: context.strategyName,
42923
+ exchangeName: context.exchangeName,
42924
+ frameName: "",
42925
+ });
42926
+ };
42927
+ /**
42928
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
42929
+ *
42930
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
42931
+ * Returns null if no pending signal exists.
42932
+ *
42933
+ * @param symbol - Trading pair symbol
42934
+ * @param context - Execution context with strategyName and exchangeName
42935
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
42936
+ */
42937
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
42938
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
42939
+ symbol,
42940
+ context,
42941
+ });
42942
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42943
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42944
+ {
42945
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42946
+ riskName &&
42947
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42948
+ riskList &&
42949
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
42950
+ actions &&
42951
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
42952
+ }
42953
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
42954
+ strategyName: context.strategyName,
42955
+ exchangeName: context.exchangeName,
42956
+ frameName: "",
42957
+ });
42958
+ };
42621
42959
  /**
42622
42960
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
42623
42961
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -42759,7 +43097,7 @@ class LiveUtils {
42759
43097
  * @param symbol - Trading pair symbol
42760
43098
  * @param strategyName - Strategy name
42761
43099
  * @param context - Execution context with exchangeName and frameName
42762
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
43100
+ * @param payload - Optional commit payload with id and note
42763
43101
  * @returns Promise that resolves when scheduled signal is cancelled
42764
43102
  *
42765
43103
  * @example
@@ -42769,14 +43107,14 @@ class LiveUtils {
42769
43107
  * exchangeName: "binance",
42770
43108
  * frameName: "",
42771
43109
  * strategyName: "my-strategy"
42772
- * }, "manual-cancel-001");
43110
+ * }, { id: "manual-cancel-001" });
42773
43111
  * ```
42774
43112
  */
42775
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
43113
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
42776
43114
  backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
42777
43115
  symbol,
42778
43116
  context,
42779
- cancelId,
43117
+ payload,
42780
43118
  });
42781
43119
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
42782
43120
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
@@ -42793,7 +43131,7 @@ class LiveUtils {
42793
43131
  strategyName: context.strategyName,
42794
43132
  exchangeName: context.exchangeName,
42795
43133
  frameName: "",
42796
- }, cancelId);
43134
+ }, payload);
42797
43135
  };
42798
43136
  /**
42799
43137
  * Closes the pending signal without stopping the strategy.
@@ -42804,7 +43142,7 @@ class LiveUtils {
42804
43142
  *
42805
43143
  * @param symbol - Trading pair symbol
42806
43144
  * @param context - Execution context with strategyName and exchangeName
42807
- * @param closeId - Optional close ID for user-initiated closes
43145
+ * @param payload - Optional commit payload with id and note
42808
43146
  * @returns Promise that resolves when pending signal is closed
42809
43147
  *
42810
43148
  * @example
@@ -42813,14 +43151,14 @@ class LiveUtils {
42813
43151
  * await Live.commitClose("BTCUSDT", {
42814
43152
  * exchangeName: "binance",
42815
43153
  * strategyName: "my-strategy"
42816
- * }, "manual-close-001");
43154
+ * }, { id: "manual-close-001" });
42817
43155
  * ```
42818
43156
  */
42819
- this.commitClosePending = async (symbol, context, closeId) => {
43157
+ this.commitClosePending = async (symbol, context, payload = {}) => {
42820
43158
  backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
42821
43159
  symbol,
42822
43160
  context,
42823
- closeId,
43161
+ payload,
42824
43162
  });
42825
43163
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
42826
43164
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
@@ -42837,7 +43175,7 @@ class LiveUtils {
42837
43175
  strategyName: context.strategyName,
42838
43176
  exchangeName: context.exchangeName,
42839
43177
  frameName: "",
42840
- }, closeId);
43178
+ }, payload);
42841
43179
  };
42842
43180
  /**
42843
43181
  * Executes partial close at profit level (moving toward TP).
@@ -43631,7 +43969,7 @@ class LiveUtils {
43631
43969
  *
43632
43970
  * @param symbol - Trading pair symbol
43633
43971
  * @param context - Execution context with strategyName and exchangeName
43634
- * @param activateId - Optional activation ID for tracking user-initiated activations
43972
+ * @param payload - Optional commit payload with id and note
43635
43973
  * @returns Promise that resolves when activation flag is set
43636
43974
  *
43637
43975
  * @example
@@ -43640,14 +43978,14 @@ class LiveUtils {
43640
43978
  * await Live.commitActivateScheduled("BTCUSDT", {
43641
43979
  * strategyName: "my-strategy",
43642
43980
  * exchangeName: "binance"
43643
- * }, "manual-activate-001");
43981
+ * }, { id: "manual-activate-001" });
43644
43982
  * ```
43645
43983
  */
43646
- this.commitActivateScheduled = async (symbol, context, activateId) => {
43984
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
43647
43985
  backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
43648
43986
  symbol,
43649
43987
  context,
43650
- activateId,
43988
+ payload,
43651
43989
  });
43652
43990
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
43653
43991
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -43664,7 +44002,7 @@ class LiveUtils {
43664
44002
  strategyName: context.strategyName,
43665
44003
  exchangeName: context.exchangeName,
43666
44004
  frameName: "",
43667
- }, activateId);
44005
+ }, payload);
43668
44006
  };
43669
44007
  /**
43670
44008
  * Adds a new DCA entry to the active pending signal.
@@ -49735,6 +50073,741 @@ class MaxDrawdownUtils {
49735
50073
  */
49736
50074
  const MaxDrawdown = new MaxDrawdownUtils();
49737
50075
 
50076
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
50077
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
50078
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
50079
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
50080
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
50081
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
50082
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
50083
+ const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
50084
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
50085
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
50086
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
50087
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
50088
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
50089
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
50090
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
50091
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
50092
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
50093
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
50094
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
50095
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
50096
+ /**
50097
+ * Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
50098
+ *
50099
+ * Provides unified access to strategyCoreService position state methods with logging
50100
+ * and full validation (strategy, exchange, frame, risk, actions).
50101
+ * Works for both live and backtest modes via the `backtest` parameter.
50102
+ * Exported as singleton instance for convenient usage.
50103
+ *
50104
+ * @example
50105
+ * ```typescript
50106
+ * import { Reflect } from "backtest-kit";
50107
+ *
50108
+ * // Get current unrealized PNL percentage
50109
+ * const pnl = await Reflect.getPositionPnlPercent(
50110
+ * "BTCUSDT",
50111
+ * 45000,
50112
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50113
+ * );
50114
+ * console.log(`PNL: ${pnl}%`);
50115
+ *
50116
+ * // Get peak profit reached
50117
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50118
+ * "BTCUSDT",
50119
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50120
+ * );
50121
+ * console.log(`Peak PNL: ${peakPnl}%`);
50122
+ * ```
50123
+ */
50124
+ class ReflectUtils {
50125
+ constructor() {
50126
+ /**
50127
+ * Returns the unrealized PNL percentage for the current pending signal at currentPrice.
50128
+ *
50129
+ * Accounts for partial closes, DCA entries, slippage and fees.
50130
+ * Returns null if no pending signal exists.
50131
+ *
50132
+ * @param symbol - Trading pair symbol
50133
+ * @param currentPrice - Current market price
50134
+ * @param context - Execution context with strategyName, exchangeName and frameName
50135
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50136
+ * @returns Promise resolving to PNL percentage or null
50137
+ *
50138
+ * @example
50139
+ * ```typescript
50140
+ * const pnl = await Reflect.getPositionPnlPercent(
50141
+ * "BTCUSDT",
50142
+ * 45000,
50143
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50144
+ * );
50145
+ * console.log(`PNL: ${pnl}%`);
50146
+ * ```
50147
+ */
50148
+ this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
50149
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
50150
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50151
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50152
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50153
+ {
50154
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50155
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50156
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50157
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50158
+ }
50159
+ return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
50160
+ };
50161
+ /**
50162
+ * Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
50163
+ *
50164
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost.
50165
+ * Accounts for partial closes, DCA entries, slippage and fees.
50166
+ * Returns null if no pending signal exists.
50167
+ *
50168
+ * @param symbol - Trading pair symbol
50169
+ * @param currentPrice - Current market price
50170
+ * @param context - Execution context with strategyName, exchangeName and frameName
50171
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50172
+ * @returns Promise resolving to PNL in dollars or null
50173
+ *
50174
+ * @example
50175
+ * ```typescript
50176
+ * const pnlCost = await Reflect.getPositionPnlCost(
50177
+ * "BTCUSDT",
50178
+ * 45000,
50179
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50180
+ * );
50181
+ * console.log(`PNL: $${pnlCost}`);
50182
+ * ```
50183
+ */
50184
+ this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
50185
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
50186
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50187
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50188
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50189
+ {
50190
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50191
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50192
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50193
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50194
+ }
50195
+ return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
50196
+ };
50197
+ /**
50198
+ * Returns the best price reached in the profit direction during this position's life.
50199
+ *
50200
+ * Returns null if no pending signal exists.
50201
+ *
50202
+ * @param symbol - Trading pair symbol
50203
+ * @param context - Execution context with strategyName, exchangeName and frameName
50204
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50205
+ * @returns Promise resolving to price or null
50206
+ *
50207
+ * @example
50208
+ * ```typescript
50209
+ * const peakPrice = await Reflect.getPositionHighestProfitPrice(
50210
+ * "BTCUSDT",
50211
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50212
+ * );
50213
+ * console.log(`Peak price: ${peakPrice}`);
50214
+ * ```
50215
+ */
50216
+ this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
50217
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
50218
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50219
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50220
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50221
+ {
50222
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50223
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50224
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50225
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50226
+ }
50227
+ return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
50228
+ };
50229
+ /**
50230
+ * Returns the timestamp when the best profit price was recorded during this position's life.
50231
+ *
50232
+ * Returns null if no pending signal exists.
50233
+ *
50234
+ * @param symbol - Trading pair symbol
50235
+ * @param context - Execution context with strategyName, exchangeName and frameName
50236
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50237
+ * @returns Promise resolving to timestamp in milliseconds or null
50238
+ *
50239
+ * @example
50240
+ * ```typescript
50241
+ * const ts = await Reflect.getPositionHighestProfitTimestamp(
50242
+ * "BTCUSDT",
50243
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50244
+ * );
50245
+ * console.log(`Peak at: ${new Date(ts).toISOString()}`);
50246
+ * ```
50247
+ */
50248
+ this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
50249
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
50250
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50251
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50252
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50253
+ {
50254
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50255
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50256
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50257
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50258
+ }
50259
+ return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
50260
+ };
50261
+ /**
50262
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
50263
+ *
50264
+ * Returns null if no pending signal exists.
50265
+ *
50266
+ * @param symbol - Trading pair symbol
50267
+ * @param context - Execution context with strategyName, exchangeName and frameName
50268
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50269
+ * @returns Promise resolving to PnL percentage or null
50270
+ *
50271
+ * @example
50272
+ * ```typescript
50273
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50274
+ * "BTCUSDT",
50275
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50276
+ * );
50277
+ * console.log(`Peak PNL: ${peakPnl}%`);
50278
+ * ```
50279
+ */
50280
+ this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
50281
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
50282
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50283
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50284
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50285
+ {
50286
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50287
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50288
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50289
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50290
+ }
50291
+ return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
50292
+ };
50293
+ /**
50294
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
50295
+ *
50296
+ * Returns null if no pending signal exists.
50297
+ *
50298
+ * @param symbol - Trading pair symbol
50299
+ * @param context - Execution context with strategyName, exchangeName and frameName
50300
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50301
+ * @returns Promise resolving to PnL cost in quote currency or null
50302
+ *
50303
+ * @example
50304
+ * ```typescript
50305
+ * const peakCost = await Reflect.getPositionHighestPnlCost(
50306
+ * "BTCUSDT",
50307
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50308
+ * );
50309
+ * console.log(`Peak PNL: $${peakCost}`);
50310
+ * ```
50311
+ */
50312
+ this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
50313
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
50314
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50315
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50316
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50317
+ {
50318
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50319
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50320
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50321
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50322
+ }
50323
+ return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
50324
+ };
50325
+ /**
50326
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
50327
+ *
50328
+ * Returns null if no pending signal exists.
50329
+ *
50330
+ * @param symbol - Trading pair symbol
50331
+ * @param context - Execution context with strategyName, exchangeName and frameName
50332
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50333
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
50334
+ *
50335
+ * @example
50336
+ * ```typescript
50337
+ * const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
50338
+ * "BTCUSDT",
50339
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50340
+ * );
50341
+ * console.log(`Breakeven reachable at peak: ${wasReachable}`);
50342
+ * ```
50343
+ */
50344
+ this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
50345
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
50346
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50347
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50348
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50349
+ {
50350
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50351
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50352
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
50353
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
50354
+ }
50355
+ return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
50356
+ };
50357
+ /**
50358
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
50359
+ *
50360
+ * Returns null if no pending signal exists.
50361
+ *
50362
+ * @param symbol - Trading pair symbol
50363
+ * @param context - Execution context with strategyName, exchangeName and frameName
50364
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50365
+ * @returns Promise resolving to minutes since highest profit price was recorded, or null
50366
+ *
50367
+ * @example
50368
+ * ```typescript
50369
+ * const minutes = await Reflect.getPositionDrawdownMinutes(
50370
+ * "BTCUSDT",
50371
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50372
+ * );
50373
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
50374
+ * ```
50375
+ */
50376
+ this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
50377
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
50378
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50379
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50380
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50381
+ {
50382
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50383
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50384
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
50385
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
50386
+ }
50387
+ return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
50388
+ };
50389
+ /**
50390
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
50391
+ *
50392
+ * Alias for getPositionDrawdownMinutes — measures how long the position has been
50393
+ * pulling back from its peak profit level.
50394
+ * Returns null if no pending signal exists.
50395
+ *
50396
+ * @param symbol - Trading pair symbol
50397
+ * @param context - Execution context with strategyName, exchangeName and frameName
50398
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50399
+ * @returns Promise resolving to minutes since last profit peak or null
50400
+ *
50401
+ * @example
50402
+ * ```typescript
50403
+ * const minutes = await Reflect.getPositionHighestProfitMinutes(
50404
+ * "BTCUSDT",
50405
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50406
+ * );
50407
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
50408
+ * ```
50409
+ */
50410
+ this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
50411
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
50412
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50413
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50414
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50415
+ {
50416
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50417
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50418
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
50419
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
50420
+ }
50421
+ return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
50422
+ };
50423
+ /**
50424
+ * Returns the number of minutes elapsed since the worst loss price was recorded.
50425
+ *
50426
+ * Measures how long ago the deepest drawdown point occurred.
50427
+ * Zero when called at the exact moment the trough was set.
50428
+ * Returns null if no pending signal exists.
50429
+ *
50430
+ * @param symbol - Trading pair symbol
50431
+ * @param context - Execution context with strategyName, exchangeName and frameName
50432
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50433
+ * @returns Promise resolving to minutes since last drawdown trough or null
50434
+ *
50435
+ * @example
50436
+ * ```typescript
50437
+ * const minutes = await Reflect.getPositionMaxDrawdownMinutes(
50438
+ * "BTCUSDT",
50439
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50440
+ * );
50441
+ * console.log(`Drawdown trough was ${minutes} minutes ago`);
50442
+ * ```
50443
+ */
50444
+ this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
50445
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
50446
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50447
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50448
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50449
+ {
50450
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50451
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50452
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
50453
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
50454
+ }
50455
+ return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
50456
+ };
50457
+ /**
50458
+ * Returns the worst price reached in the loss direction during this position's life.
50459
+ *
50460
+ * Returns null if no pending signal exists.
50461
+ *
50462
+ * @param symbol - Trading pair symbol
50463
+ * @param context - Execution context with strategyName, exchangeName and frameName
50464
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50465
+ * @returns Promise resolving to price or null
50466
+ *
50467
+ * @example
50468
+ * ```typescript
50469
+ * const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
50470
+ * "BTCUSDT",
50471
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50472
+ * );
50473
+ * console.log(`Worst price: ${troughPrice}`);
50474
+ * ```
50475
+ */
50476
+ this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
50477
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
50478
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50479
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50480
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50481
+ {
50482
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50483
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50484
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
50485
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
50486
+ }
50487
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
50488
+ };
50489
+ /**
50490
+ * Returns the timestamp when the worst loss price was recorded during this position's life.
50491
+ *
50492
+ * Returns null if no pending signal exists.
50493
+ *
50494
+ * @param symbol - Trading pair symbol
50495
+ * @param context - Execution context with strategyName, exchangeName and frameName
50496
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50497
+ * @returns Promise resolving to timestamp in milliseconds or null
50498
+ *
50499
+ * @example
50500
+ * ```typescript
50501
+ * const ts = await Reflect.getPositionMaxDrawdownTimestamp(
50502
+ * "BTCUSDT",
50503
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50504
+ * );
50505
+ * console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
50506
+ * ```
50507
+ */
50508
+ this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
50509
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
50510
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50511
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50512
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50513
+ {
50514
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50515
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50516
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
50517
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
50518
+ }
50519
+ return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
50520
+ };
50521
+ /**
50522
+ * Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
50523
+ *
50524
+ * Returns null if no pending signal exists.
50525
+ *
50526
+ * @param symbol - Trading pair symbol
50527
+ * @param context - Execution context with strategyName, exchangeName and frameName
50528
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50529
+ * @returns Promise resolving to PnL percentage or null
50530
+ *
50531
+ * @example
50532
+ * ```typescript
50533
+ * const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
50534
+ * "BTCUSDT",
50535
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50536
+ * );
50537
+ * console.log(`Worst PNL: ${worstPnl}%`);
50538
+ * ```
50539
+ */
50540
+ this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
50541
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
50542
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50543
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50544
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50545
+ {
50546
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50547
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50548
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
50549
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
50550
+ }
50551
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
50552
+ };
50553
+ /**
50554
+ * Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
50555
+ *
50556
+ * Returns null if no pending signal exists.
50557
+ *
50558
+ * @param symbol - Trading pair symbol
50559
+ * @param context - Execution context with strategyName, exchangeName and frameName
50560
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50561
+ * @returns Promise resolving to PnL cost in quote currency or null
50562
+ *
50563
+ * @example
50564
+ * ```typescript
50565
+ * const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
50566
+ * "BTCUSDT",
50567
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50568
+ * );
50569
+ * console.log(`Worst PNL: $${worstCost}`);
50570
+ * ```
50571
+ */
50572
+ this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
50573
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
50574
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50575
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50576
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50577
+ {
50578
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50579
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50580
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
50581
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
50582
+ }
50583
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
50584
+ };
50585
+ /**
50586
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
50587
+ *
50588
+ * Result is ≥ 0. Returns null if no pending signal exists.
50589
+ *
50590
+ * @param symbol - Trading pair symbol
50591
+ * @param context - Execution context with strategyName, exchangeName and frameName
50592
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50593
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
50594
+ *
50595
+ * @example
50596
+ * ```typescript
50597
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
50598
+ * "BTCUSDT",
50599
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50600
+ * );
50601
+ * console.log(`Dropped ${distance}% from peak`);
50602
+ * ```
50603
+ */
50604
+ this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
50605
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
50606
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50607
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50608
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50609
+ {
50610
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50611
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50612
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
50613
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
50614
+ }
50615
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
50616
+ };
50617
+ /**
50618
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
50619
+ *
50620
+ * Result is ≥ 0. Returns null if no pending signal exists.
50621
+ *
50622
+ * @param symbol - Trading pair symbol
50623
+ * @param context - Execution context with strategyName, exchangeName and frameName
50624
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50625
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
50626
+ *
50627
+ * @example
50628
+ * ```typescript
50629
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
50630
+ * "BTCUSDT",
50631
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50632
+ * );
50633
+ * console.log(`Dropped $${distance} from peak`);
50634
+ * ```
50635
+ */
50636
+ this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
50637
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
50638
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50639
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50640
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50641
+ {
50642
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50643
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50644
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
50645
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
50646
+ }
50647
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
50648
+ };
50649
+ /**
50650
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
50651
+ *
50652
+ * Result is ≥ 0. Returns null if no pending signal exists.
50653
+ *
50654
+ * @param symbol - Trading pair symbol
50655
+ * @param context - Execution context with strategyName, exchangeName and frameName
50656
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50657
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
50658
+ *
50659
+ * @example
50660
+ * ```typescript
50661
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
50662
+ * "BTCUSDT",
50663
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50664
+ * );
50665
+ * console.log(`${distance}% above worst trough`);
50666
+ * ```
50667
+ */
50668
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
50669
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
50670
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50671
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50672
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50673
+ {
50674
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50675
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50676
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
50677
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
50678
+ }
50679
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
50680
+ };
50681
+ /**
50682
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
50683
+ *
50684
+ * Result is ≥ 0. Returns null if no pending signal exists.
50685
+ *
50686
+ * @param symbol - Trading pair symbol
50687
+ * @param context - Execution context with strategyName, exchangeName and frameName
50688
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50689
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
50690
+ *
50691
+ * @example
50692
+ * ```typescript
50693
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
50694
+ * "BTCUSDT",
50695
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50696
+ * );
50697
+ * console.log(`$${distance} above worst trough`);
50698
+ * ```
50699
+ */
50700
+ this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
50701
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
50702
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50703
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50704
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50705
+ {
50706
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50707
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50708
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
50709
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
50710
+ }
50711
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
50712
+ };
50713
+ /**
50714
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
50715
+ *
50716
+ * Result is ≥ 0. Returns null if no pending signal exists.
50717
+ *
50718
+ * @param symbol - Trading pair symbol
50719
+ * @param context - Execution context with strategyName, exchangeName and frameName
50720
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50721
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
50722
+ *
50723
+ * @example
50724
+ * ```typescript
50725
+ * const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
50726
+ * "BTCUSDT",
50727
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50728
+ * );
50729
+ * console.log(`Peak-to-trough: ${distance}%`);
50730
+ * ```
50731
+ */
50732
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
50733
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
50734
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50735
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50736
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50737
+ {
50738
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50739
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50740
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
50741
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
50742
+ }
50743
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
50744
+ };
50745
+ /**
50746
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
50747
+ *
50748
+ * Result is ≥ 0. Returns null if no pending signal exists.
50749
+ *
50750
+ * @param symbol - Trading pair symbol
50751
+ * @param context - Execution context with strategyName, exchangeName and frameName
50752
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50753
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
50754
+ *
50755
+ * @example
50756
+ * ```typescript
50757
+ * const distance = await Reflect.getMaxDrawdownDistancePnlCost(
50758
+ * "BTCUSDT",
50759
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50760
+ * );
50761
+ * console.log(`Peak-to-trough: $${distance}`);
50762
+ * ```
50763
+ */
50764
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
50765
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
50766
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50767
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50768
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50769
+ {
50770
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50771
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50772
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
50773
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
50774
+ }
50775
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
50776
+ };
50777
+ }
50778
+ }
50779
+ /**
50780
+ * Singleton instance of ReflectUtils for convenient position state queries.
50781
+ *
50782
+ * @example
50783
+ * ```typescript
50784
+ * import { Reflect } from "backtest-kit";
50785
+ *
50786
+ * // Real-time PNL
50787
+ * const pnl = await Reflect.getPositionPnlPercent(
50788
+ * "BTCUSDT",
50789
+ * 45000,
50790
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50791
+ * );
50792
+ * console.log(`PNL: ${pnl}%`);
50793
+ *
50794
+ * // Peak profit
50795
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50796
+ * "BTCUSDT",
50797
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50798
+ * );
50799
+ * console.log(`Peak PNL: ${peakPnl}%`);
50800
+ *
50801
+ * // Drawdown from peak
50802
+ * const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
50803
+ * "BTCUSDT",
50804
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50805
+ * );
50806
+ * console.log(`Dropped ${drawdown}% from peak`);
50807
+ * ```
50808
+ */
50809
+ const Reflect$1 = new ReflectUtils();
50810
+
49738
50811
  /**
49739
50812
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
49740
50813
  *
@@ -51665,6 +52738,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51665
52738
  pnlEntries: data.signal.pnl.pnlEntries,
51666
52739
  scheduledAt: data.signal.scheduledAt,
51667
52740
  currentPrice: data.currentPrice,
52741
+ note: data.signal.note,
51668
52742
  createdAt: data.createdAt,
51669
52743
  };
51670
52744
  }
@@ -51694,6 +52768,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51694
52768
  duration: durationMin,
51695
52769
  scheduledAt: data.signal.scheduledAt,
51696
52770
  pendingAt: data.signal.pendingAt,
52771
+ note: data.signal.note,
51697
52772
  createdAt: data.createdAt,
51698
52773
  };
51699
52774
  }
@@ -51730,6 +52805,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
51730
52805
  pnlPriceClose: data.data.pnl.priceClose,
51731
52806
  pnlCost: data.data.pnl.pnlCost,
51732
52807
  pnlEntries: data.data.pnl.pnlEntries,
52808
+ note: data.data.note,
51733
52809
  scheduledAt: data.data.scheduledAt,
51734
52810
  pendingAt: data.data.pendingAt,
51735
52811
  createdAt: data.timestamp,
@@ -51765,6 +52841,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
51765
52841
  pnlPriceClose: data.data.pnl.priceClose,
51766
52842
  pnlCost: data.data.pnl.pnlCost,
51767
52843
  pnlEntries: data.data.pnl.pnlEntries,
52844
+ note: data.data.note,
51768
52845
  scheduledAt: data.data.scheduledAt,
51769
52846
  pendingAt: data.data.pendingAt,
51770
52847
  createdAt: data.timestamp,
@@ -51799,6 +52876,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
51799
52876
  pnlPriceClose: data.data.pnl.priceClose,
51800
52877
  pnlCost: data.data.pnl.pnlCost,
51801
52878
  pnlEntries: data.data.pnl.pnlEntries,
52879
+ note: data.data.note,
51802
52880
  scheduledAt: data.data.scheduledAt,
51803
52881
  pendingAt: data.data.pendingAt,
51804
52882
  createdAt: data.timestamp,
@@ -51840,6 +52918,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51840
52918
  pnlEntries: data.pnl.pnlEntries,
51841
52919
  scheduledAt: data.scheduledAt,
51842
52920
  pendingAt: data.pendingAt,
52921
+ note: data.note,
51843
52922
  createdAt: data.timestamp,
51844
52923
  };
51845
52924
  }
@@ -51872,6 +52951,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51872
52951
  pnlEntries: data.pnl.pnlEntries,
51873
52952
  scheduledAt: data.scheduledAt,
51874
52953
  pendingAt: data.pendingAt,
52954
+ note: data.note,
51875
52955
  createdAt: data.timestamp,
51876
52956
  };
51877
52957
  }
@@ -51903,6 +52983,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51903
52983
  pnlEntries: data.pnl.pnlEntries,
51904
52984
  scheduledAt: data.scheduledAt,
51905
52985
  pendingAt: data.pendingAt,
52986
+ note: data.note,
51906
52987
  createdAt: data.timestamp,
51907
52988
  };
51908
52989
  }
@@ -51935,6 +53016,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51935
53016
  pnlEntries: data.pnl.pnlEntries,
51936
53017
  scheduledAt: data.scheduledAt,
51937
53018
  pendingAt: data.pendingAt,
53019
+ note: data.note,
51938
53020
  createdAt: data.timestamp,
51939
53021
  };
51940
53022
  }
@@ -51967,6 +53049,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51967
53049
  pnlEntries: data.pnl.pnlEntries,
51968
53050
  scheduledAt: data.scheduledAt,
51969
53051
  pendingAt: data.pendingAt,
53052
+ note: data.note,
51970
53053
  createdAt: data.timestamp,
51971
53054
  };
51972
53055
  }
@@ -51999,6 +53082,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51999
53082
  pnlEntries: data.pnl.pnlEntries,
52000
53083
  scheduledAt: data.scheduledAt,
52001
53084
  pendingAt: data.pendingAt,
53085
+ note: data.note,
52002
53086
  createdAt: data.timestamp,
52003
53087
  };
52004
53088
  }
@@ -52032,6 +53116,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52032
53116
  pnlEntries: data.pnl.pnlEntries,
52033
53117
  scheduledAt: data.scheduledAt,
52034
53118
  pendingAt: data.pendingAt,
53119
+ note: data.note,
52035
53120
  createdAt: data.timestamp,
52036
53121
  };
52037
53122
  }
@@ -52055,6 +53140,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52055
53140
  pnlPriceClose: data.pnl.priceClose,
52056
53141
  pnlCost: data.pnl.pnlCost,
52057
53142
  pnlEntries: data.pnl.pnlEntries,
53143
+ note: data.note,
52058
53144
  createdAt: data.timestamp,
52059
53145
  };
52060
53146
  }
@@ -52078,6 +53164,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52078
53164
  pnlPriceClose: data.pnl.priceClose,
52079
53165
  pnlCost: data.pnl.pnlCost,
52080
53166
  pnlEntries: data.pnl.pnlEntries,
53167
+ note: data.note,
52081
53168
  createdAt: data.timestamp,
52082
53169
  };
52083
53170
  }
@@ -52119,6 +53206,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52119
53206
  totalPartials: data.totalPartials,
52120
53207
  scheduledAt: data.scheduledAt,
52121
53208
  pendingAt: data.pendingAt,
53209
+ note: data.signal.note,
52122
53210
  createdAt: data.timestamp,
52123
53211
  };
52124
53212
  }
@@ -52151,6 +53239,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52151
53239
  scheduledAt: data.scheduledAt,
52152
53240
  pendingAt: data.pendingAt,
52153
53241
  closeReason: data.closeReason,
53242
+ note: data.signal.note,
52154
53243
  createdAt: data.timestamp,
52155
53244
  };
52156
53245
  }
@@ -53650,6 +54739,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53650
54739
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
53651
54740
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53652
54741
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
54742
+ const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53653
54743
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
53654
54744
  const MS_PER_MINUTE$1 = 60000;
53655
54745
  const INTERVAL_MINUTES$1 = {
@@ -53901,7 +54991,7 @@ class CacheFileInstance {
53901
54991
  /**
53902
54992
  * Clears the index counter.
53903
54993
  */
53904
- static clearCounter() {
54994
+ static resetCounter() {
53905
54995
  CacheFileInstance._indexCounter = 0;
53906
54996
  }
53907
54997
  /**
@@ -54156,11 +55246,17 @@ class CacheUtils {
54156
55246
  */
54157
55247
  this.clear = () => {
54158
55248
  backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
54159
- {
54160
- this._getFnInstance.clear();
54161
- this._getFileInstance.clear();
54162
- }
54163
- CacheFileInstance.clearCounter();
55249
+ this._getFnInstance.clear();
55250
+ this._getFileInstance.clear();
55251
+ };
55252
+ /**
55253
+ * Resets the CacheFileInstance index counter to zero.
55254
+ * This is useful when process.cwd() changes between strategy iterations to ensure
55255
+ * that new CacheFileInstance objects start with index 0 and do not collide with old instances.
55256
+ */
55257
+ this.resetCounter = () => {
55258
+ backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
55259
+ CacheFileInstance.resetCounter();
54164
55260
  };
54165
55261
  }
54166
55262
  }
@@ -54184,10 +55280,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
54184
55280
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
54185
55281
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
54186
55282
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
55283
+ const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
54187
55284
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
54188
55285
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
54189
55286
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
54190
55287
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
55288
+ const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
54191
55289
  const MS_PER_MINUTE = 60000;
54192
55290
  const INTERVAL_MINUTES = {
54193
55291
  "1m": 1,
@@ -54254,6 +55352,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
54254
55352
  *
54255
55353
  * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
54256
55354
  *
55355
+ * @template F - Concrete function type
55356
+ *
54257
55357
  * @example
54258
55358
  * ```typescript
54259
55359
  * const instance = new IntervalFnInstance(mySignalFn, "1h");
@@ -54269,30 +55369,34 @@ class IntervalFnInstance {
54269
55369
  *
54270
55370
  * @param fn - Function to fire once per interval
54271
55371
  * @param interval - Candle interval that controls the firing boundary
55372
+ * @param key - Optional key generator for argument-based state separation.
55373
+ * Default: `([symbol]) => symbol`
54272
55374
  */
54273
- constructor(fn, interval) {
55375
+ constructor(fn, interval, key = ([symbol]) => symbol) {
54274
55376
  this.fn = fn;
54275
55377
  this.interval = interval;
54276
- /** Stores the last aligned timestamp per context+symbol key. */
55378
+ this.key = key;
55379
+ /** Stores the last aligned timestamp per context+symbol+args key. */
54277
55380
  this._stateMap = new Map();
54278
55381
  /**
54279
55382
  * Execute the signal function with once-per-interval enforcement.
54280
55383
  *
54281
55384
  * Algorithm:
54282
55385
  * 1. Align the current execution context `when` to the interval boundary.
54283
- * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
54284
- * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
55386
+ * 2. Build state key from context + key generator result.
55387
+ * 3. If the stored aligned timestamp for this key equals the current one return `null`.
55388
+ * 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54285
55389
  * the signal. If it returns `null`, leave state unchanged so the next call retries.
54286
55390
  *
54287
55391
  * Requires active method context and execution context.
54288
55392
  *
54289
- * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
55393
+ * @param args - Arguments forwarded to the wrapped function
54290
55394
  * @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
54291
55395
  * within the same interval or when `fn` itself returned `null`
54292
55396
  * @throws Error if method context, execution context, or interval is missing
54293
55397
  */
54294
- this.run = async (symbol) => {
54295
- backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
55398
+ this.run = async (...args) => {
55399
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
54296
55400
  const step = INTERVAL_MINUTES[this.interval];
54297
55401
  {
54298
55402
  if (!MethodContextService.hasContext()) {
@@ -54306,15 +55410,16 @@ class IntervalFnInstance {
54306
55410
  }
54307
55411
  }
54308
55412
  const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
54309
- const key = `${contextKey}:${symbol}`;
54310
55413
  const currentWhen = backtest.executionContextService.context.when;
54311
55414
  const currentAligned = align(currentWhen.getTime(), this.interval);
54312
- if (this._stateMap.get(key) === currentAligned) {
55415
+ const argKey = this.key(args);
55416
+ const stateKey = `${contextKey}:${argKey}`;
55417
+ if (this._stateMap.get(stateKey) === currentAligned) {
54313
55418
  return null;
54314
55419
  }
54315
- const result = await this.fn(symbol, currentWhen);
55420
+ const result = await this.fn.apply(null, args);
54316
55421
  if (result !== null) {
54317
- this._stateMap.set(key, currentAligned);
55422
+ this._stateMap.set(stateKey, currentAligned);
54318
55423
  }
54319
55424
  return result;
54320
55425
  };
@@ -54335,6 +55440,28 @@ class IntervalFnInstance {
54335
55440
  }
54336
55441
  }
54337
55442
  };
55443
+ /**
55444
+ * Garbage collect expired state entries.
55445
+ *
55446
+ * Removes all entries whose aligned timestamp differs from the current interval boundary.
55447
+ * Call this periodically to free memory from stale state entries.
55448
+ *
55449
+ * Requires active execution context to get current time.
55450
+ *
55451
+ * @returns Number of entries removed
55452
+ */
55453
+ this.gc = () => {
55454
+ const currentWhen = backtest.executionContextService.context.when;
55455
+ const currentAligned = align(currentWhen.getTime(), this.interval);
55456
+ let removed = 0;
55457
+ for (const [key, storedAligned] of this._stateMap.entries()) {
55458
+ if (storedAligned !== currentAligned) {
55459
+ this._stateMap.delete(key);
55460
+ removed++;
55461
+ }
55462
+ }
55463
+ return removed;
55464
+ };
54338
55465
  }
54339
55466
  }
54340
55467
  /**
@@ -54348,7 +55475,7 @@ class IntervalFnInstance {
54348
55475
  *
54349
55476
  * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
54350
55477
  *
54351
- * @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
55478
+ * @template F - Concrete async function type
54352
55479
  *
54353
55480
  * @example
54354
55481
  * ```typescript
@@ -54369,7 +55496,7 @@ class IntervalFileInstance {
54369
55496
  * Resets the index counter to zero.
54370
55497
  * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
54371
55498
  */
54372
- static clearCounter() {
55499
+ static resetCounter() {
54373
55500
  IntervalFileInstance._indexCounter = 0;
54374
55501
  }
54375
55502
  /**
@@ -54378,18 +55505,21 @@ class IntervalFileInstance {
54378
55505
  * @param fn - Async signal function to fire once per interval
54379
55506
  * @param interval - Candle interval that controls the firing boundary
54380
55507
  * @param name - Human-readable bucket name used as the directory prefix
55508
+ * @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
55509
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54381
55510
  */
54382
- constructor(fn, interval, name) {
55511
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
54383
55512
  this.fn = fn;
54384
55513
  this.interval = interval;
54385
55514
  this.name = name;
55515
+ this.key = key;
54386
55516
  /**
54387
55517
  * Execute the async function with persistent once-per-interval enforcement.
54388
55518
  *
54389
55519
  * Algorithm:
54390
55520
  * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
54391
- * 2. Align execution context `when` to interval boundary → `alignedTs`.
54392
- * 3. Build entity key = `${symbol}_${alignedTs}`.
55521
+ * 2. Align execution context `when` to interval boundary → `alignedMs`.
55522
+ * 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
54393
55523
  * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
54394
55524
  * 5. On hit — return `null` (interval already fired).
54395
55525
  * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
@@ -54397,12 +55527,13 @@ class IntervalFileInstance {
54397
55527
  * Requires active method context and execution context.
54398
55528
  *
54399
55529
  * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
55530
+ * @param args - Additional arguments forwarded to the wrapped function
54400
55531
  * @returns The value on the first non-null fire, `null` if already fired this interval
54401
55532
  * or if `fn` itself returned `null`
54402
55533
  * @throws Error if method context, execution context, or interval is missing
54403
55534
  */
54404
- this.run = async (symbol) => {
54405
- backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
55535
+ this.run = async (...args) => {
55536
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
54406
55537
  const step = INTERVAL_MINUTES[this.interval];
54407
55538
  {
54408
55539
  if (!MethodContextService.hasContext()) {
@@ -54415,15 +55546,16 @@ class IntervalFileInstance {
54415
55546
  throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
54416
55547
  }
54417
55548
  }
55549
+ const [symbol, ...rest] = args;
54418
55550
  const { when } = backtest.executionContextService.context;
54419
- const alignedTs = align(when.getTime(), this.interval);
55551
+ const alignedMs = align(when.getTime(), this.interval);
54420
55552
  const bucket = `${this.name}_${this.interval}_${this.index}`;
54421
- const entityKey = `${symbol}_${alignedTs}`;
55553
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
54422
55554
  const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
54423
55555
  if (cached !== null) {
54424
55556
  return null;
54425
55557
  }
54426
- const result = await this.fn(symbol, when);
55558
+ const result = await this.fn.call(null, ...args);
54427
55559
  if (result !== null) {
54428
55560
  await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
54429
55561
  }
@@ -54464,12 +55596,12 @@ class IntervalUtils {
54464
55596
  * Memoized factory to get or create an `IntervalFnInstance` for a function.
54465
55597
  * Each function reference gets its own isolated instance.
54466
55598
  */
54467
- this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
55599
+ this._getInstance = functoolsKit.memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
54468
55600
  /**
54469
55601
  * Memoized factory to get or create an `IntervalFileInstance` for an async function.
54470
55602
  * Each function reference gets its own isolated persistent instance.
54471
55603
  */
54472
- this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
55604
+ this._getFileInstance = functoolsKit.memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
54473
55605
  /**
54474
55606
  * Wrap a signal function with in-memory once-per-interval firing.
54475
55607
  *
@@ -54481,21 +55613,30 @@ class IntervalUtils {
54481
55613
  *
54482
55614
  * @param run - Signal function to wrap
54483
55615
  * @param context.interval - Candle interval that controls the firing boundary
54484
- * @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
55616
+ * @param context.key - Optional key generator for argument-based state separation
55617
+ * @returns Wrapped function with the same signature as `F`, plus a `clear()` method
54485
55618
  *
54486
55619
  * @example
54487
55620
  * ```typescript
55621
+ * // Without extra args
54488
55622
  * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
54489
- *
54490
55623
  * await fireOnce("BTCUSDT"); // → T or null (fn called)
54491
55624
  * await fireOnce("BTCUSDT"); // → null (same interval, skipped)
55625
+ *
55626
+ * // With extra args and key
55627
+ * const fireOnce = Interval.fn(mySignalFn, {
55628
+ * interval: "15m",
55629
+ * key: ([symbol, period]) => `${symbol}_${period}`,
55630
+ * });
55631
+ * await fireOnce("BTCUSDT", 14); // → T or null
55632
+ * await fireOnce("BTCUSDT", 28); // → T or null (separate state)
54492
55633
  * ```
54493
55634
  */
54494
55635
  this.fn = (run, context) => {
54495
55636
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
54496
- const wrappedFn = (symbol) => {
54497
- const instance = this._getInstance(run, context.interval);
54498
- return instance.run(symbol);
55637
+ const wrappedFn = (...args) => {
55638
+ const instance = this._getInstance(run, context.interval, context.key);
55639
+ return instance.run(...args);
54499
55640
  };
54500
55641
  wrappedFn.clear = () => {
54501
55642
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
@@ -54509,6 +55650,14 @@ class IntervalUtils {
54509
55650
  }
54510
55651
  this._getInstance.get(run)?.clear();
54511
55652
  };
55653
+ wrappedFn.gc = () => {
55654
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
55655
+ if (!ExecutionContextService.hasContext()) {
55656
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
55657
+ return;
55658
+ }
55659
+ return this._getInstance.get(run)?.gc();
55660
+ };
54512
55661
  return wrappedFn;
54513
55662
  };
54514
55663
  /**
@@ -54521,28 +55670,33 @@ class IntervalUtils {
54521
55670
  * The `run` function reference is used as the memoization key for the underlying
54522
55671
  * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
54523
55672
  *
54524
- * @template T - Async function type to wrap
55673
+ * @template F - Concrete async function type
54525
55674
  * @param run - Async signal function to wrap with persistent once-per-interval firing
54526
55675
  * @param context.interval - Candle interval that controls the firing boundary
54527
55676
  * @param context.name - Human-readable bucket name; becomes the directory prefix
54528
- * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
54529
- * that deletes persisted records from disk and disposes the memoized instance
55677
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
55678
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
55679
+ * @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
54530
55680
  *
54531
55681
  * @example
54532
55682
  * ```typescript
54533
- * const fetchSignal = async (symbol: string, when: Date) => { ... };
54534
- * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
54535
- * await fireOnce.clear(); // delete disk records so the function fires again next call
55683
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
55684
+ * const fireOnce = Interval.file(fetchSignal, {
55685
+ * interval: "1h",
55686
+ * name: "fetchSignal",
55687
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
55688
+ * });
55689
+ * await fireOnce("BTCUSDT", 14);
54536
55690
  * ```
54537
55691
  */
54538
55692
  this.file = (run, context) => {
54539
55693
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
54540
55694
  {
54541
- this._getFileInstance(run, context.interval, context.name);
55695
+ this._getFileInstance(run, context.interval, context.name, context.key);
54542
55696
  }
54543
- const wrappedFn = (symbol) => {
54544
- const instance = this._getFileInstance(run, context.interval, context.name);
54545
- return instance.run(symbol);
55697
+ const wrappedFn = (...args) => {
55698
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
55699
+ return instance.run(...args);
54546
55700
  };
54547
55701
  wrappedFn.clear = async () => {
54548
55702
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
@@ -54568,10 +55722,10 @@ class IntervalUtils {
54568
55722
  this.dispose = (run) => {
54569
55723
  backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
54570
55724
  this._getInstance.clear(run);
55725
+ this._getFileInstance.clear(run);
54571
55726
  };
54572
55727
  /**
54573
- * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
54574
- * resets the `IntervalFileInstance` index counter.
55728
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
54575
55729
  * Call this when `process.cwd()` changes between strategy iterations
54576
55730
  * so new instances are created with the updated base path.
54577
55731
  */
@@ -54579,7 +55733,15 @@ class IntervalUtils {
54579
55733
  backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
54580
55734
  this._getInstance.clear();
54581
55735
  this._getFileInstance.clear();
54582
- IntervalFileInstance.clearCounter();
55736
+ };
55737
+ /**
55738
+ * Resets the IntervalFileInstance index counter to zero.
55739
+ * This is useful when process.cwd() changes between strategy iterations to ensure
55740
+ * that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
55741
+ */
55742
+ this.resetCounter = () => {
55743
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
55744
+ IntervalFileInstance.resetCounter();
54583
55745
  };
54584
55746
  }
54585
55747
  }
@@ -55755,6 +56917,7 @@ exports.PersistSignalAdapter = PersistSignalAdapter;
55755
56917
  exports.PersistStorageAdapter = PersistStorageAdapter;
55756
56918
  exports.Position = Position;
55757
56919
  exports.PositionSize = PositionSize;
56920
+ exports.Reflect = Reflect$1;
55758
56921
  exports.Report = Report;
55759
56922
  exports.ReportBase = ReportBase;
55760
56923
  exports.ReportWriter = ReportWriter;
@@ -55813,6 +56976,8 @@ exports.getDefaultConfig = getDefaultConfig;
55813
56976
  exports.getEffectivePriceOpen = getEffectivePriceOpen;
55814
56977
  exports.getExchangeSchema = getExchangeSchema;
55815
56978
  exports.getFrameSchema = getFrameSchema;
56979
+ exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
56980
+ exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
55816
56981
  exports.getMode = getMode;
55817
56982
  exports.getNextCandles = getNextCandles;
55818
56983
  exports.getOrderBook = getOrderBook;