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.mjs CHANGED
@@ -4127,53 +4127,6 @@ const toProfitLossDto = (signal, priceClose) => {
4127
4127
  };
4128
4128
  };
4129
4129
 
4130
- /**
4131
- * Converts markdown content to plain text with minimal formatting
4132
- * @param content - Markdown string to convert
4133
- * @returns Plain text representation
4134
- */
4135
- const toPlainString = (content) => {
4136
- if (!content) {
4137
- return "";
4138
- }
4139
- let text = content;
4140
- // Remove code blocks
4141
- text = text.replace(/```[\s\S]*?```/g, "");
4142
- text = text.replace(/`([^`]+)`/g, "$1");
4143
- // Remove images
4144
- text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
4145
- // Convert links to text only (keep link text, remove URL)
4146
- text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
4147
- // Remove headers (convert to plain text)
4148
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
4149
- // Remove bold and italic markers
4150
- text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
4151
- text = text.replace(/\*\*(.+?)\*\*/g, "$1");
4152
- text = text.replace(/\*(.+?)\*/g, "$1");
4153
- text = text.replace(/___(.+?)___/g, "$1");
4154
- text = text.replace(/__(.+?)__/g, "$1");
4155
- text = text.replace(/_(.+?)_/g, "$1");
4156
- // Remove strikethrough
4157
- text = text.replace(/~~(.+?)~~/g, "$1");
4158
- // Convert lists to plain text with bullets
4159
- text = text.replace(/^\s*[-*+]\s+/gm, "• ");
4160
- text = text.replace(/^\s*\d+\.\s+/gm, "• ");
4161
- // Remove blockquotes
4162
- text = text.replace(/^\s*>\s+/gm, "");
4163
- // Remove horizontal rules
4164
- text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
4165
- // Remove HTML tags
4166
- text = text.replace(/<[^>]+>/g, "");
4167
- // Remove excessive whitespace and normalize line breaks
4168
- text = text.replace(/\n[\s\n]*\n/g, "\n");
4169
- text = text.replace(/[ \t]+/g, " ");
4170
- // Remove all newline characters
4171
- text = text.replace(/\n/g, " ");
4172
- // Remove excessive spaces after newline removal
4173
- text = text.replace(/\s+/g, " ");
4174
- return text.trim();
4175
- };
4176
-
4177
4130
  /**
4178
4131
  * Returns the total closed state of a position using costBasisAtClose snapshots.
4179
4132
  *
@@ -4808,6 +4761,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4808
4761
  originalPriceOpen: publicSignal.originalPriceOpen,
4809
4762
  scheduledAt: publicSignal.scheduledAt,
4810
4763
  pendingAt: publicSignal.pendingAt,
4764
+ note: publicSignal.note,
4811
4765
  });
4812
4766
  continue;
4813
4767
  }
@@ -4835,6 +4789,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4835
4789
  originalPriceOpen: publicSignal.originalPriceOpen,
4836
4790
  scheduledAt: publicSignal.scheduledAt,
4837
4791
  pendingAt: publicSignal.pendingAt,
4792
+ note: publicSignal.note,
4838
4793
  });
4839
4794
  continue;
4840
4795
  }
@@ -4861,6 +4816,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4861
4816
  originalPriceOpen: publicSignal.originalPriceOpen,
4862
4817
  scheduledAt: publicSignal.scheduledAt,
4863
4818
  pendingAt: publicSignal.pendingAt,
4819
+ note: publicSignal.note,
4864
4820
  });
4865
4821
  continue;
4866
4822
  }
@@ -4888,6 +4844,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4888
4844
  originalPriceOpen: publicSignal.originalPriceOpen,
4889
4845
  scheduledAt: publicSignal.scheduledAt,
4890
4846
  pendingAt: publicSignal.pendingAt,
4847
+ note: publicSignal.note,
4891
4848
  });
4892
4849
  continue;
4893
4850
  }
@@ -4915,6 +4872,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4915
4872
  originalPriceOpen: publicSignal.originalPriceOpen,
4916
4873
  scheduledAt: publicSignal.scheduledAt,
4917
4874
  pendingAt: publicSignal.pendingAt,
4875
+ note: publicSignal.note,
4918
4876
  });
4919
4877
  continue;
4920
4878
  }
@@ -4944,6 +4902,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, currentPrice, timestamp) => {
4944
4902
  originalPriceOpen: publicSignal.originalPriceOpen,
4945
4903
  scheduledAt: publicSignal.scheduledAt,
4946
4904
  pendingAt: publicSignal.pendingAt,
4905
+ note: publicSignal.note,
4947
4906
  });
4948
4907
  continue;
4949
4908
  }
@@ -5042,7 +5001,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5042
5001
  const currentPrice = await self.params.exchange.getAveragePrice(self.params.execution.context.symbol);
5043
5002
  const timeoutMs = GLOBAL_CONFIG.CC_MAX_SIGNAL_GENERATION_SECONDS * 1000;
5044
5003
  const signal = await Promise.race([
5045
- self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when),
5004
+ self.params.getSignal(self.params.execution.context.symbol, self.params.execution.context.when, currentPrice),
5046
5005
  sleep(timeoutMs).then(() => TIMEOUT_SYMBOL),
5047
5006
  ]);
5048
5007
  if (typeof signal === "symbol") {
@@ -5072,7 +5031,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5072
5031
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5073
5032
  priceOpen: signal.priceOpen, // Используем priceOpen из сигнала
5074
5033
  position: signal.position,
5075
- note: toPlainString(signal.note),
5034
+ note: signal.note || "",
5076
5035
  priceTakeProfit: signal.priceTakeProfit,
5077
5036
  priceStopLoss: signal.priceStopLoss,
5078
5037
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5098,7 +5057,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5098
5057
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5099
5058
  priceOpen: signal.priceOpen,
5100
5059
  position: signal.position,
5101
- note: toPlainString(signal.note),
5060
+ note: signal.note || "",
5102
5061
  priceTakeProfit: signal.priceTakeProfit,
5103
5062
  priceStopLoss: signal.priceStopLoss,
5104
5063
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
@@ -5123,7 +5082,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
5123
5082
  cost: signal.cost || GLOBAL_CONFIG.CC_POSITION_ENTRY_COST,
5124
5083
  priceOpen: currentPrice,
5125
5084
  ...structuredClone(signal),
5126
- note: toPlainString(signal.note),
5085
+ note: signal.note || "",
5127
5086
  minuteEstimatedTime: signal.minuteEstimatedTime ?? GLOBAL_CONFIG.CC_MAX_SIGNAL_LIFETIME_MINUTES,
5128
5087
  symbol: self.params.execution.context.symbol,
5129
5088
  exchangeName: self.params.method.context.exchangeName,
@@ -5870,6 +5829,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5870
5829
  totalPartials: scheduled._partial?.length ?? 0,
5871
5830
  originalPriceOpen: scheduled.priceOpen,
5872
5831
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
5832
+ note: scheduled.note,
5873
5833
  });
5874
5834
  return null;
5875
5835
  }
@@ -6613,7 +6573,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
6613
6573
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
6614
6574
  return result;
6615
6575
  };
6616
- const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
6576
+ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
6617
6577
  self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
6618
6578
  symbol: self.params.execution.context.symbol,
6619
6579
  signalId: scheduled.id,
@@ -6638,6 +6598,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6638
6598
  totalPartials: scheduled._partial?.length ?? 0,
6639
6599
  originalPriceOpen: scheduled.priceOpen,
6640
6600
  pnl: toProfitLossDto(scheduled, averagePrice),
6601
+ note: cancelNote ?? scheduled.note,
6641
6602
  });
6642
6603
  }
6643
6604
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6716,6 +6677,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
6716
6677
  totalPartials: scheduled._partial?.length ?? 0,
6717
6678
  originalPriceOpen: scheduled.priceOpen,
6718
6679
  pnl: toProfitLossDto(scheduled, scheduled.priceOpen),
6680
+ note: scheduled.note,
6719
6681
  });
6720
6682
  return false;
6721
6683
  }
@@ -6803,6 +6765,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6803
6765
  totalPartials: closedSignal._partial?.length ?? 0,
6804
6766
  originalPriceOpen: closedSignal.priceOpen,
6805
6767
  pnl: toProfitLossDto(closedSignal, averagePrice),
6768
+ note: closedSignal.closeNote ?? closedSignal.note,
6806
6769
  });
6807
6770
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6808
6771
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6849,7 +6812,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6849
6812
  if (self._cancelledSignal) {
6850
6813
  // Сигнал был отменен через cancel() в onSchedulePing
6851
6814
  const cancelId = self._cancelledSignal.cancelId;
6852
- const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
6815
+ const cancelNote = self._cancelledSignal.cancelNote;
6816
+ const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
6853
6817
  return { outcome: "cancelled", result };
6854
6818
  }
6855
6819
  // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
@@ -6903,6 +6867,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6903
6867
  totalPartials: activatedSignal._partial?.length ?? 0,
6904
6868
  originalPriceOpen: activatedSignal.priceOpen,
6905
6869
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6870
+ note: activatedSignal.activateNote ?? activatedSignal.note,
6906
6871
  });
6907
6872
  return { outcome: "pending" };
6908
6873
  }
@@ -6934,6 +6899,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6934
6899
  pendingAt: publicSignalForCommit.pendingAt,
6935
6900
  totalEntries: publicSignalForCommit.totalEntries,
6936
6901
  totalPartials: publicSignalForCommit.totalPartials,
6902
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
6937
6903
  });
6938
6904
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6939
6905
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -8054,6 +8020,44 @@ class ClientStrategy {
8054
8020
  const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
8055
8021
  return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
8056
8022
  }
8023
+ /**
8024
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
8025
+ *
8026
+ * Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
8027
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
8028
+ *
8029
+ * Returns null if no pending signal exists.
8030
+ *
8031
+ * @param symbol - Trading pair symbol
8032
+ * @param currentPrice - Current market price
8033
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
8034
+ */
8035
+ async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
8036
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
8037
+ if (!this._pendingSignal) {
8038
+ return null;
8039
+ }
8040
+ return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
8041
+ }
8042
+ /**
8043
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
8044
+ *
8045
+ * Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
8046
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
8047
+ *
8048
+ * Returns null if no pending signal exists.
8049
+ *
8050
+ * @param symbol - Trading pair symbol
8051
+ * @param currentPrice - Current market price
8052
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
8053
+ */
8054
+ async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
8055
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
8056
+ if (!this._pendingSignal) {
8057
+ return null;
8058
+ }
8059
+ return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
8060
+ }
8057
8061
  /**
8058
8062
  * Performs a single tick of strategy execution.
8059
8063
  *
@@ -8118,6 +8122,7 @@ class ClientStrategy {
8118
8122
  totalPartials: cancelledSignal._partial?.length ?? 0,
8119
8123
  originalPriceOpen: cancelledSignal.priceOpen,
8120
8124
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8125
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8121
8126
  });
8122
8127
  // Call onCancel callback
8123
8128
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8171,6 +8176,7 @@ class ClientStrategy {
8171
8176
  totalPartials: closedSignal._partial?.length ?? 0,
8172
8177
  originalPriceOpen: closedSignal.priceOpen,
8173
8178
  pnl: toProfitLossDto(closedSignal, currentPrice),
8179
+ note: closedSignal.closeNote ?? closedSignal.note,
8174
8180
  });
8175
8181
  // Call onClose callback
8176
8182
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8252,6 +8258,7 @@ class ClientStrategy {
8252
8258
  totalPartials: activatedSignal._partial?.length ?? 0,
8253
8259
  originalPriceOpen: activatedSignal.priceOpen,
8254
8260
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8261
+ note: activatedSignal.activateNote ?? activatedSignal.note,
8255
8262
  });
8256
8263
  return await RETURN_IDLE_FN(this, currentPrice);
8257
8264
  }
@@ -8282,6 +8289,7 @@ class ClientStrategy {
8282
8289
  pendingAt: publicSignalForCommit.pendingAt,
8283
8290
  totalEntries: publicSignalForCommit.totalEntries,
8284
8291
  totalPartials: publicSignalForCommit.totalPartials,
8292
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
8285
8293
  });
8286
8294
  // Call onOpen callback
8287
8295
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8417,6 +8425,7 @@ class ClientStrategy {
8417
8425
  totalPartials: cancelledSignal._partial?.length ?? 0,
8418
8426
  originalPriceOpen: cancelledSignal.priceOpen,
8419
8427
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8428
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8420
8429
  });
8421
8430
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8422
8431
  const cancelledResult = {
@@ -8471,6 +8480,7 @@ class ClientStrategy {
8471
8480
  totalPartials: closedSignal._partial?.length ?? 0,
8472
8481
  originalPriceOpen: closedSignal.priceOpen,
8473
8482
  pnl: toProfitLossDto(closedSignal, currentPrice),
8483
+ note: closedSignal.closeNote ?? closedSignal.note,
8474
8484
  });
8475
8485
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8476
8486
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -8654,7 +8664,8 @@ class ClientStrategy {
8654
8664
  * // Strategy continues, can generate new signals
8655
8665
  * ```
8656
8666
  */
8657
- async cancelScheduled(symbol, backtest, cancelId) {
8667
+ async cancelScheduled(symbol, backtest, payload) {
8668
+ const cancelId = payload.id;
8658
8669
  this.params.logger.debug("ClientStrategy cancelScheduled", {
8659
8670
  symbol,
8660
8671
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8666,6 +8677,7 @@ class ClientStrategy {
8666
8677
  if (this._scheduledSignal) {
8667
8678
  this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
8668
8679
  cancelId,
8680
+ cancelNote: payload.note,
8669
8681
  });
8670
8682
  this._scheduledSignal = null;
8671
8683
  }
@@ -8697,7 +8709,8 @@ class ClientStrategy {
8697
8709
  * // Scheduled signal becomes pending signal immediately
8698
8710
  * ```
8699
8711
  */
8700
- async activateScheduled(symbol, backtest, activateId) {
8712
+ async activateScheduled(symbol, backtest, payload) {
8713
+ const activateId = payload.id;
8701
8714
  this.params.logger.debug("ClientStrategy activateScheduled", {
8702
8715
  symbol,
8703
8716
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8715,6 +8728,7 @@ class ClientStrategy {
8715
8728
  if (this._scheduledSignal) {
8716
8729
  this._activatedSignal = Object.assign({}, this._scheduledSignal, {
8717
8730
  activateId,
8731
+ activateNote: payload.note,
8718
8732
  });
8719
8733
  this._scheduledSignal = null;
8720
8734
  }
@@ -8746,7 +8760,8 @@ class ClientStrategy {
8746
8760
  * // Strategy continues, can generate new signals
8747
8761
  * ```
8748
8762
  */
8749
- async closePending(symbol, backtest, closeId) {
8763
+ async closePending(symbol, backtest, payload) {
8764
+ const closeId = payload.id;
8750
8765
  this.params.logger.debug("ClientStrategy closePending", {
8751
8766
  symbol,
8752
8767
  hasPendingSignal: this._pendingSignal !== null,
@@ -8757,6 +8772,7 @@ class ClientStrategy {
8757
8772
  if (this._pendingSignal) {
8758
8773
  this._closedSignal = Object.assign({}, this._pendingSignal, {
8759
8774
  closeId,
8775
+ closeNote: payload.note,
8760
8776
  });
8761
8777
  this._pendingSignal = null;
8762
8778
  }
@@ -10020,6 +10036,8 @@ class MergeRisk {
10020
10036
  }
10021
10037
  }
10022
10038
 
10039
+ /** Default interval for strategies that do not specify one */
10040
+ const STRATEGY_DEFAULT_INTERVAL = "1m";
10023
10041
  /**
10024
10042
  * If syncSubject listener or any registered action throws, it means the signal was not properly synchronized
10025
10043
  * to the exchange (e.g. limit order failed to fill).
@@ -10405,7 +10423,7 @@ class StrategyConnectionService {
10405
10423
  * @returns Configured ClientStrategy instance
10406
10424
  */
10407
10425
  this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10408
- const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
10426
+ const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10409
10427
  return new ClientStrategy({
10410
10428
  symbol,
10411
10429
  interval,
@@ -11245,6 +11263,48 @@ class StrategyConnectionService {
11245
11263
  const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11246
11264
  return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
11247
11265
  };
11266
+ /**
11267
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
11268
+ *
11269
+ * Resolves current price via priceMetaService and delegates to
11270
+ * ClientStrategy.getMaxDrawdownDistancePnlPercentage().
11271
+ * Returns null if no pending signal exists.
11272
+ *
11273
+ * @param backtest - Whether running in backtest mode
11274
+ * @param symbol - Trading pair symbol
11275
+ * @param context - Execution context with strategyName, exchangeName, frameName
11276
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
11277
+ */
11278
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
11279
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
11280
+ symbol,
11281
+ context,
11282
+ });
11283
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11284
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11285
+ return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
11286
+ };
11287
+ /**
11288
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
11289
+ *
11290
+ * Resolves current price via priceMetaService and delegates to
11291
+ * ClientStrategy.getMaxDrawdownDistancePnlCost().
11292
+ * Returns null if no pending signal exists.
11293
+ *
11294
+ * @param backtest - Whether running in backtest mode
11295
+ * @param symbol - Trading pair symbol
11296
+ * @param context - Execution context with strategyName, exchangeName, frameName
11297
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
11298
+ */
11299
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
11300
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
11301
+ symbol,
11302
+ context,
11303
+ });
11304
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11305
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11306
+ return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
11307
+ };
11248
11308
  /**
11249
11309
  * Disposes the ClientStrategy instance for the given context.
11250
11310
  *
@@ -11304,17 +11364,17 @@ class StrategyConnectionService {
11304
11364
  * @param backtest - Whether running in backtest mode
11305
11365
  * @param symbol - Trading pair symbol
11306
11366
  * @param ctx - Context with strategyName, exchangeName, frameName
11307
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
11367
+ * @param payload - Optional commit payload with id and note
11308
11368
  * @returns Promise that resolves when scheduled signal is cancelled
11309
11369
  */
11310
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
11370
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
11311
11371
  this.loggerService.log("strategyConnectionService cancelScheduled", {
11312
11372
  symbol,
11313
11373
  context,
11314
- cancelId,
11374
+ payload,
11315
11375
  });
11316
11376
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11317
- await strategy.cancelScheduled(symbol, backtest, cancelId);
11377
+ await strategy.cancelScheduled(symbol, backtest, payload);
11318
11378
  };
11319
11379
  /**
11320
11380
  * Closes the pending signal without stopping the strategy.
@@ -11329,17 +11389,17 @@ class StrategyConnectionService {
11329
11389
  * @param backtest - Whether running in backtest mode
11330
11390
  * @param symbol - Trading pair symbol
11331
11391
  * @param context - Context with strategyName, exchangeName, frameName
11332
- * @param closeId - Optional close ID for user-initiated closes
11392
+ * @param payload - Optional commit payload with id and note
11333
11393
  * @returns Promise that resolves when pending signal is closed
11334
11394
  */
11335
- this.closePending = async (backtest, symbol, context, closeId) => {
11395
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
11336
11396
  this.loggerService.log("strategyConnectionService closePending", {
11337
11397
  symbol,
11338
11398
  context,
11339
- closeId,
11399
+ payload,
11340
11400
  });
11341
11401
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11342
- await strategy.closePending(symbol, backtest, closeId);
11402
+ await strategy.closePending(symbol, backtest, payload);
11343
11403
  };
11344
11404
  /**
11345
11405
  * Checks whether `partialProfit` would succeed without executing it.
@@ -11618,7 +11678,7 @@ class StrategyConnectionService {
11618
11678
  * @param backtest - Whether running in backtest mode
11619
11679
  * @param symbol - Trading pair symbol
11620
11680
  * @param context - Execution context with strategyName, exchangeName, frameName
11621
- * @param activateId - Optional identifier for the activation reason
11681
+ * @param payload - Optional commit payload with id and note
11622
11682
  * @returns Promise that resolves when activation flag is set
11623
11683
  *
11624
11684
  * @example
@@ -11628,19 +11688,19 @@ class StrategyConnectionService {
11628
11688
  * false,
11629
11689
  * "BTCUSDT",
11630
11690
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
11631
- * "manual-activation"
11691
+ * { id: "manual-activation" }
11632
11692
  * );
11633
11693
  * ```
11634
11694
  */
11635
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
11695
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
11636
11696
  this.loggerService.log("strategyConnectionService activateScheduled", {
11637
11697
  symbol,
11638
11698
  context,
11639
11699
  backtest,
11640
- activateId,
11700
+ payload,
11641
11701
  });
11642
11702
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11643
- return await strategy.activateScheduled(symbol, backtest, activateId);
11703
+ return await strategy.activateScheduled(symbol, backtest, payload);
11644
11704
  };
11645
11705
  /**
11646
11706
  * Checks whether `averageBuy` would succeed without executing it.
@@ -14675,18 +14735,18 @@ class StrategyCoreService {
14675
14735
  * @param backtest - Whether running in backtest mode
14676
14736
  * @param symbol - Trading pair symbol
14677
14737
  * @param ctx - Context with strategyName, exchangeName, frameName
14678
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
14738
+ * @param payload - Optional commit payload with id and note
14679
14739
  * @returns Promise that resolves when scheduled signal is cancelled
14680
14740
  */
14681
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
14741
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
14682
14742
  this.loggerService.log("strategyCoreService cancelScheduled", {
14683
14743
  symbol,
14684
14744
  context,
14685
14745
  backtest,
14686
- cancelId,
14746
+ payload,
14687
14747
  });
14688
14748
  await this.validate(context);
14689
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
14749
+ return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
14690
14750
  };
14691
14751
  /**
14692
14752
  * Closes the pending signal without stopping the strategy.
@@ -14702,18 +14762,18 @@ class StrategyCoreService {
14702
14762
  * @param backtest - Whether running in backtest mode
14703
14763
  * @param symbol - Trading pair symbol
14704
14764
  * @param context - Context with strategyName, exchangeName, frameName
14705
- * @param closeId - Optional close ID for user-initiated closes
14765
+ * @param payload - Optional commit payload with id and note
14706
14766
  * @returns Promise that resolves when pending signal is closed
14707
14767
  */
14708
- this.closePending = async (backtest, symbol, context, closeId) => {
14768
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
14709
14769
  this.loggerService.log("strategyCoreService closePending", {
14710
14770
  symbol,
14711
14771
  context,
14712
14772
  backtest,
14713
- closeId,
14773
+ payload,
14714
14774
  });
14715
14775
  await this.validate(context);
14716
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
14776
+ return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
14717
14777
  };
14718
14778
  /**
14719
14779
  * Disposes the ClientStrategy instance for the given context.
@@ -15058,7 +15118,7 @@ class StrategyCoreService {
15058
15118
  * @param backtest - Whether running in backtest mode
15059
15119
  * @param symbol - Trading pair symbol
15060
15120
  * @param context - Execution context with strategyName, exchangeName, frameName
15061
- * @param activateId - Optional identifier for the activation reason
15121
+ * @param payload - Optional commit payload with id and note
15062
15122
  * @returns Promise that resolves when activation flag is set
15063
15123
  *
15064
15124
  * @example
@@ -15068,19 +15128,19 @@ class StrategyCoreService {
15068
15128
  * false,
15069
15129
  * "BTCUSDT",
15070
15130
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
15071
- * "manual-activation"
15131
+ * { id: "manual-activation" }
15072
15132
  * );
15073
15133
  * ```
15074
15134
  */
15075
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
15135
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
15076
15136
  this.loggerService.log("strategyCoreService activateScheduled", {
15077
15137
  symbol,
15078
15138
  context,
15079
15139
  backtest,
15080
- activateId,
15140
+ payload,
15081
15141
  });
15082
15142
  await this.validate(context);
15083
- return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
15143
+ return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
15084
15144
  };
15085
15145
  /**
15086
15146
  * Checks whether `averageBuy` would succeed without executing it.
@@ -15482,6 +15542,44 @@ class StrategyCoreService {
15482
15542
  await this.validate(context);
15483
15543
  return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
15484
15544
  };
15545
+ /**
15546
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
15547
+ *
15548
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
15549
+ * Returns null if no pending signal exists.
15550
+ *
15551
+ * @param backtest - Whether running in backtest mode
15552
+ * @param symbol - Trading pair symbol
15553
+ * @param context - Execution context with strategyName, exchangeName, frameName
15554
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
15555
+ */
15556
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
15557
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
15558
+ symbol,
15559
+ context,
15560
+ });
15561
+ await this.validate(context);
15562
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
15563
+ };
15564
+ /**
15565
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
15566
+ *
15567
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
15568
+ * Returns null if no pending signal exists.
15569
+ *
15570
+ * @param backtest - Whether running in backtest mode
15571
+ * @param symbol - Trading pair symbol
15572
+ * @param context - Execution context with strategyName, exchangeName, frameName
15573
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
15574
+ */
15575
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
15576
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
15577
+ symbol,
15578
+ context,
15579
+ });
15580
+ await this.validate(context);
15581
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
15582
+ };
15485
15583
  }
15486
15584
  }
15487
15585
 
@@ -16124,8 +16222,8 @@ class StrategySchemaService {
16124
16222
  if (strategySchema.actions?.some((value) => typeof value !== "string")) {
16125
16223
  throw new Error(`strategy schema validation failed: invalid actions for strategyName=${strategySchema.strategyName} actions=[${strategySchema.actions}]`);
16126
16224
  }
16127
- if (typeof strategySchema.interval !== "string") {
16128
- throw new Error(`strategy schema validation failed: missing interval for strategyName=${strategySchema.strategyName}`);
16225
+ if (strategySchema.interval && typeof strategySchema.interval !== "string") {
16226
+ throw new Error(`strategy schema validation failed: invalid interval for strategyName=${strategySchema.strategyName}`);
16129
16227
  }
16130
16228
  if (typeof strategySchema.getSignal !== "function") {
16131
16229
  throw new Error(`strategy schema validation failed: missing getSignal for strategyName=${strategySchema.strategyName}`);
@@ -18071,6 +18169,53 @@ class WalkerCommandService {
18071
18169
  }
18072
18170
  }
18073
18171
 
18172
+ /**
18173
+ * Converts markdown content to plain text with minimal formatting
18174
+ * @param content - Markdown string to convert
18175
+ * @returns Plain text representation
18176
+ */
18177
+ const toPlainString = (content) => {
18178
+ if (!content) {
18179
+ return "";
18180
+ }
18181
+ let text = content;
18182
+ // Remove code blocks
18183
+ text = text.replace(/```[\s\S]*?```/g, "");
18184
+ text = text.replace(/`([^`]+)`/g, "$1");
18185
+ // Remove images
18186
+ text = text.replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1");
18187
+ // Convert links to text only (keep link text, remove URL)
18188
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
18189
+ // Remove headers (convert to plain text)
18190
+ text = text.replace(/^#{1,6}\s+(.+)$/gm, "$1");
18191
+ // Remove bold and italic markers
18192
+ text = text.replace(/\*\*\*(.+?)\*\*\*/g, "$1");
18193
+ text = text.replace(/\*\*(.+?)\*\*/g, "$1");
18194
+ text = text.replace(/\*(.+?)\*/g, "$1");
18195
+ text = text.replace(/___(.+?)___/g, "$1");
18196
+ text = text.replace(/__(.+?)__/g, "$1");
18197
+ text = text.replace(/_(.+?)_/g, "$1");
18198
+ // Remove strikethrough
18199
+ text = text.replace(/~~(.+?)~~/g, "$1");
18200
+ // Convert lists to plain text with bullets
18201
+ text = text.replace(/^\s*[-*+]\s+/gm, "• ");
18202
+ text = text.replace(/^\s*\d+\.\s+/gm, "• ");
18203
+ // Remove blockquotes
18204
+ text = text.replace(/^\s*>\s+/gm, "");
18205
+ // Remove horizontal rules
18206
+ text = text.replace(/^(\*{3,}|-{3,}|_{3,})$/gm, "");
18207
+ // Remove HTML tags
18208
+ text = text.replace(/<[^>]+>/g, "");
18209
+ // Remove excessive whitespace and normalize line breaks
18210
+ text = text.replace(/\n[\s\n]*\n/g, "\n");
18211
+ text = text.replace(/[ \t]+/g, " ");
18212
+ // Remove all newline characters
18213
+ text = text.replace(/\n/g, " ");
18214
+ // Remove excessive spaces after newline removal
18215
+ text = text.replace(/\s+/g, " ");
18216
+ return text.trim();
18217
+ };
18218
+
18074
18219
  /**
18075
18220
  * Column configuration for backtest markdown reports.
18076
18221
  *
@@ -18746,7 +18891,7 @@ const partial_columns = [
18746
18891
  {
18747
18892
  key: "note",
18748
18893
  label: "Note",
18749
- format: (data) => data.note || "",
18894
+ format: (data) => toPlainString(data.note ?? "N/A"),
18750
18895
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18751
18896
  },
18752
18897
  {
@@ -18906,7 +19051,7 @@ const breakeven_columns = [
18906
19051
  {
18907
19052
  key: "note",
18908
19053
  label: "Note",
18909
- format: (data) => data.note || "",
19054
+ format: (data) => toPlainString(data.note ?? "N/A"),
18910
19055
  isVisible: () => GLOBAL_CONFIG.CC_REPORT_SHOW_SIGNAL_NOTE,
18911
19056
  },
18912
19057
  {
@@ -29034,7 +29179,7 @@ class StrategyReportService {
29034
29179
  /**
29035
29180
  * Logs a cancel-scheduled event when a scheduled signal is cancelled.
29036
29181
  */
29037
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
29182
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
29038
29183
  this.loggerService.log("strategyReportService cancelScheduled", {
29039
29184
  symbol,
29040
29185
  isBacktest,
@@ -29047,6 +29192,7 @@ class StrategyReportService {
29047
29192
  await ReportWriter.writeData("strategy", {
29048
29193
  action: "cancel-scheduled",
29049
29194
  cancelId,
29195
+ note,
29050
29196
  symbol,
29051
29197
  timestamp,
29052
29198
  createdAt,
@@ -29068,7 +29214,7 @@ class StrategyReportService {
29068
29214
  /**
29069
29215
  * Logs a close-pending event when a pending signal is closed.
29070
29216
  */
29071
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
29217
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
29072
29218
  this.loggerService.log("strategyReportService closePending", {
29073
29219
  symbol,
29074
29220
  isBacktest,
@@ -29081,6 +29227,7 @@ class StrategyReportService {
29081
29227
  await ReportWriter.writeData("strategy", {
29082
29228
  action: "close-pending",
29083
29229
  closeId,
29230
+ note,
29084
29231
  symbol,
29085
29232
  timestamp,
29086
29233
  createdAt,
@@ -29330,7 +29477,7 @@ class StrategyReportService {
29330
29477
  /**
29331
29478
  * Logs an activate-scheduled event when a scheduled signal is activated early.
29332
29479
  */
29333
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
29480
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
29334
29481
  this.loggerService.log("strategyReportService activateScheduled", {
29335
29482
  symbol,
29336
29483
  currentPrice,
@@ -29344,6 +29491,7 @@ class StrategyReportService {
29344
29491
  await ReportWriter.writeData("strategy", {
29345
29492
  action: "activate-scheduled",
29346
29493
  activateId,
29494
+ note,
29347
29495
  currentPrice,
29348
29496
  symbol,
29349
29497
  timestamp,
@@ -29437,14 +29585,14 @@ class StrategyReportService {
29437
29585
  exchangeName: event.exchangeName,
29438
29586
  frameName: event.frameName,
29439
29587
  strategyName: event.strategyName,
29440
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
29588
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
29441
29589
  const unClosePending = strategyCommitSubject
29442
29590
  .filter(({ action }) => action === "close-pending")
29443
29591
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
29444
29592
  exchangeName: event.exchangeName,
29445
29593
  frameName: event.frameName,
29446
29594
  strategyName: event.strategyName,
29447
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
29595
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
29448
29596
  const unPartialProfit = strategyCommitSubject
29449
29597
  .filter(({ action }) => action === "partial-profit")
29450
29598
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -29486,7 +29634,7 @@ class StrategyReportService {
29486
29634
  exchangeName: event.exchangeName,
29487
29635
  frameName: event.frameName,
29488
29636
  strategyName: event.strategyName,
29489
- }, 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));
29637
+ }, 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));
29490
29638
  const unAverageBuy = strategyCommitSubject
29491
29639
  .filter(({ action }) => action === "average-buy")
29492
29640
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -30025,8 +30173,9 @@ class StrategyMarkdownService {
30025
30173
  * @param context - Strategy context with strategyName, exchangeName, frameName
30026
30174
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30027
30175
  * @param cancelId - Optional identifier for the cancellation reason
30176
+ * @param note - Optional note from commit payload
30028
30177
  */
30029
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
30178
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
30030
30179
  this.loggerService.log("strategyMarkdownService cancelScheduled", {
30031
30180
  symbol,
30032
30181
  isBacktest,
@@ -30047,6 +30196,7 @@ class StrategyMarkdownService {
30047
30196
  action: "cancel-scheduled",
30048
30197
  pnl,
30049
30198
  cancelId,
30199
+ note,
30050
30200
  createdAt,
30051
30201
  backtest: isBacktest,
30052
30202
  });
@@ -30059,8 +30209,9 @@ class StrategyMarkdownService {
30059
30209
  * @param context - Strategy context with strategyName, exchangeName, frameName
30060
30210
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30061
30211
  * @param closeId - Optional identifier for the close reason
30212
+ * @param note - Optional note from commit payload
30062
30213
  */
30063
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
30214
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
30064
30215
  this.loggerService.log("strategyMarkdownService closePending", {
30065
30216
  symbol,
30066
30217
  isBacktest,
@@ -30081,6 +30232,7 @@ class StrategyMarkdownService {
30081
30232
  action: "close-pending",
30082
30233
  pnl,
30083
30234
  closeId,
30235
+ note,
30084
30236
  createdAt,
30085
30237
  backtest: isBacktest,
30086
30238
  });
@@ -30379,8 +30531,9 @@ class StrategyMarkdownService {
30379
30531
  * @param scheduledAt - Signal creation timestamp in milliseconds
30380
30532
  * @param pendingAt - Pending timestamp in milliseconds
30381
30533
  * @param activateId - Optional identifier for the activation reason
30534
+ * @param note - Optional note from commit payload
30382
30535
  */
30383
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
30536
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
30384
30537
  this.loggerService.log("strategyMarkdownService activateScheduled", {
30385
30538
  symbol,
30386
30539
  currentPrice,
@@ -30403,6 +30556,7 @@ class StrategyMarkdownService {
30403
30556
  pnl,
30404
30557
  totalPartials,
30405
30558
  activateId,
30559
+ note,
30406
30560
  currentPrice,
30407
30561
  createdAt,
30408
30562
  backtest: isBacktest,
@@ -30607,14 +30761,14 @@ class StrategyMarkdownService {
30607
30761
  exchangeName: event.exchangeName,
30608
30762
  frameName: event.frameName,
30609
30763
  strategyName: event.strategyName,
30610
- }, event.timestamp, event.signalId, event.pnl, event.cancelId));
30764
+ }, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
30611
30765
  const unClosePending = strategyCommitSubject
30612
30766
  .filter(({ action }) => action === "close-pending")
30613
30767
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
30614
30768
  exchangeName: event.exchangeName,
30615
30769
  frameName: event.frameName,
30616
30770
  strategyName: event.strategyName,
30617
- }, event.timestamp, event.signalId, event.pnl, event.closeId));
30771
+ }, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
30618
30772
  const unPartialProfit = strategyCommitSubject
30619
30773
  .filter(({ action }) => action === "partial-profit")
30620
30774
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -30656,7 +30810,7 @@ class StrategyMarkdownService {
30656
30810
  exchangeName: event.exchangeName,
30657
30811
  frameName: event.frameName,
30658
30812
  strategyName: event.strategyName,
30659
- }, 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));
30813
+ }, 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));
30660
30814
  const unAverageBuy = strategyCommitSubject
30661
30815
  .filter(({ action }) => action === "average-buy")
30662
30816
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -35141,6 +35295,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
35141
35295
  const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
35142
35296
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
35143
35297
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
35298
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
35299
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
35144
35300
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
35145
35301
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35146
35302
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
@@ -35156,7 +35312,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35156
35312
  *
35157
35313
  * @param symbol - Trading pair symbol
35158
35314
  * @param strategyName - Strategy name
35159
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
35315
+ * @param payload - Optional commit payload with id and note
35160
35316
  * @returns Promise that resolves when scheduled signal is cancelled
35161
35317
  *
35162
35318
  * @example
@@ -35164,13 +35320,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35164
35320
  * import { commitCancelScheduled } from "backtest-kit";
35165
35321
  *
35166
35322
  * // Cancel scheduled signal with custom ID
35167
- * await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
35323
+ * await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
35168
35324
  * ```
35169
35325
  */
35170
- async function commitCancelScheduled(symbol, cancelId) {
35326
+ async function commitCancelScheduled(symbol, payload = {}) {
35171
35327
  backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
35172
35328
  symbol,
35173
- cancelId,
35329
+ payload,
35174
35330
  });
35175
35331
  if (!ExecutionContextService.hasContext()) {
35176
35332
  throw new Error("commitCancelScheduled requires an execution context");
@@ -35180,7 +35336,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35180
35336
  }
35181
35337
  const { backtest: isBacktest } = backtest.executionContextService.context;
35182
35338
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35183
- await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, cancelId);
35339
+ await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35184
35340
  }
35185
35341
  /**
35186
35342
  * Closes the pending signal without stopping the strategy.
@@ -35192,7 +35348,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35192
35348
  * Automatically detects backtest/live mode from execution context.
35193
35349
  *
35194
35350
  * @param symbol - Trading pair symbol
35195
- * @param closeId - Optional close ID for tracking user-initiated closes
35351
+ * @param payload - Optional commit payload with id and note
35196
35352
  * @returns Promise that resolves when pending signal is closed
35197
35353
  *
35198
35354
  * @example
@@ -35200,13 +35356,13 @@ async function commitCancelScheduled(symbol, cancelId) {
35200
35356
  * import { commitClosePending } from "backtest-kit";
35201
35357
  *
35202
35358
  * // Close pending signal with custom ID
35203
- * await commitClosePending("BTCUSDT", "manual-close-001");
35359
+ * await commitClosePending("BTCUSDT", { id: "manual-close-001" });
35204
35360
  * ```
35205
35361
  */
35206
- async function commitClosePending(symbol, closeId) {
35362
+ async function commitClosePending(symbol, payload = {}) {
35207
35363
  backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
35208
35364
  symbol,
35209
- closeId,
35365
+ payload,
35210
35366
  });
35211
35367
  if (!ExecutionContextService.hasContext()) {
35212
35368
  throw new Error("commitClosePending requires an execution context");
@@ -35216,7 +35372,7 @@ async function commitClosePending(symbol, closeId) {
35216
35372
  }
35217
35373
  const { backtest: isBacktest } = backtest.executionContextService.context;
35218
35374
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35219
- await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, closeId);
35375
+ await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35220
35376
  }
35221
35377
  /**
35222
35378
  * Executes partial close at profit level (moving toward TP).
@@ -35679,7 +35835,7 @@ async function commitBreakeven(symbol) {
35679
35835
  * Automatically detects backtest/live mode from execution context.
35680
35836
  *
35681
35837
  * @param symbol - Trading pair symbol
35682
- * @param activateId - Optional activation ID for tracking user-initiated activations
35838
+ * @param payload - Optional commit payload with id and note
35683
35839
  * @returns Promise that resolves when activation flag is set
35684
35840
  *
35685
35841
  * @example
@@ -35687,13 +35843,13 @@ async function commitBreakeven(symbol) {
35687
35843
  * import { commitActivateScheduled } from "backtest-kit";
35688
35844
  *
35689
35845
  * // Activate scheduled signal early with custom ID
35690
- * await commitActivateScheduled("BTCUSDT", "manual-activate-001");
35846
+ * await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
35691
35847
  * ```
35692
35848
  */
35693
- async function commitActivateScheduled(symbol, activateId) {
35849
+ async function commitActivateScheduled(symbol, payload = {}) {
35694
35850
  backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
35695
35851
  symbol,
35696
- activateId,
35852
+ payload,
35697
35853
  });
35698
35854
  if (!ExecutionContextService.hasContext()) {
35699
35855
  throw new Error("commitActivateScheduled requires an execution context");
@@ -35703,7 +35859,7 @@ async function commitActivateScheduled(symbol, activateId) {
35703
35859
  }
35704
35860
  const { backtest: isBacktest } = backtest.executionContextService.context;
35705
35861
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35706
- await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
35862
+ await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35707
35863
  }
35708
35864
  /**
35709
35865
  * Adds a new DCA entry to the active pending signal.
@@ -36891,6 +37047,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
36891
37047
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36892
37048
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36893
37049
  }
37050
+ /**
37051
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
37052
+ *
37053
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
37054
+ * Returns null if no pending signal exists.
37055
+ *
37056
+ * @param symbol - Trading pair symbol
37057
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
37058
+ *
37059
+ * @example
37060
+ * ```typescript
37061
+ * import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
37062
+ *
37063
+ * const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
37064
+ * // e.g. 3.5 (peak was +3.5% above trough)
37065
+ * ```
37066
+ */
37067
+ async function getMaxDrawdownDistancePnlPercentage(symbol) {
37068
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
37069
+ if (!ExecutionContextService.hasContext()) {
37070
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
37071
+ }
37072
+ if (!MethodContextService.hasContext()) {
37073
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
37074
+ }
37075
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37076
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37077
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
37078
+ }
37079
+ /**
37080
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
37081
+ *
37082
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
37083
+ * Returns null if no pending signal exists.
37084
+ *
37085
+ * @param symbol - Trading pair symbol
37086
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
37087
+ *
37088
+ * @example
37089
+ * ```typescript
37090
+ * import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
37091
+ *
37092
+ * const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
37093
+ * // e.g. 7.2 (peak was $7.2 above trough)
37094
+ * ```
37095
+ */
37096
+ async function getMaxDrawdownDistancePnlCost(symbol) {
37097
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
37098
+ if (!ExecutionContextService.hasContext()) {
37099
+ throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
37100
+ }
37101
+ if (!MethodContextService.hasContext()) {
37102
+ throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
37103
+ }
37104
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37105
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37106
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
37107
+ }
36894
37108
  /**
36895
37109
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
36896
37110
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38564,6 +38778,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
38564
38778
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
38565
38779
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
38566
38780
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
38781
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
38782
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
38567
38783
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
38568
38784
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
38569
38785
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -39945,6 +40161,62 @@ class BacktestUtils {
39945
40161
  }
39946
40162
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
39947
40163
  };
40164
+ /**
40165
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
40166
+ *
40167
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
40168
+ * Returns null if no pending signal exists.
40169
+ *
40170
+ * @param symbol - Trading pair symbol
40171
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40172
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
40173
+ */
40174
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
40175
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
40176
+ symbol,
40177
+ context,
40178
+ });
40179
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40180
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40181
+ {
40182
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40183
+ riskName &&
40184
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40185
+ riskList &&
40186
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40187
+ actions &&
40188
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40189
+ }
40190
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
40191
+ };
40192
+ /**
40193
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
40194
+ *
40195
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
40196
+ * Returns null if no pending signal exists.
40197
+ *
40198
+ * @param symbol - Trading pair symbol
40199
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40200
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
40201
+ */
40202
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
40203
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
40204
+ symbol,
40205
+ context,
40206
+ });
40207
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40208
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40209
+ {
40210
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40211
+ riskName &&
40212
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40213
+ riskList &&
40214
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40215
+ actions &&
40216
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40217
+ }
40218
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
40219
+ };
39948
40220
  /**
39949
40221
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
39950
40222
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -40079,7 +40351,7 @@ class BacktestUtils {
40079
40351
  * @param symbol - Trading pair symbol
40080
40352
  * @param strategyName - Strategy name
40081
40353
  * @param context - Execution context with exchangeName and frameName
40082
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
40354
+ * @param payload - Optional commit payload with id and note
40083
40355
  * @returns Promise that resolves when scheduled signal is cancelled
40084
40356
  *
40085
40357
  * @example
@@ -40089,14 +40361,14 @@ class BacktestUtils {
40089
40361
  * exchangeName: "binance",
40090
40362
  * frameName: "frame1",
40091
40363
  * strategyName: "my-strategy"
40092
- * }, "manual-cancel-001");
40364
+ * }, { id: "manual-cancel-001" });
40093
40365
  * ```
40094
40366
  */
40095
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
40367
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
40096
40368
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
40097
40369
  symbol,
40098
40370
  context,
40099
- cancelId,
40371
+ payload,
40100
40372
  });
40101
40373
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
40102
40374
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
@@ -40109,7 +40381,7 @@ class BacktestUtils {
40109
40381
  actions &&
40110
40382
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
40111
40383
  }
40112
- await backtest.strategyCoreService.cancelScheduled(true, symbol, context, cancelId);
40384
+ await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
40113
40385
  };
40114
40386
  /**
40115
40387
  * Closes the pending signal without stopping the strategy.
@@ -40120,7 +40392,7 @@ class BacktestUtils {
40120
40392
  *
40121
40393
  * @param symbol - Trading pair symbol
40122
40394
  * @param context - Execution context with strategyName, exchangeName, and frameName
40123
- * @param closeId - Optional close ID for user-initiated closes
40395
+ * @param payload - Optional commit payload with id and note
40124
40396
  * @returns Promise that resolves when pending signal is closed
40125
40397
  *
40126
40398
  * @example
@@ -40130,14 +40402,14 @@ class BacktestUtils {
40130
40402
  * exchangeName: "binance",
40131
40403
  * strategyName: "my-strategy",
40132
40404
  * frameName: "1m"
40133
- * }, "manual-close-001");
40405
+ * }, { id: "manual-close-001" });
40134
40406
  * ```
40135
40407
  */
40136
- this.commitClosePending = async (symbol, context, closeId) => {
40408
+ this.commitClosePending = async (symbol, context, payload = {}) => {
40137
40409
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
40138
40410
  symbol,
40139
40411
  context,
40140
- closeId,
40412
+ payload,
40141
40413
  });
40142
40414
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
40143
40415
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
@@ -40150,7 +40422,7 @@ class BacktestUtils {
40150
40422
  actions &&
40151
40423
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
40152
40424
  }
40153
- await backtest.strategyCoreService.closePending(true, symbol, context, closeId);
40425
+ await backtest.strategyCoreService.closePending(true, symbol, context, payload);
40154
40426
  };
40155
40427
  /**
40156
40428
  * Executes partial close at profit level (moving toward TP).
@@ -40786,7 +41058,7 @@ class BacktestUtils {
40786
41058
  *
40787
41059
  * @param symbol - Trading pair symbol
40788
41060
  * @param context - Execution context with strategyName, exchangeName, and frameName
40789
- * @param activateId - Optional activation ID for tracking user-initiated activations
41061
+ * @param payload - Optional commit payload with id and note
40790
41062
  * @returns Promise that resolves when activation flag is set
40791
41063
  *
40792
41064
  * @example
@@ -40796,14 +41068,14 @@ class BacktestUtils {
40796
41068
  * strategyName: "my-strategy",
40797
41069
  * exchangeName: "binance",
40798
41070
  * frameName: "1h"
40799
- * }, "manual-activate-001");
41071
+ * }, { id: "manual-activate-001" });
40800
41072
  * ```
40801
41073
  */
40802
- this.commitActivateScheduled = async (symbol, context, activateId) => {
41074
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
40803
41075
  backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
40804
41076
  symbol,
40805
41077
  context,
40806
- activateId,
41078
+ payload,
40807
41079
  });
40808
41080
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
40809
41081
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -40816,7 +41088,7 @@ class BacktestUtils {
40816
41088
  actions &&
40817
41089
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
40818
41090
  }
40819
- await backtest.strategyCoreService.activateScheduled(true, symbol, context, activateId);
41091
+ await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
40820
41092
  };
40821
41093
  /**
40822
41094
  * Adds a new DCA entry to the active pending signal.
@@ -41074,6 +41346,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
41074
41346
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
41075
41347
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
41076
41348
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
41349
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
41350
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
41077
41351
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
41078
41352
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
41079
41353
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -42598,6 +42872,70 @@ class LiveUtils {
42598
42872
  frameName: "",
42599
42873
  });
42600
42874
  };
42875
+ /**
42876
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
42877
+ *
42878
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
42879
+ * Returns null if no pending signal exists.
42880
+ *
42881
+ * @param symbol - Trading pair symbol
42882
+ * @param context - Execution context with strategyName and exchangeName
42883
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
42884
+ */
42885
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
42886
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
42887
+ symbol,
42888
+ context,
42889
+ });
42890
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42891
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42892
+ {
42893
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42894
+ riskName &&
42895
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
42896
+ riskList &&
42897
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
42898
+ actions &&
42899
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
42900
+ }
42901
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
42902
+ strategyName: context.strategyName,
42903
+ exchangeName: context.exchangeName,
42904
+ frameName: "",
42905
+ });
42906
+ };
42907
+ /**
42908
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
42909
+ *
42910
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
42911
+ * Returns null if no pending signal exists.
42912
+ *
42913
+ * @param symbol - Trading pair symbol
42914
+ * @param context - Execution context with strategyName and exchangeName
42915
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
42916
+ */
42917
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
42918
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
42919
+ symbol,
42920
+ context,
42921
+ });
42922
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42923
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42924
+ {
42925
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
42926
+ riskName &&
42927
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
42928
+ riskList &&
42929
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
42930
+ actions &&
42931
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
42932
+ }
42933
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
42934
+ strategyName: context.strategyName,
42935
+ exchangeName: context.exchangeName,
42936
+ frameName: "",
42937
+ });
42938
+ };
42601
42939
  /**
42602
42940
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
42603
42941
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -42739,7 +43077,7 @@ class LiveUtils {
42739
43077
  * @param symbol - Trading pair symbol
42740
43078
  * @param strategyName - Strategy name
42741
43079
  * @param context - Execution context with exchangeName and frameName
42742
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
43080
+ * @param payload - Optional commit payload with id and note
42743
43081
  * @returns Promise that resolves when scheduled signal is cancelled
42744
43082
  *
42745
43083
  * @example
@@ -42749,14 +43087,14 @@ class LiveUtils {
42749
43087
  * exchangeName: "binance",
42750
43088
  * frameName: "",
42751
43089
  * strategyName: "my-strategy"
42752
- * }, "manual-cancel-001");
43090
+ * }, { id: "manual-cancel-001" });
42753
43091
  * ```
42754
43092
  */
42755
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
43093
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
42756
43094
  backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
42757
43095
  symbol,
42758
43096
  context,
42759
- cancelId,
43097
+ payload,
42760
43098
  });
42761
43099
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
42762
43100
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
@@ -42773,7 +43111,7 @@ class LiveUtils {
42773
43111
  strategyName: context.strategyName,
42774
43112
  exchangeName: context.exchangeName,
42775
43113
  frameName: "",
42776
- }, cancelId);
43114
+ }, payload);
42777
43115
  };
42778
43116
  /**
42779
43117
  * Closes the pending signal without stopping the strategy.
@@ -42784,7 +43122,7 @@ class LiveUtils {
42784
43122
  *
42785
43123
  * @param symbol - Trading pair symbol
42786
43124
  * @param context - Execution context with strategyName and exchangeName
42787
- * @param closeId - Optional close ID for user-initiated closes
43125
+ * @param payload - Optional commit payload with id and note
42788
43126
  * @returns Promise that resolves when pending signal is closed
42789
43127
  *
42790
43128
  * @example
@@ -42793,14 +43131,14 @@ class LiveUtils {
42793
43131
  * await Live.commitClose("BTCUSDT", {
42794
43132
  * exchangeName: "binance",
42795
43133
  * strategyName: "my-strategy"
42796
- * }, "manual-close-001");
43134
+ * }, { id: "manual-close-001" });
42797
43135
  * ```
42798
43136
  */
42799
- this.commitClosePending = async (symbol, context, closeId) => {
43137
+ this.commitClosePending = async (symbol, context, payload = {}) => {
42800
43138
  backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
42801
43139
  symbol,
42802
43140
  context,
42803
- closeId,
43141
+ payload,
42804
43142
  });
42805
43143
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
42806
43144
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
@@ -42817,7 +43155,7 @@ class LiveUtils {
42817
43155
  strategyName: context.strategyName,
42818
43156
  exchangeName: context.exchangeName,
42819
43157
  frameName: "",
42820
- }, closeId);
43158
+ }, payload);
42821
43159
  };
42822
43160
  /**
42823
43161
  * Executes partial close at profit level (moving toward TP).
@@ -43611,7 +43949,7 @@ class LiveUtils {
43611
43949
  *
43612
43950
  * @param symbol - Trading pair symbol
43613
43951
  * @param context - Execution context with strategyName and exchangeName
43614
- * @param activateId - Optional activation ID for tracking user-initiated activations
43952
+ * @param payload - Optional commit payload with id and note
43615
43953
  * @returns Promise that resolves when activation flag is set
43616
43954
  *
43617
43955
  * @example
@@ -43620,14 +43958,14 @@ class LiveUtils {
43620
43958
  * await Live.commitActivateScheduled("BTCUSDT", {
43621
43959
  * strategyName: "my-strategy",
43622
43960
  * exchangeName: "binance"
43623
- * }, "manual-activate-001");
43961
+ * }, { id: "manual-activate-001" });
43624
43962
  * ```
43625
43963
  */
43626
- this.commitActivateScheduled = async (symbol, context, activateId) => {
43964
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
43627
43965
  backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
43628
43966
  symbol,
43629
43967
  context,
43630
- activateId,
43968
+ payload,
43631
43969
  });
43632
43970
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
43633
43971
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -43644,7 +43982,7 @@ class LiveUtils {
43644
43982
  strategyName: context.strategyName,
43645
43983
  exchangeName: context.exchangeName,
43646
43984
  frameName: "",
43647
- }, activateId);
43985
+ }, payload);
43648
43986
  };
43649
43987
  /**
43650
43988
  * Adds a new DCA entry to the active pending signal.
@@ -49715,6 +50053,741 @@ class MaxDrawdownUtils {
49715
50053
  */
49716
50054
  const MaxDrawdown = new MaxDrawdownUtils();
49717
50055
 
50056
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
50057
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
50058
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
50059
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
50060
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
50061
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
50062
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
50063
+ const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
50064
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
50065
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
50066
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
50067
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
50068
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
50069
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
50070
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
50071
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
50072
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
50073
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
50074
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
50075
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
50076
+ /**
50077
+ * Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
50078
+ *
50079
+ * Provides unified access to strategyCoreService position state methods with logging
50080
+ * and full validation (strategy, exchange, frame, risk, actions).
50081
+ * Works for both live and backtest modes via the `backtest` parameter.
50082
+ * Exported as singleton instance for convenient usage.
50083
+ *
50084
+ * @example
50085
+ * ```typescript
50086
+ * import { Reflect } from "backtest-kit";
50087
+ *
50088
+ * // Get current unrealized PNL percentage
50089
+ * const pnl = await Reflect.getPositionPnlPercent(
50090
+ * "BTCUSDT",
50091
+ * 45000,
50092
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50093
+ * );
50094
+ * console.log(`PNL: ${pnl}%`);
50095
+ *
50096
+ * // Get peak profit reached
50097
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50098
+ * "BTCUSDT",
50099
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50100
+ * );
50101
+ * console.log(`Peak PNL: ${peakPnl}%`);
50102
+ * ```
50103
+ */
50104
+ class ReflectUtils {
50105
+ constructor() {
50106
+ /**
50107
+ * Returns the unrealized PNL percentage for the current pending signal at currentPrice.
50108
+ *
50109
+ * Accounts for partial closes, DCA entries, slippage and fees.
50110
+ * Returns null if no pending signal exists.
50111
+ *
50112
+ * @param symbol - Trading pair symbol
50113
+ * @param currentPrice - Current market price
50114
+ * @param context - Execution context with strategyName, exchangeName and frameName
50115
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50116
+ * @returns Promise resolving to PNL percentage or null
50117
+ *
50118
+ * @example
50119
+ * ```typescript
50120
+ * const pnl = await Reflect.getPositionPnlPercent(
50121
+ * "BTCUSDT",
50122
+ * 45000,
50123
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50124
+ * );
50125
+ * console.log(`PNL: ${pnl}%`);
50126
+ * ```
50127
+ */
50128
+ this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
50129
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
50130
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50131
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50132
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50133
+ {
50134
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50135
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50136
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50137
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50138
+ }
50139
+ return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
50140
+ };
50141
+ /**
50142
+ * Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
50143
+ *
50144
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost.
50145
+ * Accounts for partial closes, DCA entries, slippage and fees.
50146
+ * Returns null if no pending signal exists.
50147
+ *
50148
+ * @param symbol - Trading pair symbol
50149
+ * @param currentPrice - Current market price
50150
+ * @param context - Execution context with strategyName, exchangeName and frameName
50151
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50152
+ * @returns Promise resolving to PNL in dollars or null
50153
+ *
50154
+ * @example
50155
+ * ```typescript
50156
+ * const pnlCost = await Reflect.getPositionPnlCost(
50157
+ * "BTCUSDT",
50158
+ * 45000,
50159
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50160
+ * );
50161
+ * console.log(`PNL: $${pnlCost}`);
50162
+ * ```
50163
+ */
50164
+ this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
50165
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
50166
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50167
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50168
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50169
+ {
50170
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50171
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50172
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50173
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50174
+ }
50175
+ return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
50176
+ };
50177
+ /**
50178
+ * Returns the best price reached in the profit direction during this position's life.
50179
+ *
50180
+ * Returns null if no pending signal exists.
50181
+ *
50182
+ * @param symbol - Trading pair symbol
50183
+ * @param context - Execution context with strategyName, exchangeName and frameName
50184
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50185
+ * @returns Promise resolving to price or null
50186
+ *
50187
+ * @example
50188
+ * ```typescript
50189
+ * const peakPrice = await Reflect.getPositionHighestProfitPrice(
50190
+ * "BTCUSDT",
50191
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50192
+ * );
50193
+ * console.log(`Peak price: ${peakPrice}`);
50194
+ * ```
50195
+ */
50196
+ this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
50197
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
50198
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50199
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50200
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50201
+ {
50202
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50203
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50204
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50205
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50206
+ }
50207
+ return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
50208
+ };
50209
+ /**
50210
+ * Returns the timestamp when the best profit price was recorded during this position's life.
50211
+ *
50212
+ * Returns null if no pending signal exists.
50213
+ *
50214
+ * @param symbol - Trading pair symbol
50215
+ * @param context - Execution context with strategyName, exchangeName and frameName
50216
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50217
+ * @returns Promise resolving to timestamp in milliseconds or null
50218
+ *
50219
+ * @example
50220
+ * ```typescript
50221
+ * const ts = await Reflect.getPositionHighestProfitTimestamp(
50222
+ * "BTCUSDT",
50223
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50224
+ * );
50225
+ * console.log(`Peak at: ${new Date(ts).toISOString()}`);
50226
+ * ```
50227
+ */
50228
+ this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
50229
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
50230
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50231
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50232
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50233
+ {
50234
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50235
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50236
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50237
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50238
+ }
50239
+ return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
50240
+ };
50241
+ /**
50242
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
50243
+ *
50244
+ * Returns null if no pending signal exists.
50245
+ *
50246
+ * @param symbol - Trading pair symbol
50247
+ * @param context - Execution context with strategyName, exchangeName and frameName
50248
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50249
+ * @returns Promise resolving to PnL percentage or null
50250
+ *
50251
+ * @example
50252
+ * ```typescript
50253
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50254
+ * "BTCUSDT",
50255
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50256
+ * );
50257
+ * console.log(`Peak PNL: ${peakPnl}%`);
50258
+ * ```
50259
+ */
50260
+ this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
50261
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
50262
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50263
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50264
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50265
+ {
50266
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50267
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50268
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50269
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50270
+ }
50271
+ return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
50272
+ };
50273
+ /**
50274
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
50275
+ *
50276
+ * Returns null if no pending signal exists.
50277
+ *
50278
+ * @param symbol - Trading pair symbol
50279
+ * @param context - Execution context with strategyName, exchangeName and frameName
50280
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50281
+ * @returns Promise resolving to PnL cost in quote currency or null
50282
+ *
50283
+ * @example
50284
+ * ```typescript
50285
+ * const peakCost = await Reflect.getPositionHighestPnlCost(
50286
+ * "BTCUSDT",
50287
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50288
+ * );
50289
+ * console.log(`Peak PNL: $${peakCost}`);
50290
+ * ```
50291
+ */
50292
+ this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
50293
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
50294
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50295
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50296
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50297
+ {
50298
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50299
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50300
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50301
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50302
+ }
50303
+ return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
50304
+ };
50305
+ /**
50306
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
50307
+ *
50308
+ * Returns null if no pending signal exists.
50309
+ *
50310
+ * @param symbol - Trading pair symbol
50311
+ * @param context - Execution context with strategyName, exchangeName and frameName
50312
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50313
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
50314
+ *
50315
+ * @example
50316
+ * ```typescript
50317
+ * const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
50318
+ * "BTCUSDT",
50319
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50320
+ * );
50321
+ * console.log(`Breakeven reachable at peak: ${wasReachable}`);
50322
+ * ```
50323
+ */
50324
+ this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
50325
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
50326
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50327
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50328
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50329
+ {
50330
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50331
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
50332
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
50333
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
50334
+ }
50335
+ return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
50336
+ };
50337
+ /**
50338
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
50339
+ *
50340
+ * Returns null if no pending signal exists.
50341
+ *
50342
+ * @param symbol - Trading pair symbol
50343
+ * @param context - Execution context with strategyName, exchangeName and frameName
50344
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50345
+ * @returns Promise resolving to minutes since highest profit price was recorded, or null
50346
+ *
50347
+ * @example
50348
+ * ```typescript
50349
+ * const minutes = await Reflect.getPositionDrawdownMinutes(
50350
+ * "BTCUSDT",
50351
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50352
+ * );
50353
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
50354
+ * ```
50355
+ */
50356
+ this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
50357
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
50358
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50359
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50360
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50361
+ {
50362
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50363
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
50364
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
50365
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
50366
+ }
50367
+ return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
50368
+ };
50369
+ /**
50370
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
50371
+ *
50372
+ * Alias for getPositionDrawdownMinutes — measures how long the position has been
50373
+ * pulling back from its peak profit level.
50374
+ * Returns null if no pending signal exists.
50375
+ *
50376
+ * @param symbol - Trading pair symbol
50377
+ * @param context - Execution context with strategyName, exchangeName and frameName
50378
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50379
+ * @returns Promise resolving to minutes since last profit peak or null
50380
+ *
50381
+ * @example
50382
+ * ```typescript
50383
+ * const minutes = await Reflect.getPositionHighestProfitMinutes(
50384
+ * "BTCUSDT",
50385
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50386
+ * );
50387
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
50388
+ * ```
50389
+ */
50390
+ this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
50391
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
50392
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50393
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50394
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50395
+ {
50396
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50397
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
50398
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
50399
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
50400
+ }
50401
+ return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
50402
+ };
50403
+ /**
50404
+ * Returns the number of minutes elapsed since the worst loss price was recorded.
50405
+ *
50406
+ * Measures how long ago the deepest drawdown point occurred.
50407
+ * Zero when called at the exact moment the trough was set.
50408
+ * Returns null if no pending signal exists.
50409
+ *
50410
+ * @param symbol - Trading pair symbol
50411
+ * @param context - Execution context with strategyName, exchangeName and frameName
50412
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50413
+ * @returns Promise resolving to minutes since last drawdown trough or null
50414
+ *
50415
+ * @example
50416
+ * ```typescript
50417
+ * const minutes = await Reflect.getPositionMaxDrawdownMinutes(
50418
+ * "BTCUSDT",
50419
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50420
+ * );
50421
+ * console.log(`Drawdown trough was ${minutes} minutes ago`);
50422
+ * ```
50423
+ */
50424
+ this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
50425
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
50426
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50427
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50428
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50429
+ {
50430
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50431
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
50432
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
50433
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
50434
+ }
50435
+ return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
50436
+ };
50437
+ /**
50438
+ * Returns the worst price reached in the loss direction during this position's life.
50439
+ *
50440
+ * Returns null if no pending signal exists.
50441
+ *
50442
+ * @param symbol - Trading pair symbol
50443
+ * @param context - Execution context with strategyName, exchangeName and frameName
50444
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50445
+ * @returns Promise resolving to price or null
50446
+ *
50447
+ * @example
50448
+ * ```typescript
50449
+ * const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
50450
+ * "BTCUSDT",
50451
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50452
+ * );
50453
+ * console.log(`Worst price: ${troughPrice}`);
50454
+ * ```
50455
+ */
50456
+ this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
50457
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
50458
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50459
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50460
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50461
+ {
50462
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50463
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
50464
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
50465
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
50466
+ }
50467
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
50468
+ };
50469
+ /**
50470
+ * Returns the timestamp when the worst loss price was recorded during this position's life.
50471
+ *
50472
+ * Returns null if no pending signal exists.
50473
+ *
50474
+ * @param symbol - Trading pair symbol
50475
+ * @param context - Execution context with strategyName, exchangeName and frameName
50476
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50477
+ * @returns Promise resolving to timestamp in milliseconds or null
50478
+ *
50479
+ * @example
50480
+ * ```typescript
50481
+ * const ts = await Reflect.getPositionMaxDrawdownTimestamp(
50482
+ * "BTCUSDT",
50483
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50484
+ * );
50485
+ * console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
50486
+ * ```
50487
+ */
50488
+ this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
50489
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
50490
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50491
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50492
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50493
+ {
50494
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50495
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
50496
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
50497
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
50498
+ }
50499
+ return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
50500
+ };
50501
+ /**
50502
+ * Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
50503
+ *
50504
+ * Returns null if no pending signal exists.
50505
+ *
50506
+ * @param symbol - Trading pair symbol
50507
+ * @param context - Execution context with strategyName, exchangeName and frameName
50508
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50509
+ * @returns Promise resolving to PnL percentage or null
50510
+ *
50511
+ * @example
50512
+ * ```typescript
50513
+ * const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
50514
+ * "BTCUSDT",
50515
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50516
+ * );
50517
+ * console.log(`Worst PNL: ${worstPnl}%`);
50518
+ * ```
50519
+ */
50520
+ this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
50521
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
50522
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50523
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50524
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50525
+ {
50526
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50527
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
50528
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
50529
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
50530
+ }
50531
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
50532
+ };
50533
+ /**
50534
+ * Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
50535
+ *
50536
+ * Returns null if no pending signal exists.
50537
+ *
50538
+ * @param symbol - Trading pair symbol
50539
+ * @param context - Execution context with strategyName, exchangeName and frameName
50540
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50541
+ * @returns Promise resolving to PnL cost in quote currency or null
50542
+ *
50543
+ * @example
50544
+ * ```typescript
50545
+ * const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
50546
+ * "BTCUSDT",
50547
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50548
+ * );
50549
+ * console.log(`Worst PNL: $${worstCost}`);
50550
+ * ```
50551
+ */
50552
+ this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
50553
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
50554
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50555
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50556
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50557
+ {
50558
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50559
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
50560
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
50561
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
50562
+ }
50563
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
50564
+ };
50565
+ /**
50566
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
50567
+ *
50568
+ * Result is ≥ 0. Returns null if no pending signal exists.
50569
+ *
50570
+ * @param symbol - Trading pair symbol
50571
+ * @param context - Execution context with strategyName, exchangeName and frameName
50572
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50573
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
50574
+ *
50575
+ * @example
50576
+ * ```typescript
50577
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
50578
+ * "BTCUSDT",
50579
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50580
+ * );
50581
+ * console.log(`Dropped ${distance}% from peak`);
50582
+ * ```
50583
+ */
50584
+ this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
50585
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
50586
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50587
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50588
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50589
+ {
50590
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50591
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
50592
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
50593
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
50594
+ }
50595
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
50596
+ };
50597
+ /**
50598
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
50599
+ *
50600
+ * Result is ≥ 0. Returns null if no pending signal exists.
50601
+ *
50602
+ * @param symbol - Trading pair symbol
50603
+ * @param context - Execution context with strategyName, exchangeName and frameName
50604
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50605
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
50606
+ *
50607
+ * @example
50608
+ * ```typescript
50609
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
50610
+ * "BTCUSDT",
50611
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50612
+ * );
50613
+ * console.log(`Dropped $${distance} from peak`);
50614
+ * ```
50615
+ */
50616
+ this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
50617
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
50618
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50619
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50620
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50621
+ {
50622
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50623
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
50624
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
50625
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
50626
+ }
50627
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
50628
+ };
50629
+ /**
50630
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
50631
+ *
50632
+ * Result is ≥ 0. Returns null if no pending signal exists.
50633
+ *
50634
+ * @param symbol - Trading pair symbol
50635
+ * @param context - Execution context with strategyName, exchangeName and frameName
50636
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50637
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
50638
+ *
50639
+ * @example
50640
+ * ```typescript
50641
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
50642
+ * "BTCUSDT",
50643
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50644
+ * );
50645
+ * console.log(`${distance}% above worst trough`);
50646
+ * ```
50647
+ */
50648
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
50649
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
50650
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50651
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50652
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50653
+ {
50654
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50655
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
50656
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
50657
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
50658
+ }
50659
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
50660
+ };
50661
+ /**
50662
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
50663
+ *
50664
+ * Result is ≥ 0. Returns null if no pending signal exists.
50665
+ *
50666
+ * @param symbol - Trading pair symbol
50667
+ * @param context - Execution context with strategyName, exchangeName and frameName
50668
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50669
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
50670
+ *
50671
+ * @example
50672
+ * ```typescript
50673
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
50674
+ * "BTCUSDT",
50675
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50676
+ * );
50677
+ * console.log(`$${distance} above worst trough`);
50678
+ * ```
50679
+ */
50680
+ this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
50681
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
50682
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50683
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50684
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50685
+ {
50686
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50687
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
50688
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
50689
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
50690
+ }
50691
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
50692
+ };
50693
+ /**
50694
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
50695
+ *
50696
+ * Result is ≥ 0. Returns null if no pending signal exists.
50697
+ *
50698
+ * @param symbol - Trading pair symbol
50699
+ * @param context - Execution context with strategyName, exchangeName and frameName
50700
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50701
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
50702
+ *
50703
+ * @example
50704
+ * ```typescript
50705
+ * const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
50706
+ * "BTCUSDT",
50707
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50708
+ * );
50709
+ * console.log(`Peak-to-trough: ${distance}%`);
50710
+ * ```
50711
+ */
50712
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
50713
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
50714
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50715
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50716
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50717
+ {
50718
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50719
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
50720
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
50721
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
50722
+ }
50723
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
50724
+ };
50725
+ /**
50726
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
50727
+ *
50728
+ * Result is ≥ 0. Returns null if no pending signal exists.
50729
+ *
50730
+ * @param symbol - Trading pair symbol
50731
+ * @param context - Execution context with strategyName, exchangeName and frameName
50732
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50733
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
50734
+ *
50735
+ * @example
50736
+ * ```typescript
50737
+ * const distance = await Reflect.getMaxDrawdownDistancePnlCost(
50738
+ * "BTCUSDT",
50739
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50740
+ * );
50741
+ * console.log(`Peak-to-trough: $${distance}`);
50742
+ * ```
50743
+ */
50744
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
50745
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
50746
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50747
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50748
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50749
+ {
50750
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50751
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
50752
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
50753
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
50754
+ }
50755
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
50756
+ };
50757
+ }
50758
+ }
50759
+ /**
50760
+ * Singleton instance of ReflectUtils for convenient position state queries.
50761
+ *
50762
+ * @example
50763
+ * ```typescript
50764
+ * import { Reflect } from "backtest-kit";
50765
+ *
50766
+ * // Real-time PNL
50767
+ * const pnl = await Reflect.getPositionPnlPercent(
50768
+ * "BTCUSDT",
50769
+ * 45000,
50770
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50771
+ * );
50772
+ * console.log(`PNL: ${pnl}%`);
50773
+ *
50774
+ * // Peak profit
50775
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50776
+ * "BTCUSDT",
50777
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50778
+ * );
50779
+ * console.log(`Peak PNL: ${peakPnl}%`);
50780
+ *
50781
+ * // Drawdown from peak
50782
+ * const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
50783
+ * "BTCUSDT",
50784
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50785
+ * );
50786
+ * console.log(`Dropped ${drawdown}% from peak`);
50787
+ * ```
50788
+ */
50789
+ const Reflect$1 = new ReflectUtils();
50790
+
49718
50791
  /**
49719
50792
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
49720
50793
  *
@@ -51645,6 +52718,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51645
52718
  pnlEntries: data.signal.pnl.pnlEntries,
51646
52719
  scheduledAt: data.signal.scheduledAt,
51647
52720
  currentPrice: data.currentPrice,
52721
+ note: data.signal.note,
51648
52722
  createdAt: data.createdAt,
51649
52723
  };
51650
52724
  }
@@ -51674,6 +52748,7 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
51674
52748
  duration: durationMin,
51675
52749
  scheduledAt: data.signal.scheduledAt,
51676
52750
  pendingAt: data.signal.pendingAt,
52751
+ note: data.signal.note,
51677
52752
  createdAt: data.createdAt,
51678
52753
  };
51679
52754
  }
@@ -51710,6 +52785,7 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
51710
52785
  pnlPriceClose: data.data.pnl.priceClose,
51711
52786
  pnlCost: data.data.pnl.pnlCost,
51712
52787
  pnlEntries: data.data.pnl.pnlEntries,
52788
+ note: data.data.note,
51713
52789
  scheduledAt: data.data.scheduledAt,
51714
52790
  pendingAt: data.data.pendingAt,
51715
52791
  createdAt: data.timestamp,
@@ -51745,6 +52821,7 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
51745
52821
  pnlPriceClose: data.data.pnl.priceClose,
51746
52822
  pnlCost: data.data.pnl.pnlCost,
51747
52823
  pnlEntries: data.data.pnl.pnlEntries,
52824
+ note: data.data.note,
51748
52825
  scheduledAt: data.data.scheduledAt,
51749
52826
  pendingAt: data.data.pendingAt,
51750
52827
  createdAt: data.timestamp,
@@ -51779,6 +52856,7 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
51779
52856
  pnlPriceClose: data.data.pnl.priceClose,
51780
52857
  pnlCost: data.data.pnl.pnlCost,
51781
52858
  pnlEntries: data.data.pnl.pnlEntries,
52859
+ note: data.data.note,
51782
52860
  scheduledAt: data.data.scheduledAt,
51783
52861
  pendingAt: data.data.pendingAt,
51784
52862
  createdAt: data.timestamp,
@@ -51820,6 +52898,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51820
52898
  pnlEntries: data.pnl.pnlEntries,
51821
52899
  scheduledAt: data.scheduledAt,
51822
52900
  pendingAt: data.pendingAt,
52901
+ note: data.note,
51823
52902
  createdAt: data.timestamp,
51824
52903
  };
51825
52904
  }
@@ -51852,6 +52931,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51852
52931
  pnlEntries: data.pnl.pnlEntries,
51853
52932
  scheduledAt: data.scheduledAt,
51854
52933
  pendingAt: data.pendingAt,
52934
+ note: data.note,
51855
52935
  createdAt: data.timestamp,
51856
52936
  };
51857
52937
  }
@@ -51883,6 +52963,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51883
52963
  pnlEntries: data.pnl.pnlEntries,
51884
52964
  scheduledAt: data.scheduledAt,
51885
52965
  pendingAt: data.pendingAt,
52966
+ note: data.note,
51886
52967
  createdAt: data.timestamp,
51887
52968
  };
51888
52969
  }
@@ -51915,6 +52996,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51915
52996
  pnlEntries: data.pnl.pnlEntries,
51916
52997
  scheduledAt: data.scheduledAt,
51917
52998
  pendingAt: data.pendingAt,
52999
+ note: data.note,
51918
53000
  createdAt: data.timestamp,
51919
53001
  };
51920
53002
  }
@@ -51947,6 +53029,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51947
53029
  pnlEntries: data.pnl.pnlEntries,
51948
53030
  scheduledAt: data.scheduledAt,
51949
53031
  pendingAt: data.pendingAt,
53032
+ note: data.note,
51950
53033
  createdAt: data.timestamp,
51951
53034
  };
51952
53035
  }
@@ -51979,6 +53062,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
51979
53062
  pnlEntries: data.pnl.pnlEntries,
51980
53063
  scheduledAt: data.scheduledAt,
51981
53064
  pendingAt: data.pendingAt,
53065
+ note: data.note,
51982
53066
  createdAt: data.timestamp,
51983
53067
  };
51984
53068
  }
@@ -52012,6 +53096,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52012
53096
  pnlEntries: data.pnl.pnlEntries,
52013
53097
  scheduledAt: data.scheduledAt,
52014
53098
  pendingAt: data.pendingAt,
53099
+ note: data.note,
52015
53100
  createdAt: data.timestamp,
52016
53101
  };
52017
53102
  }
@@ -52035,6 +53120,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52035
53120
  pnlPriceClose: data.pnl.priceClose,
52036
53121
  pnlCost: data.pnl.pnlCost,
52037
53122
  pnlEntries: data.pnl.pnlEntries,
53123
+ note: data.note,
52038
53124
  createdAt: data.timestamp,
52039
53125
  };
52040
53126
  }
@@ -52058,6 +53144,7 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
52058
53144
  pnlPriceClose: data.pnl.priceClose,
52059
53145
  pnlCost: data.pnl.pnlCost,
52060
53146
  pnlEntries: data.pnl.pnlEntries,
53147
+ note: data.note,
52061
53148
  createdAt: data.timestamp,
52062
53149
  };
52063
53150
  }
@@ -52099,6 +53186,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52099
53186
  totalPartials: data.totalPartials,
52100
53187
  scheduledAt: data.scheduledAt,
52101
53188
  pendingAt: data.pendingAt,
53189
+ note: data.signal.note,
52102
53190
  createdAt: data.timestamp,
52103
53191
  };
52104
53192
  }
@@ -52131,6 +53219,7 @@ const CREATE_SIGNAL_SYNC_NOTIFICATION_FN = (data) => {
52131
53219
  scheduledAt: data.scheduledAt,
52132
53220
  pendingAt: data.pendingAt,
52133
53221
  closeReason: data.closeReason,
53222
+ note: data.signal.note,
52134
53223
  createdAt: data.timestamp,
52135
53224
  };
52136
53225
  }
@@ -53630,6 +54719,7 @@ const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53630
54719
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
53631
54720
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53632
54721
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
54722
+ const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53633
54723
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
53634
54724
  const MS_PER_MINUTE$1 = 60000;
53635
54725
  const INTERVAL_MINUTES$1 = {
@@ -53881,7 +54971,7 @@ class CacheFileInstance {
53881
54971
  /**
53882
54972
  * Clears the index counter.
53883
54973
  */
53884
- static clearCounter() {
54974
+ static resetCounter() {
53885
54975
  CacheFileInstance._indexCounter = 0;
53886
54976
  }
53887
54977
  /**
@@ -54136,11 +55226,17 @@ class CacheUtils {
54136
55226
  */
54137
55227
  this.clear = () => {
54138
55228
  backtest.loggerService.info(CACHE_METHOD_NAME_CLEAR);
54139
- {
54140
- this._getFnInstance.clear();
54141
- this._getFileInstance.clear();
54142
- }
54143
- CacheFileInstance.clearCounter();
55229
+ this._getFnInstance.clear();
55230
+ this._getFileInstance.clear();
55231
+ };
55232
+ /**
55233
+ * Resets the CacheFileInstance index counter to zero.
55234
+ * This is useful when process.cwd() changes between strategy iterations to ensure
55235
+ * that new CacheFileInstance objects start with index 0 and do not collide with old instances.
55236
+ */
55237
+ this.resetCounter = () => {
55238
+ backtest.loggerService.info(CACHE_METHOD_NAME_RESET_COUNTER);
55239
+ CacheFileInstance.resetCounter();
54144
55240
  };
54145
55241
  }
54146
55242
  }
@@ -54164,10 +55260,12 @@ const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
54164
55260
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
54165
55261
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
54166
55262
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
55263
+ const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
54167
55264
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
54168
55265
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
54169
55266
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
54170
55267
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
55268
+ const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
54171
55269
  const MS_PER_MINUTE = 60000;
54172
55270
  const INTERVAL_MINUTES = {
54173
55271
  "1m": 1,
@@ -54234,6 +55332,8 @@ const CREATE_KEY_FN = (strategyName, exchangeName, frameName, isBacktest) => {
54234
55332
  *
54235
55333
  * State is kept in memory; use `IntervalFileInstance` for persistence across restarts.
54236
55334
  *
55335
+ * @template F - Concrete function type
55336
+ *
54237
55337
  * @example
54238
55338
  * ```typescript
54239
55339
  * const instance = new IntervalFnInstance(mySignalFn, "1h");
@@ -54249,30 +55349,34 @@ class IntervalFnInstance {
54249
55349
  *
54250
55350
  * @param fn - Function to fire once per interval
54251
55351
  * @param interval - Candle interval that controls the firing boundary
55352
+ * @param key - Optional key generator for argument-based state separation.
55353
+ * Default: `([symbol]) => symbol`
54252
55354
  */
54253
- constructor(fn, interval) {
55355
+ constructor(fn, interval, key = ([symbol]) => symbol) {
54254
55356
  this.fn = fn;
54255
55357
  this.interval = interval;
54256
- /** Stores the last aligned timestamp per context+symbol key. */
55358
+ this.key = key;
55359
+ /** Stores the last aligned timestamp per context+symbol+args key. */
54257
55360
  this._stateMap = new Map();
54258
55361
  /**
54259
55362
  * Execute the signal function with once-per-interval enforcement.
54260
55363
  *
54261
55364
  * Algorithm:
54262
55365
  * 1. Align the current execution context `when` to the interval boundary.
54263
- * 2. If the stored aligned timestamp for this context+symbol equals the current one → return `null`.
54264
- * 3. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
55366
+ * 2. Build state key from context + key generator result.
55367
+ * 3. If the stored aligned timestamp for this key equals the current one return `null`.
55368
+ * 4. Otherwise call `fn`. If it returns a non-null signal, record the aligned timestamp and return
54265
55369
  * the signal. If it returns `null`, leave state unchanged so the next call retries.
54266
55370
  *
54267
55371
  * Requires active method context and execution context.
54268
55372
  *
54269
- * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
55373
+ * @param args - Arguments forwarded to the wrapped function
54270
55374
  * @returns The value returned by `fn` on the first non-null fire, `null` on all subsequent calls
54271
55375
  * within the same interval or when `fn` itself returned `null`
54272
55376
  * @throws Error if method context, execution context, or interval is missing
54273
55377
  */
54274
- this.run = async (symbol) => {
54275
- backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { symbol });
55378
+ this.run = async (...args) => {
55379
+ backtest.loggerService.debug(INTERVAL_METHOD_NAME_RUN, { args });
54276
55380
  const step = INTERVAL_MINUTES[this.interval];
54277
55381
  {
54278
55382
  if (!MethodContextService.hasContext()) {
@@ -54286,15 +55390,16 @@ class IntervalFnInstance {
54286
55390
  }
54287
55391
  }
54288
55392
  const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
54289
- const key = `${contextKey}:${symbol}`;
54290
55393
  const currentWhen = backtest.executionContextService.context.when;
54291
55394
  const currentAligned = align(currentWhen.getTime(), this.interval);
54292
- if (this._stateMap.get(key) === currentAligned) {
55395
+ const argKey = this.key(args);
55396
+ const stateKey = `${contextKey}:${argKey}`;
55397
+ if (this._stateMap.get(stateKey) === currentAligned) {
54293
55398
  return null;
54294
55399
  }
54295
- const result = await this.fn(symbol, currentWhen);
55400
+ const result = await this.fn.apply(null, args);
54296
55401
  if (result !== null) {
54297
- this._stateMap.set(key, currentAligned);
55402
+ this._stateMap.set(stateKey, currentAligned);
54298
55403
  }
54299
55404
  return result;
54300
55405
  };
@@ -54315,6 +55420,28 @@ class IntervalFnInstance {
54315
55420
  }
54316
55421
  }
54317
55422
  };
55423
+ /**
55424
+ * Garbage collect expired state entries.
55425
+ *
55426
+ * Removes all entries whose aligned timestamp differs from the current interval boundary.
55427
+ * Call this periodically to free memory from stale state entries.
55428
+ *
55429
+ * Requires active execution context to get current time.
55430
+ *
55431
+ * @returns Number of entries removed
55432
+ */
55433
+ this.gc = () => {
55434
+ const currentWhen = backtest.executionContextService.context.when;
55435
+ const currentAligned = align(currentWhen.getTime(), this.interval);
55436
+ let removed = 0;
55437
+ for (const [key, storedAligned] of this._stateMap.entries()) {
55438
+ if (storedAligned !== currentAligned) {
55439
+ this._stateMap.delete(key);
55440
+ removed++;
55441
+ }
55442
+ }
55443
+ return removed;
55444
+ };
54318
55445
  }
54319
55446
  }
54320
55447
  /**
@@ -54328,7 +55455,7 @@ class IntervalFnInstance {
54328
55455
  *
54329
55456
  * Fired state survives process restarts — unlike `IntervalFnInstance` which is in-memory only.
54330
55457
  *
54331
- * @template T - Async function type: `(symbol: string, ...args) => Promise<R | null>`
55458
+ * @template F - Concrete async function type
54332
55459
  *
54333
55460
  * @example
54334
55461
  * ```typescript
@@ -54349,7 +55476,7 @@ class IntervalFileInstance {
54349
55476
  * Resets the index counter to zero.
54350
55477
  * Call this when clearing all instances (e.g. on `IntervalUtils.clear()`).
54351
55478
  */
54352
- static clearCounter() {
55479
+ static resetCounter() {
54353
55480
  IntervalFileInstance._indexCounter = 0;
54354
55481
  }
54355
55482
  /**
@@ -54358,18 +55485,21 @@ class IntervalFileInstance {
54358
55485
  * @param fn - Async signal function to fire once per interval
54359
55486
  * @param interval - Candle interval that controls the firing boundary
54360
55487
  * @param name - Human-readable bucket name used as the directory prefix
55488
+ * @param key - Dynamic key generator; receives `[symbol, alignMs, ...rest]`.
55489
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
54361
55490
  */
54362
- constructor(fn, interval, name) {
55491
+ constructor(fn, interval, name, key = ([symbol, alignMs]) => `${symbol}_${alignMs}`) {
54363
55492
  this.fn = fn;
54364
55493
  this.interval = interval;
54365
55494
  this.name = name;
55495
+ this.key = key;
54366
55496
  /**
54367
55497
  * Execute the async function with persistent once-per-interval enforcement.
54368
55498
  *
54369
55499
  * Algorithm:
54370
55500
  * 1. Build bucket = `${name}_${interval}_${index}` — fixed per instance, used as directory name.
54371
- * 2. Align execution context `when` to interval boundary → `alignedTs`.
54372
- * 3. Build entity key = `${symbol}_${alignedTs}`.
55501
+ * 2. Align execution context `when` to interval boundary → `alignedMs`.
55502
+ * 3. Build entity key from the key generator (receives `[symbol, alignedMs, ...rest]`).
54373
55503
  * 4. Try to read from `PersistIntervalAdapter` using (bucket, entityKey).
54374
55504
  * 5. On hit — return `null` (interval already fired).
54375
55505
  * 6. On miss — call `fn`. If non-null, write to disk and return result. If null, skip write and return null.
@@ -54377,12 +55507,13 @@ class IntervalFileInstance {
54377
55507
  * Requires active method context and execution context.
54378
55508
  *
54379
55509
  * @param symbol - Trading pair symbol (e.g. "BTCUSDT")
55510
+ * @param args - Additional arguments forwarded to the wrapped function
54380
55511
  * @returns The value on the first non-null fire, `null` if already fired this interval
54381
55512
  * or if `fn` itself returned `null`
54382
55513
  * @throws Error if method context, execution context, or interval is missing
54383
55514
  */
54384
- this.run = async (symbol) => {
54385
- backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { symbol });
55515
+ this.run = async (...args) => {
55516
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN, { args });
54386
55517
  const step = INTERVAL_MINUTES[this.interval];
54387
55518
  {
54388
55519
  if (!MethodContextService.hasContext()) {
@@ -54395,15 +55526,16 @@ class IntervalFileInstance {
54395
55526
  throw new Error(`IntervalFileInstance unknown interval=${this.interval}`);
54396
55527
  }
54397
55528
  }
55529
+ const [symbol, ...rest] = args;
54398
55530
  const { when } = backtest.executionContextService.context;
54399
- const alignedTs = align(when.getTime(), this.interval);
55531
+ const alignedMs = align(when.getTime(), this.interval);
54400
55532
  const bucket = `${this.name}_${this.interval}_${this.index}`;
54401
- const entityKey = `${symbol}_${alignedTs}`;
55533
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
54402
55534
  const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
54403
55535
  if (cached !== null) {
54404
55536
  return null;
54405
55537
  }
54406
- const result = await this.fn(symbol, when);
55538
+ const result = await this.fn.call(null, ...args);
54407
55539
  if (result !== null) {
54408
55540
  await PersistIntervalAdapter.writeIntervalData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
54409
55541
  }
@@ -54444,12 +55576,12 @@ class IntervalUtils {
54444
55576
  * Memoized factory to get or create an `IntervalFnInstance` for a function.
54445
55577
  * Each function reference gets its own isolated instance.
54446
55578
  */
54447
- this._getInstance = memoize(([run]) => run, (run, interval) => new IntervalFnInstance(run, interval));
55579
+ this._getInstance = memoize(([run]) => run, (run, interval, key) => new IntervalFnInstance(run, interval, key));
54448
55580
  /**
54449
55581
  * Memoized factory to get or create an `IntervalFileInstance` for an async function.
54450
55582
  * Each function reference gets its own isolated persistent instance.
54451
55583
  */
54452
- this._getFileInstance = memoize(([run]) => run, (run, interval, name) => new IntervalFileInstance(run, interval, name));
55584
+ this._getFileInstance = memoize(([run]) => run, (run, interval, name, key) => new IntervalFileInstance(run, interval, name, key));
54453
55585
  /**
54454
55586
  * Wrap a signal function with in-memory once-per-interval firing.
54455
55587
  *
@@ -54461,21 +55593,30 @@ class IntervalUtils {
54461
55593
  *
54462
55594
  * @param run - Signal function to wrap
54463
55595
  * @param context.interval - Candle interval that controls the firing boundary
54464
- * @returns Wrapped function with the same signature as `TIntervalFn<T>`, plus a `clear()` method
55596
+ * @param context.key - Optional key generator for argument-based state separation
55597
+ * @returns Wrapped function with the same signature as `F`, plus a `clear()` method
54465
55598
  *
54466
55599
  * @example
54467
55600
  * ```typescript
55601
+ * // Without extra args
54468
55602
  * const fireOnce = Interval.fn(mySignalFn, { interval: "15m" });
54469
- *
54470
55603
  * await fireOnce("BTCUSDT"); // → T or null (fn called)
54471
55604
  * await fireOnce("BTCUSDT"); // → null (same interval, skipped)
55605
+ *
55606
+ * // With extra args and key
55607
+ * const fireOnce = Interval.fn(mySignalFn, {
55608
+ * interval: "15m",
55609
+ * key: ([symbol, period]) => `${symbol}_${period}`,
55610
+ * });
55611
+ * await fireOnce("BTCUSDT", 14); // → T or null
55612
+ * await fireOnce("BTCUSDT", 28); // → T or null (separate state)
54472
55613
  * ```
54473
55614
  */
54474
55615
  this.fn = (run, context) => {
54475
55616
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN, { context });
54476
- const wrappedFn = (symbol) => {
54477
- const instance = this._getInstance(run, context.interval);
54478
- return instance.run(symbol);
55617
+ const wrappedFn = (...args) => {
55618
+ const instance = this._getInstance(run, context.interval, context.key);
55619
+ return instance.run(...args);
54479
55620
  };
54480
55621
  wrappedFn.clear = () => {
54481
55622
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
@@ -54489,6 +55630,14 @@ class IntervalUtils {
54489
55630
  }
54490
55631
  this._getInstance.get(run)?.clear();
54491
55632
  };
55633
+ wrappedFn.gc = () => {
55634
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
55635
+ if (!ExecutionContextService.hasContext()) {
55636
+ backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
55637
+ return;
55638
+ }
55639
+ return this._getInstance.get(run)?.gc();
55640
+ };
54492
55641
  return wrappedFn;
54493
55642
  };
54494
55643
  /**
@@ -54501,28 +55650,33 @@ class IntervalUtils {
54501
55650
  * The `run` function reference is used as the memoization key for the underlying
54502
55651
  * `IntervalFileInstance`, so each unique function reference gets its own isolated instance.
54503
55652
  *
54504
- * @template T - Async function type to wrap
55653
+ * @template F - Concrete async function type
54505
55654
  * @param run - Async signal function to wrap with persistent once-per-interval firing
54506
55655
  * @param context.interval - Candle interval that controls the firing boundary
54507
55656
  * @param context.name - Human-readable bucket name; becomes the directory prefix
54508
- * @returns Wrapped function with the same signature as `T`, plus an async `clear()` method
54509
- * that deletes persisted records from disk and disposes the memoized instance
55657
+ * @param context.key - Optional entity key generator. Receives `[symbol, alignMs, ...rest]`.
55658
+ * Default: `([symbol, alignMs]) => \`${symbol}_${alignMs}\``
55659
+ * @returns Wrapped function with the same signature as `F`, plus an async `clear()` method
54510
55660
  *
54511
55661
  * @example
54512
55662
  * ```typescript
54513
- * const fetchSignal = async (symbol: string, when: Date) => { ... };
54514
- * const fireOnce = Interval.file(fetchSignal, { interval: "1h", name: "fetchSignal" });
54515
- * await fireOnce.clear(); // delete disk records so the function fires again next call
55663
+ * const fetchSignal = async (symbol: string, period: number) => { ... };
55664
+ * const fireOnce = Interval.file(fetchSignal, {
55665
+ * interval: "1h",
55666
+ * name: "fetchSignal",
55667
+ * key: ([symbol, alignMs, period]) => `${symbol}_${alignMs}_${period}`,
55668
+ * });
55669
+ * await fireOnce("BTCUSDT", 14);
54516
55670
  * ```
54517
55671
  */
54518
55672
  this.file = (run, context) => {
54519
55673
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE, { context });
54520
55674
  {
54521
- this._getFileInstance(run, context.interval, context.name);
55675
+ this._getFileInstance(run, context.interval, context.name, context.key);
54522
55676
  }
54523
- const wrappedFn = (symbol) => {
54524
- const instance = this._getFileInstance(run, context.interval, context.name);
54525
- return instance.run(symbol);
55677
+ const wrappedFn = (...args) => {
55678
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
55679
+ return instance.run(...args);
54526
55680
  };
54527
55681
  wrappedFn.clear = async () => {
54528
55682
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
@@ -54548,10 +55702,10 @@ class IntervalUtils {
54548
55702
  this.dispose = (run) => {
54549
55703
  backtest.loggerService.info(INTERVAL_METHOD_NAME_DISPOSE, { run });
54550
55704
  this._getInstance.clear(run);
55705
+ this._getFileInstance.clear(run);
54551
55706
  };
54552
55707
  /**
54553
- * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects and
54554
- * resets the `IntervalFileInstance` index counter.
55708
+ * Clears all memoized `IntervalFnInstance` and `IntervalFileInstance` objects.
54555
55709
  * Call this when `process.cwd()` changes between strategy iterations
54556
55710
  * so new instances are created with the updated base path.
54557
55711
  */
@@ -54559,7 +55713,15 @@ class IntervalUtils {
54559
55713
  backtest.loggerService.info(INTERVAL_METHOD_NAME_CLEAR);
54560
55714
  this._getInstance.clear();
54561
55715
  this._getFileInstance.clear();
54562
- IntervalFileInstance.clearCounter();
55716
+ };
55717
+ /**
55718
+ * Resets the IntervalFileInstance index counter to zero.
55719
+ * This is useful when process.cwd() changes between strategy iterations to ensure
55720
+ * that new IntervalFileInstance objects start with index 0 and do not collide with old instances.
55721
+ */
55722
+ this.resetCounter = () => {
55723
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_RESET_COUNTER);
55724
+ IntervalFileInstance.resetCounter();
54563
55725
  };
54564
55726
  }
54565
55727
  }
@@ -55693,4 +56855,4 @@ const validateSignal = (signal, currentPrice) => {
55693
56855
  return !errors.length;
55694
56856
  };
55695
56857
 
55696
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };
56858
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Dump, Exchange, ExecutionContextService, Heat, HighestProfit, Interval, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MarkdownWriter, MaxDrawdown, Memory, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistIntervalAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistMemoryAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, Position, PositionSize, Reflect$1 as Reflect, Report, ReportBase, ReportWriter, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Sync, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialLossCost, commitPartialProfit, commitPartialProfitCost, commitTrailingStop, commitTrailingStopCost, commitTrailingTake, commitTrailingTakeCost, dumpAgentAnswer, dumpError, dumpJson, dumpRecord, dumpTable, dumpText, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMaxDrawdownDistancePnlCost, getMaxDrawdownDistancePnlPercentage, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestMaxDrawdownPnlCost, getPositionHighestMaxDrawdownPnlPercentage, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitDistancePnlCost, getPositionHighestProfitDistancePnlPercentage, getPositionHighestProfitMinutes, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionMaxDrawdownMinutes, getPositionMaxDrawdownPnlCost, getPositionMaxDrawdownPnlPercentage, getPositionMaxDrawdownPrice, getPositionMaxDrawdownTimestamp, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasNoPendingSignal, hasNoScheduledSignal, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listMemory, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, listenMaxDrawdown, listenMaxDrawdownOnce, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenSync, listenSyncOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, percentDiff, percentToCloseCost, percentValue, readMemory, removeMemory, roundTicks, runInMockContext, searchMemory, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, validateCommonSignal, validatePendingSignal, validateScheduledSignal, validateSignal, waitForCandle, warmCandles, writeMemory };