backtest-kit 5.5.3 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/build/index.cjs +1999 -173
  2. package/build/index.mjs +1987 -173
  3. package/package.json +1 -1
  4. package/types.d.ts +1141 -179
package/build/index.mjs CHANGED
@@ -107,6 +107,7 @@ const markdownServices$1 = {
107
107
  riskMarkdownService: Symbol('riskMarkdownService'),
108
108
  strategyMarkdownService: Symbol('strategyMarkdownService'),
109
109
  syncMarkdownService: Symbol('syncMarkdownService'),
110
+ highestProfitMarkdownService: Symbol('highestProfitMarkdownService'),
110
111
  };
111
112
  const reportServices$1 = {
112
113
  backtestReportService: Symbol('backtestReportService'),
@@ -120,6 +121,7 @@ const reportServices$1 = {
120
121
  riskReportService: Symbol('riskReportService'),
121
122
  strategyReportService: Symbol('strategyReportService'),
122
123
  syncReportService: Symbol('syncReportService'),
124
+ highestProfitReportService: Symbol('highestProfitReportService'),
123
125
  };
124
126
  const validationServices$1 = {
125
127
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -622,6 +624,12 @@ const strategyCommitSubject = new Subject();
622
624
  * BacktestLogicPrivateService::*run
623
625
  */
624
626
  const backtestScheduleOpenSubject = new Subject();
627
+ /**
628
+ * Highest profit emitter for real-time profit tracking.
629
+ * Emits updates on the highest profit achieved for an open position.
630
+ * Allows users to track profit milestones and implement custom management logic based on profit levels.
631
+ */
632
+ const highestProfitSubject = new Subject();
625
633
 
626
634
  var emitters = /*#__PURE__*/Object.freeze({
627
635
  __proto__: null,
@@ -633,6 +641,7 @@ var emitters = /*#__PURE__*/Object.freeze({
633
641
  doneWalkerSubject: doneWalkerSubject,
634
642
  errorEmitter: errorEmitter,
635
643
  exitEmitter: exitEmitter,
644
+ highestProfitSubject: highestProfitSubject,
636
645
  partialLossSubject: partialLossSubject,
637
646
  partialProfitSubject: partialProfitSubject,
638
647
  performanceEmitter: performanceEmitter,
@@ -4251,6 +4260,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4251
4260
  timestamp: currentTime,
4252
4261
  _isScheduled: false,
4253
4262
  _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4263
+ _peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4254
4264
  };
4255
4265
  // Валидируем сигнал перед возвратом
4256
4266
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4275,6 +4285,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4275
4285
  timestamp: currentTime,
4276
4286
  _isScheduled: true,
4277
4287
  _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4288
+ _peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4278
4289
  };
4279
4290
  // Валидируем сигнал перед возвратом
4280
4291
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -4295,6 +4306,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
4295
4306
  timestamp: currentTime,
4296
4307
  _isScheduled: false,
4297
4308
  _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4309
+ _peak: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4298
4310
  };
4299
4311
  // Валидируем сигнал перед возвратом
4300
4312
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4997,6 +5009,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
4997
5009
  ...scheduled,
4998
5010
  pendingAt: activationTime,
4999
5011
  _isScheduled: false,
5012
+ _peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
5000
5013
  };
5001
5014
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5002
5015
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
@@ -5633,7 +5646,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
5633
5646
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
5634
5647
  return result;
5635
5648
  };
5636
- const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5649
+ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backtest) => {
5637
5650
  let percentTp = 0;
5638
5651
  let percentSl = 0;
5639
5652
  const currentTime = self.params.execution.context.when.getTime();
@@ -5654,6 +5667,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5654
5667
  const tpDistance = effectiveTakeProfit - effectivePriceOpen;
5655
5668
  const progressPercent = (currentDistance / tpDistance) * 100;
5656
5669
  percentTp = Math.min(progressPercent, 100);
5670
+ if (currentPrice > signal._peak.price) {
5671
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
5672
+ signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
5673
+ if (self.params.callbacks?.onWrite) {
5674
+ self.params.callbacks.onWrite(signal.symbol, signal, backtest);
5675
+ }
5676
+ !backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
5677
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
5678
+ }
5657
5679
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
5658
5680
  }
5659
5681
  else if (currentDistance < 0) {
@@ -5678,6 +5700,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5678
5700
  const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5679
5701
  const progressPercent = (currentDistance / tpDistance) * 100;
5680
5702
  percentTp = Math.min(progressPercent, 100);
5703
+ if (currentPrice < signal._peak.price) {
5704
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
5705
+ signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
5706
+ if (self.params.callbacks?.onWrite) {
5707
+ self.params.callbacks.onWrite(signal.symbol, signal, backtest);
5708
+ }
5709
+ !backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
5710
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
5711
+ }
5681
5712
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
5682
5713
  }
5683
5714
  if (currentDistance < 0) {
@@ -5725,7 +5756,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
5725
5756
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
5726
5757
  return result;
5727
5758
  };
5728
- const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason) => {
5759
+ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
5729
5760
  self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
5730
5761
  symbol: self.params.execution.context.symbol,
5731
5762
  signalId: scheduled.id,
@@ -5735,6 +5766,23 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
5735
5766
  reason,
5736
5767
  });
5737
5768
  await self.setScheduledSignal(null);
5769
+ if (reason === "user") {
5770
+ await CALL_COMMIT_FN(self, {
5771
+ action: "cancel-scheduled",
5772
+ symbol: self.params.execution.context.symbol,
5773
+ strategyName: self.params.strategyName,
5774
+ exchangeName: self.params.exchangeName,
5775
+ frameName: self.params.frameName,
5776
+ signalId: scheduled.id,
5777
+ backtest: true,
5778
+ cancelId,
5779
+ timestamp: closeTimestamp,
5780
+ totalEntries: scheduled._entry?.length ?? 1,
5781
+ totalPartials: scheduled._partial?.length ?? 0,
5782
+ originalPriceOpen: scheduled.priceOpen,
5783
+ pnl: toProfitLossDto(scheduled, averagePrice),
5784
+ });
5785
+ }
5738
5786
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
5739
5787
  const result = {
5740
5788
  action: "cancelled",
@@ -5747,6 +5795,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
5747
5795
  symbol: self.params.execution.context.symbol,
5748
5796
  backtest: self.params.execution.context.backtest,
5749
5797
  reason,
5798
+ cancelId,
5750
5799
  createdAt: closeTimestamp,
5751
5800
  };
5752
5801
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
@@ -5786,6 +5835,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
5786
5835
  ...scheduled,
5787
5836
  pendingAt: activationTime,
5788
5837
  _isScheduled: false,
5838
+ _peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
5789
5839
  };
5790
5840
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5791
5841
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
@@ -5935,7 +5985,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5935
5985
  // КРИТИЧНО: Проверяем был ли сигнал отменен пользователем через cancel()
5936
5986
  if (self._cancelledSignal) {
5937
5987
  // Сигнал был отменен через cancel() в onSchedulePing
5938
- const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user");
5988
+ const cancelId = self._cancelledSignal.cancelId;
5989
+ const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
5939
5990
  return { outcome: "cancelled", result };
5940
5991
  }
5941
5992
  // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
@@ -5965,6 +6016,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5965
6016
  ...activatedSignal,
5966
6017
  pendingAt: candle.timestamp,
5967
6018
  _isScheduled: false,
6019
+ _peak: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
5968
6020
  };
5969
6021
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5970
6022
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(candle.timestamp, pendingSignal.priceOpen, pendingSignal, self);
@@ -6166,6 +6218,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
6166
6218
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6167
6219
  const tpDistance = effectiveTakeProfit - effectivePriceOpen;
6168
6220
  const progressPercent = (currentDistance / tpDistance) * 100;
6221
+ if (averagePrice > signal._peak.price) {
6222
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
6223
+ signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
6224
+ if (self.params.callbacks?.onWrite) {
6225
+ self.params.callbacks.onWrite(signal.symbol, signal, true);
6226
+ }
6227
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
6228
+ }
6169
6229
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
6170
6230
  }
6171
6231
  else if (currentDistance < 0) {
@@ -6188,6 +6248,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
6188
6248
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6189
6249
  const tpDistance = effectivePriceOpen - effectiveTakeProfit;
6190
6250
  const progressPercent = (currentDistance / tpDistance) * 100;
6251
+ if (averagePrice < signal._peak.price) {
6252
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
6253
+ signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
6254
+ if (self.params.callbacks?.onWrite) {
6255
+ self.params.callbacks.onWrite(signal.symbol, signal, true);
6256
+ }
6257
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
6258
+ }
6191
6259
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
6192
6260
  }
6193
6261
  if (currentDistance < 0) {
@@ -6564,8 +6632,8 @@ class ClientStrategy {
6564
6632
  * @param symbol - Trading pair symbol
6565
6633
  * @returns Promise resolving to effective entry price or null
6566
6634
  */
6567
- async getPositionAveragePrice(symbol) {
6568
- this.params.logger.debug("ClientStrategy getPositionAveragePrice", { symbol });
6635
+ async getPositionEffectivePrice(symbol) {
6636
+ this.params.logger.debug("ClientStrategy getPositionEffectivePrice", { symbol });
6569
6637
  if (!this._pendingSignal) {
6570
6638
  return null;
6571
6639
  }
@@ -6722,6 +6790,170 @@ class ClientStrategy {
6722
6790
  }
6723
6791
  return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
6724
6792
  }
6793
+ /**
6794
+ * Returns the original estimated duration for the current pending signal.
6795
+ *
6796
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
6797
+ * number of minutes the position is expected to be active before `time_expired`.
6798
+ *
6799
+ * Returns null if no pending signal exists.
6800
+ *
6801
+ * @param symbol - Trading pair symbol
6802
+ * @returns Promise resolving to estimated duration in minutes or null
6803
+ */
6804
+ async getPositionEstimateMinutes(symbol) {
6805
+ this.params.logger.debug("ClientStrategy getPositionEstimateMinutes", { symbol });
6806
+ if (!this._pendingSignal) {
6807
+ return null;
6808
+ }
6809
+ return this._pendingSignal.minuteEstimatedTime;
6810
+ }
6811
+ /**
6812
+ * Returns the remaining time before the position expires, clamped to zero.
6813
+ *
6814
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
6815
+ * Returns 0 once the estimate is exceeded (never negative).
6816
+ *
6817
+ * Returns null if no pending signal exists.
6818
+ *
6819
+ * @param symbol - Trading pair symbol
6820
+ * @param timestamp - Current Unix timestamp in milliseconds
6821
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
6822
+ */
6823
+ async getPositionCountdownMinutes(symbol, timestamp) {
6824
+ this.params.logger.debug("ClientStrategy getPositionCountdownMinutes", { symbol });
6825
+ if (!this._pendingSignal) {
6826
+ return null;
6827
+ }
6828
+ const elapsed = Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
6829
+ return Math.max(0, this._pendingSignal.minuteEstimatedTime - elapsed);
6830
+ }
6831
+ /**
6832
+ * Returns the best price reached in the profit direction during this position's life.
6833
+ *
6834
+ * Initialized at position open with the entry price and timestamp.
6835
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6836
+ * - LONG: tracks the highest price seen above effective entry
6837
+ * - SHORT: tracks the lowest price seen below effective entry
6838
+ *
6839
+ * Returns null if no pending signal exists.
6840
+ * Never returns null when a signal is active — always contains at least the entry price.
6841
+ *
6842
+ * @param symbol - Trading pair symbol
6843
+ * @returns Promise resolving to price or null
6844
+ */
6845
+ async getPositionHighestProfitPrice(symbol) {
6846
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitPrice", { symbol });
6847
+ if (!this._pendingSignal) {
6848
+ return null;
6849
+ }
6850
+ return this._pendingSignal._peak.price;
6851
+ }
6852
+ /**
6853
+ * Returns the timestamp when the best profit price was recorded during this position's life.
6854
+ *
6855
+ * Returns null if no pending signal exists.
6856
+ *
6857
+ * @param symbol - Trading pair symbol
6858
+ * @returns Promise resolving to timestamp in milliseconds or null
6859
+ */
6860
+ async getPositionHighestProfitTimestamp(symbol) {
6861
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitTimestamp", { symbol });
6862
+ if (!this._pendingSignal) {
6863
+ return null;
6864
+ }
6865
+ return this._pendingSignal._peak.timestamp;
6866
+ }
6867
+ /**
6868
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
6869
+ *
6870
+ * Initialized at position open with 0.
6871
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6872
+ * - LONG: tracks the PnL percentage at the highest price seen above effective entry
6873
+ * - SHORT: tracks the PnL percentage at the lowest price seen below effective entry
6874
+ *
6875
+ * Returns null if no pending signal exists.
6876
+ * Never returns null when a signal is active — always contains at least 0.
6877
+ *
6878
+ * @param symbol - Trading pair symbol
6879
+ * @returns Promise resolving to PnL percentage or null
6880
+ */
6881
+ async getPositionHighestPnlPercentage(symbol) {
6882
+ this.params.logger.debug("ClientStrategy getPositionHighestPnlPercentage", { symbol });
6883
+ if (!this._pendingSignal) {
6884
+ return null;
6885
+ }
6886
+ return this._pendingSignal._peak.pnlPercentage;
6887
+ }
6888
+ /**
6889
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
6890
+ *
6891
+ * Initialized at position open with 0.
6892
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6893
+ * - LONG: tracks the PnL cost at the highest price seen above effective entry
6894
+ * - SHORT: tracks the PnL cost at the lowest price seen below effective entry
6895
+ *
6896
+ * Returns null if no pending signal exists.
6897
+ * Never returns null when a signal is active — always contains at least 0.
6898
+ *
6899
+ * @param symbol - Trading pair symbol
6900
+ * @returns Promise resolving to PnL cost or null
6901
+ */
6902
+ async getPositionHighestPnlCost(symbol) {
6903
+ this.params.logger.debug("ClientStrategy getPositionHighestPnlCost", { symbol });
6904
+ if (!this._pendingSignal) {
6905
+ return null;
6906
+ }
6907
+ return this._pendingSignal._peak.pnlCost;
6908
+ }
6909
+ /**
6910
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
6911
+ *
6912
+ * Measures how long the position has been pulling back from its peak profit level.
6913
+ * Zero when called at the exact moment the peak was set.
6914
+ * Grows continuously as price moves away from the peak without setting a new record.
6915
+ *
6916
+ * Returns null if no pending signal exists.
6917
+ *
6918
+ * @param symbol - Trading pair symbol
6919
+ * @param timestamp - Current Unix timestamp in milliseconds
6920
+ * @returns Promise resolving to drawdown duration in minutes or null
6921
+ */
6922
+ async getPositionHighestProfitBreakeven(symbol) {
6923
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitBreakeven", { symbol });
6924
+ if (!this._pendingSignal) {
6925
+ return null;
6926
+ }
6927
+ const signal = this._pendingSignal;
6928
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
6929
+ const peakPrice = signal._peak.price;
6930
+ const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
6931
+ if (signal.position === "long") {
6932
+ return peakPrice >= effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
6933
+ }
6934
+ else {
6935
+ return peakPrice <= effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
6936
+ }
6937
+ }
6938
+ /**
6939
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
6940
+ *
6941
+ * Measures how long the position has been pulling back from its peak.
6942
+ * Zero when called at the exact moment the peak was set.
6943
+ *
6944
+ * Returns null if no pending signal exists.
6945
+ *
6946
+ * @param symbol - Trading pair symbol
6947
+ * @param timestamp - Current Unix timestamp in milliseconds
6948
+ * @returns Promise resolving to drawdown duration in minutes or null
6949
+ */
6950
+ async getPositionDrawdownMinutes(symbol, timestamp) {
6951
+ this.params.logger.debug("ClientStrategy getPositionDrawdownMinutes", { symbol });
6952
+ if (!this._pendingSignal) {
6953
+ return null;
6954
+ }
6955
+ return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
6956
+ }
6725
6957
  /**
6726
6958
  * Performs a single tick of strategy execution.
6727
6959
  *
@@ -6897,6 +7129,7 @@ class ClientStrategy {
6897
7129
  ...activatedSignal,
6898
7130
  pendingAt: currentTime,
6899
7131
  _isScheduled: false,
7132
+ _peak: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
6900
7133
  };
6901
7134
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(currentTime, currentPrice, pendingSignal, this);
6902
7135
  if (!syncOpenAllowed) {
@@ -7019,7 +7252,7 @@ class ClientStrategy {
7019
7252
  if (closedResult) {
7020
7253
  return closedResult;
7021
7254
  }
7022
- return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice);
7255
+ return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice, false);
7023
7256
  }
7024
7257
  /**
7025
7258
  * Fast backtests a signal using historical candle data.
@@ -9006,7 +9239,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
9006
9239
  * @param backtest - Whether running in backtest mode
9007
9240
  * @returns Unique string key for memoization
9008
9241
  */
9009
- const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
9242
+ const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
9010
9243
  const parts = [symbol, strategyName, exchangeName];
9011
9244
  if (frameName)
9012
9245
  parts.push(frameName);
@@ -9130,6 +9363,43 @@ const CREATE_COMMIT_FN = (self) => trycatch(async (event) => {
9130
9363
  },
9131
9364
  defaultValue: null,
9132
9365
  });
9366
+ /**
9367
+ * Creates a callback function for emitting highest profit updates to highestProfitSubject.
9368
+ * Called by ClientStrategy when the highest profit for an open position is updated.
9369
+ * Emits HighestProfitContract event to all subscribers with the current price and timestamp.
9370
+ * Used for real-time profit tracking and management logic based on profit levels.
9371
+ *
9372
+ * @param self - Reference to StrategyConnectionService instance
9373
+ * @param strategyName - Name of the strategy
9374
+ * @param exchangeName - Name of the exchange
9375
+ * @param frameName - Name of the frame
9376
+ * @param isBacktest - Flag indicating if the operation is for backtesting
9377
+ * @returns Callback function for highest profit updates
9378
+ */
9379
+ const CREATE_HIGHEST_PROFIT_FN = (self, strategyName, exchangeName, frameName, isBacktest) => trycatch(async (signal, currentPrice, timestamp) => {
9380
+ await highestProfitSubject.next({
9381
+ symbol: signal.symbol,
9382
+ signal,
9383
+ currentPrice,
9384
+ timestamp,
9385
+ strategyName,
9386
+ exchangeName,
9387
+ frameName,
9388
+ backtest: isBacktest,
9389
+ });
9390
+ }, {
9391
+ fallback: (error) => {
9392
+ const message = "StrategyConnectionService CREATE_HIGHEST_PROFIT_FN thrown";
9393
+ const payload = {
9394
+ error: errorData(error),
9395
+ message: getErrorMessage(error),
9396
+ };
9397
+ bt.loggerService.warn(message, payload);
9398
+ console.warn(message, payload);
9399
+ errorEmitter.next(error);
9400
+ },
9401
+ defaultValue: null,
9402
+ });
9133
9403
  /**
9134
9404
  * Creates a callback function for emitting dispose events.
9135
9405
  *
@@ -9200,7 +9470,7 @@ class StrategyConnectionService {
9200
9470
  * @param backtest - Whether running in backtest mode
9201
9471
  * @returns Configured ClientStrategy instance
9202
9472
  */
9203
- this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9473
+ this.getStrategy = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9204
9474
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
9205
9475
  return new ClientStrategy({
9206
9476
  symbol,
@@ -9228,6 +9498,7 @@ class StrategyConnectionService {
9228
9498
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
9229
9499
  onCommit: CREATE_COMMIT_FN(),
9230
9500
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
9501
+ onHighestProfit: CREATE_HIGHEST_PROFIT_FN(this, strategyName, exchangeName, frameName, backtest),
9231
9502
  });
9232
9503
  });
9233
9504
  /**
@@ -9301,14 +9572,14 @@ class StrategyConnectionService {
9301
9572
  * @param context - Execution context with strategyName, exchangeName, frameName
9302
9573
  * @returns Promise resolving to effective entry price or null
9303
9574
  */
9304
- this.getPositionAveragePrice = async (backtest, symbol, context) => {
9305
- this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
9575
+ this.getPositionEffectivePrice = async (backtest, symbol, context) => {
9576
+ this.loggerService.log("strategyConnectionService getPositionEffectivePrice", {
9306
9577
  symbol,
9307
9578
  context,
9308
9579
  backtest,
9309
9580
  });
9310
9581
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9311
- return await strategy.getPositionAveragePrice(symbol);
9582
+ return await strategy.getPositionEffectivePrice(symbol);
9312
9583
  };
9313
9584
  /**
9314
9585
  * Returns the number of DCA entries made for the current pending signal.
@@ -9658,6 +9929,162 @@ class StrategyConnectionService {
9658
9929
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9659
9930
  return await strategy.hasPendingSignal(symbol);
9660
9931
  };
9932
+ /**
9933
+ * Returns the original estimated duration for the current pending signal.
9934
+ *
9935
+ * Delegates to ClientStrategy.getPositionEstimateMinutes().
9936
+ * Returns null if no pending signal exists.
9937
+ *
9938
+ * @param backtest - Whether running in backtest mode
9939
+ * @param symbol - Trading pair symbol
9940
+ * @param context - Execution context with strategyName, exchangeName, frameName
9941
+ * @returns Promise resolving to estimated duration in minutes or null
9942
+ */
9943
+ this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
9944
+ this.loggerService.log("strategyConnectionService getPositionEstimateMinutes", {
9945
+ symbol,
9946
+ context,
9947
+ });
9948
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9949
+ return await strategy.getPositionEstimateMinutes(symbol);
9950
+ };
9951
+ /**
9952
+ * Returns the remaining time before the position expires, clamped to zero.
9953
+ *
9954
+ * Resolves current timestamp via timeMetaService and delegates to
9955
+ * ClientStrategy.getPositionCountdownMinutes().
9956
+ * Returns null if no pending signal exists.
9957
+ *
9958
+ * @param backtest - Whether running in backtest mode
9959
+ * @param symbol - Trading pair symbol
9960
+ * @param context - Execution context with strategyName, exchangeName, frameName
9961
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
9962
+ */
9963
+ this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
9964
+ this.loggerService.log("strategyConnectionService getPositionCountdownMinutes", {
9965
+ symbol,
9966
+ context,
9967
+ });
9968
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9969
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9970
+ return await strategy.getPositionCountdownMinutes(symbol, timestamp);
9971
+ };
9972
+ /**
9973
+ * Returns the best price reached in the profit direction during this position's life.
9974
+ *
9975
+ * Delegates to ClientStrategy.getPositionHighestProfitPrice().
9976
+ * Returns null if no pending signal exists.
9977
+ *
9978
+ * @param backtest - Whether running in backtest mode
9979
+ * @param symbol - Trading pair symbol
9980
+ * @param context - Execution context with strategyName, exchangeName, frameName
9981
+ * @returns Promise resolving to price or null
9982
+ */
9983
+ this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
9984
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitPrice", {
9985
+ symbol,
9986
+ context,
9987
+ });
9988
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9989
+ return await strategy.getPositionHighestProfitPrice(symbol);
9990
+ };
9991
+ /**
9992
+ * Returns the timestamp when the best profit price was recorded during this position's life.
9993
+ *
9994
+ * Delegates to ClientStrategy.getPositionHighestProfitTimestamp().
9995
+ * Returns null if no pending signal exists.
9996
+ *
9997
+ * @param backtest - Whether running in backtest mode
9998
+ * @param symbol - Trading pair symbol
9999
+ * @param context - Execution context with strategyName, exchangeName, frameName
10000
+ * @returns Promise resolving to timestamp in milliseconds or null
10001
+ */
10002
+ this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
10003
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitTimestamp", {
10004
+ symbol,
10005
+ context,
10006
+ });
10007
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10008
+ return await strategy.getPositionHighestProfitTimestamp(symbol);
10009
+ };
10010
+ /**
10011
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
10012
+ *
10013
+ * Delegates to ClientStrategy.getPositionHighestPnlPercentage().
10014
+ * Returns null if no pending signal exists.
10015
+ *
10016
+ * @param backtest - Whether running in backtest mode
10017
+ * @param symbol - Trading pair symbol
10018
+ * @param context - Execution context with strategyName, exchangeName, frameName
10019
+ * @returns Promise resolving to PnL percentage or null
10020
+ */
10021
+ this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
10022
+ this.loggerService.log("strategyConnectionService getPositionHighestPnlPercentage", {
10023
+ symbol,
10024
+ context,
10025
+ });
10026
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10027
+ return await strategy.getPositionHighestPnlPercentage(symbol);
10028
+ };
10029
+ /**
10030
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
10031
+ *
10032
+ * Delegates to ClientStrategy.getPositionHighestPnlCost().
10033
+ * Returns null if no pending signal exists.
10034
+ *
10035
+ * @param backtest - Whether running in backtest mode
10036
+ * @param symbol - Trading pair symbol
10037
+ * @param context - Execution context with strategyName, exchangeName, frameName
10038
+ * @returns Promise resolving to PnL cost or null
10039
+ */
10040
+ this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
10041
+ this.loggerService.log("strategyConnectionService getPositionHighestPnlCost", {
10042
+ symbol,
10043
+ context,
10044
+ });
10045
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10046
+ return await strategy.getPositionHighestPnlCost(symbol);
10047
+ };
10048
+ /**
10049
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
10050
+ *
10051
+ * Delegates to ClientStrategy.getPositionHighestProfitBreakeven().
10052
+ * Returns null if no pending signal exists.
10053
+ *
10054
+ * @param backtest - Whether running in backtest mode
10055
+ * @param symbol - Trading pair symbol
10056
+ * @param context - Execution context with strategyName, exchangeName, frameName
10057
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
10058
+ */
10059
+ this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
10060
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitBreakeven", {
10061
+ symbol,
10062
+ context,
10063
+ });
10064
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10065
+ return await strategy.getPositionHighestProfitBreakeven(symbol);
10066
+ };
10067
+ /**
10068
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
10069
+ *
10070
+ * Resolves current timestamp via timeMetaService and delegates to
10071
+ * ClientStrategy.getPositionDrawdownMinutes().
10072
+ * Returns null if no pending signal exists.
10073
+ *
10074
+ * @param backtest - Whether running in backtest mode
10075
+ * @param symbol - Trading pair symbol
10076
+ * @param context - Execution context with strategyName, exchangeName, frameName
10077
+ * @returns Promise resolving to drawdown duration in minutes or null
10078
+ */
10079
+ this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
10080
+ this.loggerService.log("strategyConnectionService getPositionDrawdownMinutes", {
10081
+ symbol,
10082
+ context,
10083
+ });
10084
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10085
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
10086
+ return await strategy.getPositionDrawdownMinutes(symbol, timestamp);
10087
+ };
9661
10088
  /**
9662
10089
  * Disposes the ClientStrategy instance for the given context.
9663
10090
  *
@@ -9696,7 +10123,7 @@ class StrategyConnectionService {
9696
10123
  }
9697
10124
  return;
9698
10125
  }
9699
- const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
10126
+ const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9700
10127
  if (!this.getStrategy.has(key)) {
9701
10128
  return;
9702
10129
  }
@@ -10869,7 +11296,7 @@ class ClientRisk {
10869
11296
  * @param backtest - Whether running in backtest mode
10870
11297
  * @returns Unique string key for memoization
10871
11298
  */
10872
- const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
11299
+ const CREATE_KEY_FN$o = (riskName, exchangeName, frameName, backtest) => {
10873
11300
  const parts = [riskName, exchangeName];
10874
11301
  if (frameName)
10875
11302
  parts.push(frameName);
@@ -10968,7 +11395,7 @@ class RiskConnectionService {
10968
11395
  * @param backtest - True if backtest mode, false if live mode
10969
11396
  * @returns Configured ClientRisk instance
10970
11397
  */
10971
- this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
11398
+ this.getRisk = memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10972
11399
  const schema = this.riskSchemaService.get(riskName);
10973
11400
  return new ClientRisk({
10974
11401
  ...schema,
@@ -11036,7 +11463,7 @@ class RiskConnectionService {
11036
11463
  payload,
11037
11464
  });
11038
11465
  if (payload) {
11039
- const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
11466
+ const key = CREATE_KEY_FN$o(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
11040
11467
  this.getRisk.clear(key);
11041
11468
  }
11042
11469
  else {
@@ -12503,7 +12930,7 @@ class ClientAction {
12503
12930
  * @param backtest - Whether running in backtest mode
12504
12931
  * @returns Unique string key for memoization
12505
12932
  */
12506
- const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
12933
+ const CREATE_KEY_FN$n = (actionName, strategyName, exchangeName, frameName, backtest) => {
12507
12934
  const parts = [actionName, strategyName, exchangeName];
12508
12935
  if (frameName)
12509
12936
  parts.push(frameName);
@@ -12554,7 +12981,7 @@ class ActionConnectionService {
12554
12981
  * @param backtest - True if backtest mode, false if live mode
12555
12982
  * @returns Configured ClientAction instance
12556
12983
  */
12557
- this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12984
+ this.getAction = memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12558
12985
  const schema = this.actionSchemaService.get(actionName);
12559
12986
  return new ClientAction({
12560
12987
  ...schema,
@@ -12764,7 +13191,7 @@ class ActionConnectionService {
12764
13191
  await Promise.all(actions.map(async (action) => await action.dispose()));
12765
13192
  return;
12766
13193
  }
12767
- const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13194
+ const key = CREATE_KEY_FN$n(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12768
13195
  if (!this.getAction.has(key)) {
12769
13196
  return;
12770
13197
  }
@@ -12782,7 +13209,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
12782
13209
  * @param exchangeName - Exchange name
12783
13210
  * @returns Unique string key for memoization
12784
13211
  */
12785
- const CREATE_KEY_FN$l = (exchangeName) => {
13212
+ const CREATE_KEY_FN$m = (exchangeName) => {
12786
13213
  return exchangeName;
12787
13214
  };
12788
13215
  /**
@@ -12806,7 +13233,7 @@ class ExchangeCoreService {
12806
13233
  * @param exchangeName - Name of the exchange to validate
12807
13234
  * @returns Promise that resolves when validation is complete
12808
13235
  */
12809
- this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
13236
+ this.validate = memoize(([exchangeName]) => CREATE_KEY_FN$m(exchangeName), async (exchangeName) => {
12810
13237
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
12811
13238
  exchangeName,
12812
13239
  });
@@ -13058,7 +13485,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
13058
13485
  * @param context - Execution context with strategyName, exchangeName, frameName
13059
13486
  * @returns Unique string key for memoization
13060
13487
  */
13061
- const CREATE_KEY_FN$k = (context) => {
13488
+ const CREATE_KEY_FN$l = (context) => {
13062
13489
  const parts = [context.strategyName, context.exchangeName];
13063
13490
  if (context.frameName)
13064
13491
  parts.push(context.frameName);
@@ -13090,7 +13517,7 @@ class StrategyCoreService {
13090
13517
  * @param context - Execution context with strategyName, exchangeName, frameName
13091
13518
  * @returns Promise that resolves when validation is complete
13092
13519
  */
13093
- this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
13520
+ this.validate = memoize(([context]) => CREATE_KEY_FN$l(context), async (context) => {
13094
13521
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
13095
13522
  context,
13096
13523
  });
@@ -13154,13 +13581,13 @@ class StrategyCoreService {
13154
13581
  await this.validate(context);
13155
13582
  return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
13156
13583
  };
13157
- this.getPositionAveragePrice = async (backtest, symbol, context) => {
13158
- this.loggerService.log("strategyCoreService getPositionAveragePrice", {
13584
+ this.getPositionEffectivePrice = async (backtest, symbol, context) => {
13585
+ this.loggerService.log("strategyCoreService getPositionEffectivePrice", {
13159
13586
  symbol,
13160
13587
  context,
13161
13588
  });
13162
13589
  await this.validate(context);
13163
- return await this.strategyConnectionService.getPositionAveragePrice(backtest, symbol, context);
13590
+ return await this.strategyConnectionService.getPositionEffectivePrice(backtest, symbol, context);
13164
13591
  };
13165
13592
  this.getPositionInvestedCount = async (backtest, symbol, context) => {
13166
13593
  this.loggerService.log("strategyCoreService getPositionInvestedCount", {
@@ -13853,6 +14280,143 @@ class StrategyCoreService {
13853
14280
  await this.validate(context);
13854
14281
  return await this.strategyConnectionService.hasPendingSignal(backtest, symbol, context);
13855
14282
  };
14283
+ /**
14284
+ * Returns the original estimated duration for the current pending signal.
14285
+ *
14286
+ * Validates strategy existence and delegates to connection service.
14287
+ * Returns null if no pending signal exists.
14288
+ *
14289
+ * @param backtest - Whether running in backtest mode
14290
+ * @param symbol - Trading pair symbol
14291
+ * @param context - Execution context with strategyName, exchangeName, frameName
14292
+ * @returns Promise resolving to estimated duration in minutes or null
14293
+ */
14294
+ this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
14295
+ this.loggerService.log("strategyCoreService getPositionEstimateMinutes", {
14296
+ symbol,
14297
+ context,
14298
+ });
14299
+ await this.validate(context);
14300
+ return await this.strategyConnectionService.getPositionEstimateMinutes(backtest, symbol, context);
14301
+ };
14302
+ /**
14303
+ * Returns the remaining time before the position expires, clamped to zero.
14304
+ *
14305
+ * Validates strategy existence and delegates to connection service.
14306
+ * Returns null if no pending signal exists.
14307
+ *
14308
+ * @param backtest - Whether running in backtest mode
14309
+ * @param symbol - Trading pair symbol
14310
+ * @param context - Execution context with strategyName, exchangeName, frameName
14311
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
14312
+ */
14313
+ this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
14314
+ this.loggerService.log("strategyCoreService getPositionCountdownMinutes", {
14315
+ symbol,
14316
+ context,
14317
+ });
14318
+ await this.validate(context);
14319
+ return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
14320
+ };
14321
+ /**
14322
+ * Returns the best price reached in the profit direction during this position's life.
14323
+ *
14324
+ * @param backtest - Whether running in backtest mode
14325
+ * @param symbol - Trading pair symbol
14326
+ * @param context - Execution context with strategyName, exchangeName, frameName
14327
+ * @returns Promise resolving to price or null
14328
+ */
14329
+ this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
14330
+ this.loggerService.log("strategyCoreService getPositionHighestProfitPrice", {
14331
+ symbol,
14332
+ context,
14333
+ });
14334
+ await this.validate(context);
14335
+ return await this.strategyConnectionService.getPositionHighestProfitPrice(backtest, symbol, context);
14336
+ };
14337
+ /**
14338
+ * Returns the timestamp when the best profit price was recorded during this position's life.
14339
+ *
14340
+ * @param backtest - Whether running in backtest mode
14341
+ * @param symbol - Trading pair symbol
14342
+ * @param context - Execution context with strategyName, exchangeName, frameName
14343
+ * @returns Promise resolving to timestamp in milliseconds or null
14344
+ */
14345
+ this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
14346
+ this.loggerService.log("strategyCoreService getPositionHighestProfitTimestamp", {
14347
+ symbol,
14348
+ context,
14349
+ });
14350
+ await this.validate(context);
14351
+ return await this.strategyConnectionService.getPositionHighestProfitTimestamp(backtest, symbol, context);
14352
+ };
14353
+ /**
14354
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
14355
+ *
14356
+ * @param backtest - Whether running in backtest mode
14357
+ * @param symbol - Trading pair symbol
14358
+ * @param context - Execution context with strategyName, exchangeName, frameName
14359
+ * @returns Promise resolving to PnL percentage or null
14360
+ */
14361
+ this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
14362
+ this.loggerService.log("strategyCoreService getPositionHighestPnlPercentage", {
14363
+ symbol,
14364
+ context,
14365
+ });
14366
+ await this.validate(context);
14367
+ return await this.strategyConnectionService.getPositionHighestPnlPercentage(backtest, symbol, context);
14368
+ };
14369
+ /**
14370
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
14371
+ *
14372
+ * @param backtest - Whether running in backtest mode
14373
+ * @param symbol - Trading pair symbol
14374
+ * @param context - Execution context with strategyName, exchangeName, frameName
14375
+ * @returns Promise resolving to PnL cost or null
14376
+ */
14377
+ this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
14378
+ this.loggerService.log("strategyCoreService getPositionHighestPnlCost", {
14379
+ symbol,
14380
+ context,
14381
+ });
14382
+ await this.validate(context);
14383
+ return await this.strategyConnectionService.getPositionHighestPnlCost(backtest, symbol, context);
14384
+ };
14385
+ /**
14386
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
14387
+ *
14388
+ * @param backtest - Whether running in backtest mode
14389
+ * @param symbol - Trading pair symbol
14390
+ * @param context - Execution context with strategyName, exchangeName, frameName
14391
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
14392
+ */
14393
+ this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
14394
+ this.loggerService.log("strategyCoreService getPositionHighestProfitBreakeven", {
14395
+ symbol,
14396
+ context,
14397
+ });
14398
+ await this.validate(context);
14399
+ return await this.strategyConnectionService.getPositionHighestProfitBreakeven(backtest, symbol, context);
14400
+ };
14401
+ /**
14402
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
14403
+ *
14404
+ * Validates strategy existence and delegates to connection service.
14405
+ * Returns null if no pending signal exists.
14406
+ *
14407
+ * @param backtest - Whether running in backtest mode
14408
+ * @param symbol - Trading pair symbol
14409
+ * @param context - Execution context with strategyName, exchangeName, frameName
14410
+ * @returns Promise resolving to drawdown duration in minutes or null
14411
+ */
14412
+ this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
14413
+ this.loggerService.log("strategyCoreService getPositionDrawdownMinutes", {
14414
+ symbol,
14415
+ context,
14416
+ });
14417
+ await this.validate(context);
14418
+ return await this.strategyConnectionService.getPositionDrawdownMinutes(backtest, symbol, context);
14419
+ };
13856
14420
  }
13857
14421
  }
13858
14422
 
@@ -13925,7 +14489,7 @@ class SizingGlobalService {
13925
14489
  * @param context - Context with riskName, exchangeName, frameName
13926
14490
  * @returns Unique string key for memoization
13927
14491
  */
13928
- const CREATE_KEY_FN$j = (context) => {
14492
+ const CREATE_KEY_FN$k = (context) => {
13929
14493
  const parts = [context.riskName, context.exchangeName];
13930
14494
  if (context.frameName)
13931
14495
  parts.push(context.frameName);
@@ -13951,7 +14515,7 @@ class RiskGlobalService {
13951
14515
  * @param payload - Payload with riskName, exchangeName and frameName
13952
14516
  * @returns Promise that resolves when validation is complete
13953
14517
  */
13954
- this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
14518
+ this.validate = memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
13955
14519
  this.loggerService.log("riskGlobalService validate", {
13956
14520
  context,
13957
14521
  });
@@ -14029,7 +14593,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
14029
14593
  * @param context - Execution context with strategyName, exchangeName, frameName
14030
14594
  * @returns Unique string key for memoization
14031
14595
  */
14032
- const CREATE_KEY_FN$i = (context) => {
14596
+ const CREATE_KEY_FN$j = (context) => {
14033
14597
  const parts = [context.strategyName, context.exchangeName];
14034
14598
  if (context.frameName)
14035
14599
  parts.push(context.frameName);
@@ -14073,7 +14637,7 @@ class ActionCoreService {
14073
14637
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
14074
14638
  * @returns Promise that resolves when all validations complete
14075
14639
  */
14076
- this.validate = memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
14640
+ this.validate = memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
14077
14641
  this.loggerService.log(METHOD_NAME_VALIDATE, {
14078
14642
  context,
14079
14643
  });
@@ -17884,6 +18448,93 @@ const sync_columns = [
17884
18448
  },
17885
18449
  ];
17886
18450
 
18451
+ /**
18452
+ * Column configuration for highest profit markdown reports.
18453
+ *
18454
+ * Defines the table structure for displaying highest-profit-record events.
18455
+ *
18456
+ * @see HighestProfitMarkdownService
18457
+ * @see ColumnModel
18458
+ * @see HighestProfitEvent
18459
+ */
18460
+ const highest_profit_columns = [
18461
+ {
18462
+ key: "symbol",
18463
+ label: "Symbol",
18464
+ format: (data) => data.symbol,
18465
+ isVisible: () => true,
18466
+ },
18467
+ {
18468
+ key: "strategyName",
18469
+ label: "Strategy",
18470
+ format: (data) => data.strategyName,
18471
+ isVisible: () => true,
18472
+ },
18473
+ {
18474
+ key: "signalId",
18475
+ label: "Signal ID",
18476
+ format: (data) => data.signalId,
18477
+ isVisible: () => true,
18478
+ },
18479
+ {
18480
+ key: "position",
18481
+ label: "Position",
18482
+ format: (data) => data.position.toUpperCase(),
18483
+ isVisible: () => true,
18484
+ },
18485
+ {
18486
+ key: "pnl",
18487
+ label: "PNL (net)",
18488
+ format: (data) => {
18489
+ const pnlPercentage = data.pnl.pnlPercentage;
18490
+ return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
18491
+ },
18492
+ isVisible: () => true,
18493
+ },
18494
+ {
18495
+ key: "pnlCost",
18496
+ label: "PNL (USD)",
18497
+ format: (data) => `${data.pnl.pnlCost > 0 ? "+" : ""}${data.pnl.pnlCost.toFixed(2)} USD`,
18498
+ isVisible: () => true,
18499
+ },
18500
+ {
18501
+ key: "currentPrice",
18502
+ label: "Record Price",
18503
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
18504
+ isVisible: () => true,
18505
+ },
18506
+ {
18507
+ key: "priceOpen",
18508
+ label: "Entry Price",
18509
+ format: (data) => `${data.priceOpen.toFixed(8)} USD`,
18510
+ isVisible: () => true,
18511
+ },
18512
+ {
18513
+ key: "priceTakeProfit",
18514
+ label: "Take Profit",
18515
+ format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
18516
+ isVisible: () => true,
18517
+ },
18518
+ {
18519
+ key: "priceStopLoss",
18520
+ label: "Stop Loss",
18521
+ format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
18522
+ isVisible: () => true,
18523
+ },
18524
+ {
18525
+ key: "timestamp",
18526
+ label: "Timestamp",
18527
+ format: (data) => new Date(data.timestamp).toISOString(),
18528
+ isVisible: () => true,
18529
+ },
18530
+ {
18531
+ key: "mode",
18532
+ label: "Mode",
18533
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
18534
+ isVisible: () => true,
18535
+ },
18536
+ ];
18537
+
17887
18538
  /**
17888
18539
  * Column configuration for walker strategy comparison table in markdown reports.
17889
18540
  *
@@ -18101,6 +18752,8 @@ const COLUMN_CONFIG = {
18101
18752
  strategy_columns,
18102
18753
  /** Columns for signal sync lifecycle events (signal-open, signal-close) */
18103
18754
  sync_columns,
18755
+ /** Columns for highest profit milestone tracking events */
18756
+ highest_profit_columns,
18104
18757
  /** Walker: PnL summary columns */
18105
18758
  walker_pnl_columns,
18106
18759
  /** Walker: strategy-level summary columns */
@@ -18141,6 +18794,7 @@ const WILDCARD_TARGET$1 = {
18141
18794
  schedule: true,
18142
18795
  walker: true,
18143
18796
  sync: true,
18797
+ highest_profit: true,
18144
18798
  };
18145
18799
  /**
18146
18800
  * JSONL-based markdown adapter with append-only writes.
@@ -18368,7 +19022,7 @@ class MarkdownUtils {
18368
19022
  *
18369
19023
  * @returns Cleanup function that unsubscribes from all enabled services
18370
19024
  */
18371
- this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, } = WILDCARD_TARGET$1) => {
19025
+ this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, strategy = false, risk = false, schedule = false, walker = false, sync = false, highest_profit = false, } = WILDCARD_TARGET$1) => {
18372
19026
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
18373
19027
  backtest: bt$1,
18374
19028
  breakeven,
@@ -18381,6 +19035,7 @@ class MarkdownUtils {
18381
19035
  schedule,
18382
19036
  walker,
18383
19037
  sync,
19038
+ highest_profit,
18384
19039
  });
18385
19040
  const unList = [];
18386
19041
  if (bt$1) {
@@ -18416,6 +19071,9 @@ class MarkdownUtils {
18416
19071
  if (sync) {
18417
19072
  unList.push(bt.syncMarkdownService.subscribe());
18418
19073
  }
19074
+ if (highest_profit) {
19075
+ unList.push(bt.highestProfitMarkdownService.subscribe());
19076
+ }
18419
19077
  return compose(...unList.map((un) => () => void un()));
18420
19078
  };
18421
19079
  /**
@@ -18455,7 +19113,7 @@ class MarkdownUtils {
18455
19113
  * Markdown.disable();
18456
19114
  * ```
18457
19115
  */
18458
- this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, } = WILDCARD_TARGET$1) => {
19116
+ this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, strategy = false, schedule = false, walker = false, sync = false, highest_profit = false, } = WILDCARD_TARGET$1) => {
18459
19117
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
18460
19118
  backtest: bt$1,
18461
19119
  breakeven,
@@ -18468,6 +19126,7 @@ class MarkdownUtils {
18468
19126
  schedule,
18469
19127
  walker,
18470
19128
  sync,
19129
+ highest_profit,
18471
19130
  });
18472
19131
  if (bt$1) {
18473
19132
  bt.backtestMarkdownService.unsubscribe();
@@ -18502,6 +19161,9 @@ class MarkdownUtils {
18502
19161
  if (sync) {
18503
19162
  bt.syncMarkdownService.unsubscribe();
18504
19163
  }
19164
+ if (highest_profit) {
19165
+ bt.highestProfitMarkdownService.unsubscribe();
19166
+ }
18505
19167
  };
18506
19168
  }
18507
19169
  }
@@ -18608,7 +19270,7 @@ const Markdown = new MarkdownAdapter();
18608
19270
  * @param backtest - Whether running in backtest mode
18609
19271
  * @returns Unique string key for memoization
18610
19272
  */
18611
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19273
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
18612
19274
  const parts = [symbol, strategyName, exchangeName];
18613
19275
  if (frameName)
18614
19276
  parts.push(frameName);
@@ -18625,7 +19287,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
18625
19287
  * @param timestamp - Unix timestamp in milliseconds
18626
19288
  * @returns Filename string
18627
19289
  */
18628
- const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19290
+ const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
18629
19291
  const parts = [symbol, strategyName, exchangeName];
18630
19292
  if (frameName) {
18631
19293
  parts.push(frameName);
@@ -18654,12 +19316,12 @@ function isUnsafe$3(value) {
18654
19316
  return false;
18655
19317
  }
18656
19318
  /** Maximum number of signals to store in backtest reports */
18657
- const MAX_EVENTS$9 = 250;
19319
+ const MAX_EVENTS$a = 250;
18658
19320
  /**
18659
19321
  * Storage class for accumulating closed signals per strategy.
18660
19322
  * Maintains a list of all closed signals and provides methods to generate reports.
18661
19323
  */
18662
- let ReportStorage$8 = class ReportStorage {
19324
+ let ReportStorage$9 = class ReportStorage {
18663
19325
  constructor(symbol, strategyName, exchangeName, frameName) {
18664
19326
  this.symbol = symbol;
18665
19327
  this.strategyName = strategyName;
@@ -18676,7 +19338,7 @@ let ReportStorage$8 = class ReportStorage {
18676
19338
  addSignal(data) {
18677
19339
  this._signalList.unshift(data);
18678
19340
  // Trim queue if exceeded MAX_EVENTS
18679
- if (this._signalList.length > MAX_EVENTS$9) {
19341
+ if (this._signalList.length > MAX_EVENTS$a) {
18680
19342
  this._signalList.pop();
18681
19343
  }
18682
19344
  }
@@ -18800,7 +19462,7 @@ let ReportStorage$8 = class ReportStorage {
18800
19462
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
18801
19463
  const markdown = await this.getReport(strategyName, columns);
18802
19464
  const timestamp = getContextTimestamp();
18803
- const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19465
+ const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18804
19466
  await Markdown.writeData("backtest", markdown, {
18805
19467
  path,
18806
19468
  file: filename,
@@ -18847,7 +19509,7 @@ class BacktestMarkdownService {
18847
19509
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18848
19510
  * Each combination gets its own isolated storage instance.
18849
19511
  */
18850
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
19512
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
18851
19513
  /**
18852
19514
  * Processes tick events and accumulates closed signals.
18853
19515
  * Should be called from IStrategyCallbacks.onTick.
@@ -19004,7 +19666,7 @@ class BacktestMarkdownService {
19004
19666
  payload,
19005
19667
  });
19006
19668
  if (payload) {
19007
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19669
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19008
19670
  this.getStorage.clear(key);
19009
19671
  }
19010
19672
  else {
@@ -19066,7 +19728,7 @@ class BacktestMarkdownService {
19066
19728
  * @param backtest - Whether running in backtest mode
19067
19729
  * @returns Unique string key for memoization
19068
19730
  */
19069
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19731
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19070
19732
  const parts = [symbol, strategyName, exchangeName];
19071
19733
  if (frameName)
19072
19734
  parts.push(frameName);
@@ -19083,7 +19745,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
19083
19745
  * @param timestamp - Unix timestamp in milliseconds
19084
19746
  * @returns Filename string
19085
19747
  */
19086
- const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19748
+ const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19087
19749
  const parts = [symbol, strategyName, exchangeName];
19088
19750
  if (frameName) {
19089
19751
  parts.push(frameName);
@@ -19112,12 +19774,12 @@ function isUnsafe$2(value) {
19112
19774
  return false;
19113
19775
  }
19114
19776
  /** Maximum number of events to store in live trading reports */
19115
- const MAX_EVENTS$8 = 250;
19777
+ const MAX_EVENTS$9 = 250;
19116
19778
  /**
19117
19779
  * Storage class for accumulating all tick events per strategy.
19118
19780
  * Maintains a chronological list of all events (idle, opened, active, closed).
19119
19781
  */
19120
- let ReportStorage$7 = class ReportStorage {
19782
+ let ReportStorage$8 = class ReportStorage {
19121
19783
  constructor(symbol, strategyName, exchangeName, frameName) {
19122
19784
  this.symbol = symbol;
19123
19785
  this.strategyName = strategyName;
@@ -19149,7 +19811,7 @@ let ReportStorage$7 = class ReportStorage {
19149
19811
  }
19150
19812
  {
19151
19813
  this._eventList.unshift(newEvent);
19152
- if (this._eventList.length > MAX_EVENTS$8) {
19814
+ if (this._eventList.length > MAX_EVENTS$9) {
19153
19815
  this._eventList.pop();
19154
19816
  }
19155
19817
  }
@@ -19179,7 +19841,7 @@ let ReportStorage$7 = class ReportStorage {
19179
19841
  scheduledAt: data.signal.scheduledAt,
19180
19842
  });
19181
19843
  // Trim queue if exceeded MAX_EVENTS
19182
- if (this._eventList.length > MAX_EVENTS$8) {
19844
+ if (this._eventList.length > MAX_EVENTS$9) {
19183
19845
  this._eventList.pop();
19184
19846
  }
19185
19847
  }
@@ -19223,7 +19885,7 @@ let ReportStorage$7 = class ReportStorage {
19223
19885
  // If no previous active event found, add new event
19224
19886
  this._eventList.unshift(newEvent);
19225
19887
  // Trim queue if exceeded MAX_EVENTS
19226
- if (this._eventList.length > MAX_EVENTS$8) {
19888
+ if (this._eventList.length > MAX_EVENTS$9) {
19227
19889
  this._eventList.pop();
19228
19890
  }
19229
19891
  }
@@ -19260,7 +19922,7 @@ let ReportStorage$7 = class ReportStorage {
19260
19922
  };
19261
19923
  this._eventList.unshift(newEvent);
19262
19924
  // Trim queue if exceeded MAX_EVENTS
19263
- if (this._eventList.length > MAX_EVENTS$8) {
19925
+ if (this._eventList.length > MAX_EVENTS$9) {
19264
19926
  this._eventList.pop();
19265
19927
  }
19266
19928
  }
@@ -19288,7 +19950,7 @@ let ReportStorage$7 = class ReportStorage {
19288
19950
  scheduledAt: data.signal.scheduledAt,
19289
19951
  });
19290
19952
  // Trim queue if exceeded MAX_EVENTS
19291
- if (this._eventList.length > MAX_EVENTS$8) {
19953
+ if (this._eventList.length > MAX_EVENTS$9) {
19292
19954
  this._eventList.pop();
19293
19955
  }
19294
19956
  }
@@ -19331,7 +19993,7 @@ let ReportStorage$7 = class ReportStorage {
19331
19993
  // If no previous waiting event found, add new event
19332
19994
  this._eventList.unshift(newEvent);
19333
19995
  // Trim queue if exceeded MAX_EVENTS
19334
- if (this._eventList.length > MAX_EVENTS$8) {
19996
+ if (this._eventList.length > MAX_EVENTS$9) {
19335
19997
  this._eventList.pop();
19336
19998
  }
19337
19999
  }
@@ -19360,7 +20022,7 @@ let ReportStorage$7 = class ReportStorage {
19360
20022
  scheduledAt: data.signal.scheduledAt,
19361
20023
  });
19362
20024
  // Trim queue if exceeded MAX_EVENTS
19363
- if (this._eventList.length > MAX_EVENTS$8) {
20025
+ if (this._eventList.length > MAX_EVENTS$9) {
19364
20026
  this._eventList.pop();
19365
20027
  }
19366
20028
  }
@@ -19499,7 +20161,7 @@ let ReportStorage$7 = class ReportStorage {
19499
20161
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
19500
20162
  const markdown = await this.getReport(strategyName, columns);
19501
20163
  const timestamp = getContextTimestamp();
19502
- const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20164
+ const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19503
20165
  await Markdown.writeData("live", markdown, {
19504
20166
  path,
19505
20167
  signalId: "",
@@ -19549,7 +20211,7 @@ class LiveMarkdownService {
19549
20211
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19550
20212
  * Each combination gets its own isolated storage instance.
19551
20213
  */
19552
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
20214
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
19553
20215
  /**
19554
20216
  * Subscribes to live signal emitter to receive tick events.
19555
20217
  * Protected against multiple subscriptions.
@@ -19767,7 +20429,7 @@ class LiveMarkdownService {
19767
20429
  payload,
19768
20430
  });
19769
20431
  if (payload) {
19770
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20432
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19771
20433
  this.getStorage.clear(key);
19772
20434
  }
19773
20435
  else {
@@ -19787,7 +20449,7 @@ class LiveMarkdownService {
19787
20449
  * @param backtest - Whether running in backtest mode
19788
20450
  * @returns Unique string key for memoization
19789
20451
  */
19790
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20452
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19791
20453
  const parts = [symbol, strategyName, exchangeName];
19792
20454
  if (frameName)
19793
20455
  parts.push(frameName);
@@ -19804,7 +20466,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
19804
20466
  * @param timestamp - Unix timestamp in milliseconds
19805
20467
  * @returns Filename string
19806
20468
  */
19807
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20469
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19808
20470
  const parts = [symbol, strategyName, exchangeName];
19809
20471
  if (frameName) {
19810
20472
  parts.push(frameName);
@@ -19815,12 +20477,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
19815
20477
  return `${parts.join("_")}-${timestamp}.md`;
19816
20478
  };
19817
20479
  /** Maximum number of events to store in schedule reports */
19818
- const MAX_EVENTS$7 = 250;
20480
+ const MAX_EVENTS$8 = 250;
19819
20481
  /**
19820
20482
  * Storage class for accumulating scheduled signal events per strategy.
19821
20483
  * Maintains a chronological list of scheduled and cancelled events.
19822
20484
  */
19823
- let ReportStorage$6 = class ReportStorage {
20485
+ let ReportStorage$7 = class ReportStorage {
19824
20486
  constructor(symbol, strategyName, exchangeName, frameName) {
19825
20487
  this.symbol = symbol;
19826
20488
  this.strategyName = strategyName;
@@ -19856,7 +20518,7 @@ let ReportStorage$6 = class ReportStorage {
19856
20518
  scheduledAt: data.signal.scheduledAt,
19857
20519
  });
19858
20520
  // Trim queue if exceeded MAX_EVENTS
19859
- if (this._eventList.length > MAX_EVENTS$7) {
20521
+ if (this._eventList.length > MAX_EVENTS$8) {
19860
20522
  this._eventList.pop();
19861
20523
  }
19862
20524
  }
@@ -19892,7 +20554,7 @@ let ReportStorage$6 = class ReportStorage {
19892
20554
  };
19893
20555
  this._eventList.unshift(newEvent);
19894
20556
  // Trim queue if exceeded MAX_EVENTS
19895
- if (this._eventList.length > MAX_EVENTS$7) {
20557
+ if (this._eventList.length > MAX_EVENTS$8) {
19896
20558
  this._eventList.pop();
19897
20559
  }
19898
20560
  }
@@ -19930,7 +20592,7 @@ let ReportStorage$6 = class ReportStorage {
19930
20592
  };
19931
20593
  this._eventList.unshift(newEvent);
19932
20594
  // Trim queue if exceeded MAX_EVENTS
19933
- if (this._eventList.length > MAX_EVENTS$7) {
20595
+ if (this._eventList.length > MAX_EVENTS$8) {
19934
20596
  this._eventList.pop();
19935
20597
  }
19936
20598
  }
@@ -20037,7 +20699,7 @@ let ReportStorage$6 = class ReportStorage {
20037
20699
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
20038
20700
  const markdown = await this.getReport(strategyName, columns);
20039
20701
  const timestamp = getContextTimestamp();
20040
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20702
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20041
20703
  await Markdown.writeData("schedule", markdown, {
20042
20704
  path,
20043
20705
  file: filename,
@@ -20078,7 +20740,7 @@ class ScheduleMarkdownService {
20078
20740
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20079
20741
  * Each combination gets its own isolated storage instance.
20080
20742
  */
20081
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
20743
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$7(symbol, strategyName, exchangeName, frameName));
20082
20744
  /**
20083
20745
  * Subscribes to signal emitter to receive scheduled signal events.
20084
20746
  * Protected against multiple subscriptions.
@@ -20281,7 +20943,7 @@ class ScheduleMarkdownService {
20281
20943
  payload,
20282
20944
  });
20283
20945
  if (payload) {
20284
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20946
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20285
20947
  this.getStorage.clear(key);
20286
20948
  }
20287
20949
  else {
@@ -20301,7 +20963,7 @@ class ScheduleMarkdownService {
20301
20963
  * @param backtest - Whether running in backtest mode
20302
20964
  * @returns Unique string key for memoization
20303
20965
  */
20304
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20966
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20305
20967
  const parts = [symbol, strategyName, exchangeName];
20306
20968
  if (frameName)
20307
20969
  parts.push(frameName);
@@ -20318,7 +20980,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
20318
20980
  * @param timestamp - Unix timestamp in milliseconds
20319
20981
  * @returns Filename string
20320
20982
  */
20321
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20983
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20322
20984
  const parts = [symbol, strategyName, exchangeName];
20323
20985
  if (frameName) {
20324
20986
  parts.push(frameName);
@@ -20338,7 +21000,7 @@ function percentile(sortedArray, p) {
20338
21000
  return sortedArray[Math.max(0, index)];
20339
21001
  }
20340
21002
  /** Maximum number of performance events to store per strategy */
20341
- const MAX_EVENTS$6 = 10000;
21003
+ const MAX_EVENTS$7 = 10000;
20342
21004
  /**
20343
21005
  * Storage class for accumulating performance metrics per strategy.
20344
21006
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -20360,7 +21022,7 @@ class PerformanceStorage {
20360
21022
  addEvent(event) {
20361
21023
  this._events.unshift(event);
20362
21024
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
20363
- if (this._events.length > MAX_EVENTS$6) {
21025
+ if (this._events.length > MAX_EVENTS$7) {
20364
21026
  this._events.pop();
20365
21027
  }
20366
21028
  }
@@ -20477,6 +21139,10 @@ class PerformanceStorage {
20477
21139
  return [
20478
21140
  `# Performance Report: ${strategyName}`,
20479
21141
  "",
21142
+ summaryTable,
21143
+ "",
21144
+ "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type.",
21145
+ "",
20480
21146
  `**Total events:** ${stats.totalEvents}`,
20481
21147
  `**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
20482
21148
  `**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
@@ -20485,11 +21151,6 @@ class PerformanceStorage {
20485
21151
  "",
20486
21152
  percentages.join("\n"),
20487
21153
  "",
20488
- "## Detailed Metrics",
20489
- "",
20490
- summaryTable,
20491
- "",
20492
- "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type."
20493
21154
  ].join("\n");
20494
21155
  }
20495
21156
  /**
@@ -20502,7 +21163,7 @@ class PerformanceStorage {
20502
21163
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
20503
21164
  const markdown = await this.getReport(strategyName, columns);
20504
21165
  const timestamp = getContextTimestamp();
20505
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21166
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20506
21167
  await Markdown.writeData("performance", markdown, {
20507
21168
  path,
20508
21169
  file: filename,
@@ -20549,7 +21210,7 @@ class PerformanceMarkdownService {
20549
21210
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20550
21211
  * Each combination gets its own isolated storage instance.
20551
21212
  */
20552
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
21213
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$f(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
20553
21214
  /**
20554
21215
  * Subscribes to performance emitter to receive performance events.
20555
21216
  * Protected against multiple subscriptions.
@@ -20716,7 +21377,7 @@ class PerformanceMarkdownService {
20716
21377
  payload,
20717
21378
  });
20718
21379
  if (payload) {
20719
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21380
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20720
21381
  this.getStorage.clear(key);
20721
21382
  }
20722
21383
  else {
@@ -20730,7 +21391,7 @@ class PerformanceMarkdownService {
20730
21391
  * Creates a filename for markdown report based on walker name.
20731
21392
  * Filename format: "walkerName-timestamp.md"
20732
21393
  */
20733
- const CREATE_FILE_NAME_FN$6 = (walkerName, timestamp) => {
21394
+ const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
20734
21395
  return `${walkerName}-${timestamp}.md`;
20735
21396
  };
20736
21397
  /**
@@ -20765,7 +21426,7 @@ function formatMetric(value) {
20765
21426
  * Storage class for accumulating walker results.
20766
21427
  * Maintains a list of all strategy results and provides methods to generate reports.
20767
21428
  */
20768
- let ReportStorage$5 = class ReportStorage {
21429
+ let ReportStorage$6 = class ReportStorage {
20769
21430
  constructor(walkerName) {
20770
21431
  this.walkerName = walkerName;
20771
21432
  /** Walker metadata (set from first addResult call) */
@@ -20953,7 +21614,7 @@ let ReportStorage$5 = class ReportStorage {
20953
21614
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
20954
21615
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
20955
21616
  const timestamp = getContextTimestamp();
20956
- const filename = CREATE_FILE_NAME_FN$6(this.walkerName, timestamp);
21617
+ const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
20957
21618
  await Markdown.writeData("walker", markdown, {
20958
21619
  path,
20959
21620
  file: filename,
@@ -20989,7 +21650,7 @@ class WalkerMarkdownService {
20989
21650
  * Memoized function to get or create ReportStorage for a walker.
20990
21651
  * Each walker gets its own isolated storage instance.
20991
21652
  */
20992
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$5(walkerName));
21653
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
20993
21654
  /**
20994
21655
  * Subscribes to walker emitter to receive walker progress events.
20995
21656
  * Protected against multiple subscriptions.
@@ -21186,7 +21847,7 @@ class WalkerMarkdownService {
21186
21847
  * @param backtest - Whether running in backtest mode
21187
21848
  * @returns Unique string key for memoization
21188
21849
  */
21189
- const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21850
+ const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
21190
21851
  const parts = [exchangeName];
21191
21852
  if (frameName)
21192
21853
  parts.push(frameName);
@@ -21197,7 +21858,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21197
21858
  * Creates a filename for markdown report based on memoization key components.
21198
21859
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
21199
21860
  */
21200
- const CREATE_FILE_NAME_FN$5 = (strategyName, exchangeName, frameName, timestamp) => {
21861
+ const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
21201
21862
  const parts = [strategyName, exchangeName];
21202
21863
  if (frameName) {
21203
21864
  parts.push(frameName);
@@ -21230,7 +21891,7 @@ function isUnsafe(value) {
21230
21891
  return false;
21231
21892
  }
21232
21893
  /** Maximum number of signals to store per symbol in heatmap reports */
21233
- const MAX_EVENTS$5 = 250;
21894
+ const MAX_EVENTS$6 = 250;
21234
21895
  /**
21235
21896
  * Storage class for accumulating closed signals per strategy and generating heatmap.
21236
21897
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -21256,7 +21917,7 @@ class HeatmapStorage {
21256
21917
  const signals = this.symbolData.get(symbol);
21257
21918
  signals.unshift(data);
21258
21919
  // Trim queue if exceeded MAX_EVENTS per symbol
21259
- if (signals.length > MAX_EVENTS$5) {
21920
+ if (signals.length > MAX_EVENTS$6) {
21260
21921
  signals.pop();
21261
21922
  }
21262
21923
  }
@@ -21507,7 +22168,7 @@ class HeatmapStorage {
21507
22168
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
21508
22169
  const markdown = await this.getReport(strategyName, columns);
21509
22170
  const timestamp = getContextTimestamp();
21510
- const filename = CREATE_FILE_NAME_FN$5(strategyName, this.exchangeName, this.frameName, timestamp);
22171
+ const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
21511
22172
  await Markdown.writeData("heat", markdown, {
21512
22173
  path,
21513
22174
  file: filename,
@@ -21553,7 +22214,7 @@ class HeatMarkdownService {
21553
22214
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21554
22215
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21555
22216
  */
21556
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
22217
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21557
22218
  /**
21558
22219
  * Subscribes to signal emitter to receive tick events.
21559
22220
  * Protected against multiple subscriptions.
@@ -21748,7 +22409,7 @@ class HeatMarkdownService {
21748
22409
  payload,
21749
22410
  });
21750
22411
  if (payload) {
21751
- const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
22412
+ const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
21752
22413
  this.getStorage.clear(key);
21753
22414
  }
21754
22415
  else {
@@ -22779,7 +23440,7 @@ class ClientPartial {
22779
23440
  * @param backtest - Whether running in backtest mode
22780
23441
  * @returns Unique string key for memoization
22781
23442
  */
22782
- const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23443
+ const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22783
23444
  /**
22784
23445
  * Creates a callback function for emitting profit events to partialProfitSubject.
22785
23446
  *
@@ -22901,7 +23562,7 @@ class PartialConnectionService {
22901
23562
  * Key format: "signalId:backtest" or "signalId:live"
22902
23563
  * Value: ClientPartial instance with logger and event emitters
22903
23564
  */
22904
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
23565
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
22905
23566
  return new ClientPartial({
22906
23567
  signalId,
22907
23568
  logger: this.loggerService,
@@ -22991,7 +23652,7 @@ class PartialConnectionService {
22991
23652
  const partial = this.getPartial(data.id, backtest);
22992
23653
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
22993
23654
  await partial.clear(symbol, data, priceClose, backtest);
22994
- const key = CREATE_KEY_FN$c(data.id, backtest);
23655
+ const key = CREATE_KEY_FN$d(data.id, backtest);
22995
23656
  this.getPartial.clear(key);
22996
23657
  };
22997
23658
  }
@@ -23007,7 +23668,7 @@ class PartialConnectionService {
23007
23668
  * @param backtest - Whether running in backtest mode
23008
23669
  * @returns Unique string key for memoization
23009
23670
  */
23010
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
23671
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
23011
23672
  const parts = [symbol, strategyName, exchangeName];
23012
23673
  if (frameName)
23013
23674
  parts.push(frameName);
@@ -23018,7 +23679,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
23018
23679
  * Creates a filename for markdown report based on memoization key components.
23019
23680
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
23020
23681
  */
23021
- const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23682
+ const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23022
23683
  const parts = [symbol, strategyName, exchangeName];
23023
23684
  if (frameName) {
23024
23685
  parts.push(frameName);
@@ -23029,12 +23690,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
23029
23690
  return `${parts.join("_")}-${timestamp}.md`;
23030
23691
  };
23031
23692
  /** Maximum number of events to store in partial reports */
23032
- const MAX_EVENTS$4 = 250;
23693
+ const MAX_EVENTS$5 = 250;
23033
23694
  /**
23034
23695
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
23035
23696
  * Maintains a chronological list of profit and loss level events.
23036
23697
  */
23037
- let ReportStorage$4 = class ReportStorage {
23698
+ let ReportStorage$5 = class ReportStorage {
23038
23699
  constructor(symbol, strategyName, exchangeName, frameName) {
23039
23700
  this.symbol = symbol;
23040
23701
  this.strategyName = strategyName;
@@ -23077,7 +23738,7 @@ let ReportStorage$4 = class ReportStorage {
23077
23738
  backtest,
23078
23739
  });
23079
23740
  // Trim queue if exceeded MAX_EVENTS
23080
- if (this._eventList.length > MAX_EVENTS$4) {
23741
+ if (this._eventList.length > MAX_EVENTS$5) {
23081
23742
  this._eventList.pop();
23082
23743
  }
23083
23744
  }
@@ -23115,7 +23776,7 @@ let ReportStorage$4 = class ReportStorage {
23115
23776
  backtest,
23116
23777
  });
23117
23778
  // Trim queue if exceeded MAX_EVENTS
23118
- if (this._eventList.length > MAX_EVENTS$4) {
23779
+ if (this._eventList.length > MAX_EVENTS$5) {
23119
23780
  this._eventList.pop();
23120
23781
  }
23121
23782
  }
@@ -23191,7 +23852,7 @@ let ReportStorage$4 = class ReportStorage {
23191
23852
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
23192
23853
  const markdown = await this.getReport(symbol, strategyName, columns);
23193
23854
  const timestamp = getContextTimestamp();
23194
- const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23855
+ const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23195
23856
  await Markdown.writeData("partial", markdown, {
23196
23857
  path,
23197
23858
  file: filename,
@@ -23232,7 +23893,7 @@ class PartialMarkdownService {
23232
23893
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23233
23894
  * Each combination gets its own isolated storage instance.
23234
23895
  */
23235
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
23896
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$c(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
23236
23897
  /**
23237
23898
  * Subscribes to partial profit/loss signal emitters to receive events.
23238
23899
  * Protected against multiple subscriptions.
@@ -23442,7 +24103,7 @@ class PartialMarkdownService {
23442
24103
  payload,
23443
24104
  });
23444
24105
  if (payload) {
23445
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24106
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23446
24107
  this.getStorage.clear(key);
23447
24108
  }
23448
24109
  else {
@@ -23458,7 +24119,7 @@ class PartialMarkdownService {
23458
24119
  * @param context - Context with strategyName, exchangeName, frameName
23459
24120
  * @returns Unique string key for memoization
23460
24121
  */
23461
- const CREATE_KEY_FN$a = (context) => {
24122
+ const CREATE_KEY_FN$b = (context) => {
23462
24123
  const parts = [context.strategyName, context.exchangeName];
23463
24124
  if (context.frameName)
23464
24125
  parts.push(context.frameName);
@@ -23532,7 +24193,7 @@ class PartialGlobalService {
23532
24193
  * @param context - Context with strategyName, exchangeName and frameName
23533
24194
  * @param methodName - Name of the calling method for error tracking
23534
24195
  */
23535
- this.validate = memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
24196
+ this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
23536
24197
  this.loggerService.log("partialGlobalService validate", {
23537
24198
  context,
23538
24199
  methodName,
@@ -23987,7 +24648,7 @@ class ClientBreakeven {
23987
24648
  * @param backtest - Whether running in backtest mode
23988
24649
  * @returns Unique string key for memoization
23989
24650
  */
23990
- const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24651
+ const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23991
24652
  /**
23992
24653
  * Creates a callback function for emitting breakeven events to breakevenSubject.
23993
24654
  *
@@ -24073,7 +24734,7 @@ class BreakevenConnectionService {
24073
24734
  * Key format: "signalId:backtest" or "signalId:live"
24074
24735
  * Value: ClientBreakeven instance with logger and event emitter
24075
24736
  */
24076
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
24737
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
24077
24738
  return new ClientBreakeven({
24078
24739
  signalId,
24079
24740
  logger: this.loggerService,
@@ -24134,7 +24795,7 @@ class BreakevenConnectionService {
24134
24795
  const breakeven = this.getBreakeven(data.id, backtest);
24135
24796
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24136
24797
  await breakeven.clear(symbol, data, priceClose, backtest);
24137
- const key = CREATE_KEY_FN$9(data.id, backtest);
24798
+ const key = CREATE_KEY_FN$a(data.id, backtest);
24138
24799
  this.getBreakeven.clear(key);
24139
24800
  };
24140
24801
  }
@@ -24150,7 +24811,7 @@ class BreakevenConnectionService {
24150
24811
  * @param backtest - Whether running in backtest mode
24151
24812
  * @returns Unique string key for memoization
24152
24813
  */
24153
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24814
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24154
24815
  const parts = [symbol, strategyName, exchangeName];
24155
24816
  if (frameName)
24156
24817
  parts.push(frameName);
@@ -24161,7 +24822,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
24161
24822
  * Creates a filename for markdown report based on memoization key components.
24162
24823
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24163
24824
  */
24164
- const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24825
+ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24165
24826
  const parts = [symbol, strategyName, exchangeName];
24166
24827
  if (frameName) {
24167
24828
  parts.push(frameName);
@@ -24172,12 +24833,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
24172
24833
  return `${parts.join("_")}-${timestamp}.md`;
24173
24834
  };
24174
24835
  /** Maximum number of events to store in breakeven reports */
24175
- const MAX_EVENTS$3 = 250;
24836
+ const MAX_EVENTS$4 = 250;
24176
24837
  /**
24177
24838
  * Storage class for accumulating breakeven events per symbol-strategy pair.
24178
24839
  * Maintains a chronological list of breakeven events.
24179
24840
  */
24180
- let ReportStorage$3 = class ReportStorage {
24841
+ let ReportStorage$4 = class ReportStorage {
24181
24842
  constructor(symbol, strategyName, exchangeName, frameName) {
24182
24843
  this.symbol = symbol;
24183
24844
  this.strategyName = strategyName;
@@ -24218,7 +24879,7 @@ let ReportStorage$3 = class ReportStorage {
24218
24879
  backtest,
24219
24880
  });
24220
24881
  // Trim queue if exceeded MAX_EVENTS
24221
- if (this._eventList.length > MAX_EVENTS$3) {
24882
+ if (this._eventList.length > MAX_EVENTS$4) {
24222
24883
  this._eventList.pop();
24223
24884
  }
24224
24885
  }
@@ -24286,7 +24947,7 @@ let ReportStorage$3 = class ReportStorage {
24286
24947
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
24287
24948
  const markdown = await this.getReport(symbol, strategyName, columns);
24288
24949
  const timestamp = getContextTimestamp();
24289
- const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24950
+ const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24290
24951
  await Markdown.writeData("breakeven", markdown, {
24291
24952
  path,
24292
24953
  file: filename,
@@ -24327,7 +24988,7 @@ class BreakevenMarkdownService {
24327
24988
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24328
24989
  * Each combination gets its own isolated storage instance.
24329
24990
  */
24330
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
24991
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$4(symbol, strategyName, exchangeName, frameName));
24331
24992
  /**
24332
24993
  * Subscribes to breakeven signal emitter to receive events.
24333
24994
  * Protected against multiple subscriptions.
@@ -24516,7 +25177,7 @@ class BreakevenMarkdownService {
24516
25177
  payload,
24517
25178
  });
24518
25179
  if (payload) {
24519
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25180
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24520
25181
  this.getStorage.clear(key);
24521
25182
  }
24522
25183
  else {
@@ -24532,7 +25193,7 @@ class BreakevenMarkdownService {
24532
25193
  * @param context - Context with strategyName, exchangeName, frameName
24533
25194
  * @returns Unique string key for memoization
24534
25195
  */
24535
- const CREATE_KEY_FN$7 = (context) => {
25196
+ const CREATE_KEY_FN$8 = (context) => {
24536
25197
  const parts = [context.strategyName, context.exchangeName];
24537
25198
  if (context.frameName)
24538
25199
  parts.push(context.frameName);
@@ -24606,7 +25267,7 @@ class BreakevenGlobalService {
24606
25267
  * @param context - Context with strategyName, exchangeName and frameName
24607
25268
  * @param methodName - Name of the calling method for error tracking
24608
25269
  */
24609
- this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
25270
+ this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
24610
25271
  this.loggerService.log("breakevenGlobalService validate", {
24611
25272
  context,
24612
25273
  methodName,
@@ -24826,7 +25487,7 @@ class ConfigValidationService {
24826
25487
  * @param backtest - Whether running in backtest mode
24827
25488
  * @returns Unique string key for memoization
24828
25489
  */
24829
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
25490
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24830
25491
  const parts = [symbol, strategyName, exchangeName];
24831
25492
  if (frameName)
24832
25493
  parts.push(frameName);
@@ -24837,7 +25498,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
24837
25498
  * Creates a filename for markdown report based on memoization key components.
24838
25499
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24839
25500
  */
24840
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
25501
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24841
25502
  const parts = [symbol, strategyName, exchangeName];
24842
25503
  if (frameName) {
24843
25504
  parts.push(frameName);
@@ -24848,12 +25509,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
24848
25509
  return `${parts.join("_")}-${timestamp}.md`;
24849
25510
  };
24850
25511
  /** Maximum number of events to store in risk reports */
24851
- const MAX_EVENTS$2 = 250;
25512
+ const MAX_EVENTS$3 = 250;
24852
25513
  /**
24853
25514
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
24854
25515
  * Maintains a chronological list of rejected signals due to risk limits.
24855
25516
  */
24856
- let ReportStorage$2 = class ReportStorage {
25517
+ let ReportStorage$3 = class ReportStorage {
24857
25518
  constructor(symbol, strategyName, exchangeName, frameName) {
24858
25519
  this.symbol = symbol;
24859
25520
  this.strategyName = strategyName;
@@ -24870,7 +25531,7 @@ let ReportStorage$2 = class ReportStorage {
24870
25531
  addRejectionEvent(event) {
24871
25532
  this._eventList.unshift(event);
24872
25533
  // Trim queue if exceeded MAX_EVENTS
24873
- if (this._eventList.length > MAX_EVENTS$2) {
25534
+ if (this._eventList.length > MAX_EVENTS$3) {
24874
25535
  this._eventList.pop();
24875
25536
  }
24876
25537
  }
@@ -24954,7 +25615,7 @@ let ReportStorage$2 = class ReportStorage {
24954
25615
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
24955
25616
  const markdown = await this.getReport(symbol, strategyName, columns);
24956
25617
  const timestamp = getContextTimestamp();
24957
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25618
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24958
25619
  await Markdown.writeData("risk", markdown, {
24959
25620
  path,
24960
25621
  file: filename,
@@ -24995,7 +25656,7 @@ class RiskMarkdownService {
24995
25656
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24996
25657
  * Each combination gets its own isolated storage instance.
24997
25658
  */
24998
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
25659
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
24999
25660
  /**
25000
25661
  * Subscribes to risk rejection emitter to receive rejection events.
25001
25662
  * Protected against multiple subscriptions.
@@ -25184,7 +25845,7 @@ class RiskMarkdownService {
25184
25845
  payload,
25185
25846
  });
25186
25847
  if (payload) {
25187
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25848
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25188
25849
  this.getStorage.clear(key);
25189
25850
  }
25190
25851
  else {
@@ -25482,6 +26143,7 @@ const WILDCARD_TARGET = {
25482
26143
  schedule: true,
25483
26144
  walker: true,
25484
26145
  sync: true,
26146
+ highest_profit: true,
25485
26147
  };
25486
26148
  /**
25487
26149
  * Utility class for managing report services.
@@ -25519,7 +26181,7 @@ class ReportUtils {
25519
26181
  *
25520
26182
  * @returns Cleanup function that unsubscribes from all enabled services
25521
26183
  */
25522
- this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, } = WILDCARD_TARGET) => {
26184
+ this.enable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, } = WILDCARD_TARGET) => {
25523
26185
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
25524
26186
  backtest: bt$1,
25525
26187
  breakeven,
@@ -25567,6 +26229,9 @@ class ReportUtils {
25567
26229
  if (sync) {
25568
26230
  unList.push(bt.syncReportService.subscribe());
25569
26231
  }
26232
+ if (highest_profit) {
26233
+ unList.push(bt.highestProfitReportService.subscribe());
26234
+ }
25570
26235
  return compose(...unList.map((un) => () => void un()));
25571
26236
  };
25572
26237
  /**
@@ -25605,7 +26270,7 @@ class ReportUtils {
25605
26270
  * Report.disable();
25606
26271
  * ```
25607
26272
  */
25608
- this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, } = WILDCARD_TARGET) => {
26273
+ this.disable = ({ backtest: bt$1 = false, breakeven = false, heat = false, live = false, partial = false, performance = false, risk = false, schedule = false, walker = false, strategy = false, sync = false, highest_profit = false, } = WILDCARD_TARGET) => {
25609
26274
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
25610
26275
  backtest: bt$1,
25611
26276
  breakeven,
@@ -25652,6 +26317,9 @@ class ReportUtils {
25652
26317
  if (sync) {
25653
26318
  bt.syncReportService.unsubscribe();
25654
26319
  }
26320
+ if (highest_profit) {
26321
+ bt.highestProfitReportService.unsubscribe();
26322
+ }
25655
26323
  };
25656
26324
  }
25657
26325
  }
@@ -27859,6 +28527,60 @@ class SyncReportService {
27859
28527
  }
27860
28528
  }
27861
28529
 
28530
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
28531
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
28532
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
28533
+ /**
28534
+ * Service for logging highest profit events to the JSONL report database.
28535
+ *
28536
+ * Listens to highestProfitSubject and writes each new price record to
28537
+ * Report.writeData() for persistence and analytics.
28538
+ */
28539
+ class HighestProfitReportService {
28540
+ constructor() {
28541
+ this.loggerService = inject(TYPES.loggerService);
28542
+ this.tick = async (data) => {
28543
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
28544
+ await Report.writeData("highest_profit", {
28545
+ timestamp: data.timestamp,
28546
+ symbol: data.symbol,
28547
+ strategyName: data.signal.strategyName,
28548
+ exchangeName: data.exchangeName,
28549
+ frameName: data.frameName,
28550
+ backtest: data.backtest,
28551
+ signalId: data.signal.id,
28552
+ position: data.signal.position,
28553
+ currentPrice: data.currentPrice,
28554
+ priceOpen: data.signal.priceOpen,
28555
+ priceTakeProfit: data.signal.priceTakeProfit,
28556
+ priceStopLoss: data.signal.priceStopLoss,
28557
+ }, {
28558
+ symbol: data.symbol,
28559
+ strategyName: data.signal.strategyName,
28560
+ exchangeName: data.exchangeName,
28561
+ frameName: data.frameName,
28562
+ signalId: data.signal.id,
28563
+ walkerName: "",
28564
+ });
28565
+ };
28566
+ this.subscribe = singleshot(() => {
28567
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
28568
+ const unsub = highestProfitSubject.subscribe(this.tick);
28569
+ return () => {
28570
+ this.subscribe.clear();
28571
+ unsub();
28572
+ };
28573
+ });
28574
+ this.unsubscribe = async () => {
28575
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
28576
+ if (this.subscribe.hasValue()) {
28577
+ const lastSubscription = this.subscribe();
28578
+ lastSubscription();
28579
+ }
28580
+ };
28581
+ }
28582
+ }
28583
+
27862
28584
  /**
27863
28585
  * Creates a unique key for memoizing ReportStorage instances.
27864
28586
  *
@@ -27872,7 +28594,7 @@ class SyncReportService {
27872
28594
  * @returns Colon-separated key string for memoization
27873
28595
  * @internal
27874
28596
  */
27875
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28597
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27876
28598
  const parts = [symbol, strategyName, exchangeName];
27877
28599
  if (frameName)
27878
28600
  parts.push(frameName);
@@ -27892,7 +28614,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
27892
28614
  * @returns Underscore-separated filename with .md extension
27893
28615
  * @internal
27894
28616
  */
27895
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28617
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
27896
28618
  const parts = [symbol, strategyName, exchangeName];
27897
28619
  if (frameName) {
27898
28620
  parts.push(frameName);
@@ -27907,7 +28629,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
27907
28629
  * Older events are discarded when this limit is exceeded.
27908
28630
  * @internal
27909
28631
  */
27910
- const MAX_EVENTS$1 = 250;
28632
+ const MAX_EVENTS$2 = 250;
27911
28633
  /**
27912
28634
  * In-memory storage for accumulating strategy events per symbol-strategy pair.
27913
28635
  *
@@ -27920,7 +28642,7 @@ const MAX_EVENTS$1 = 250;
27920
28642
  *
27921
28643
  * @internal
27922
28644
  */
27923
- let ReportStorage$1 = class ReportStorage {
28645
+ let ReportStorage$2 = class ReportStorage {
27924
28646
  /**
27925
28647
  * Creates a new ReportStorage instance.
27926
28648
  *
@@ -27946,7 +28668,7 @@ let ReportStorage$1 = class ReportStorage {
27946
28668
  */
27947
28669
  addEvent(event) {
27948
28670
  this._eventList.unshift(event);
27949
- if (this._eventList.length > MAX_EVENTS$1) {
28671
+ if (this._eventList.length > MAX_EVENTS$2) {
27950
28672
  this._eventList.pop();
27951
28673
  }
27952
28674
  }
@@ -28051,7 +28773,7 @@ let ReportStorage$1 = class ReportStorage {
28051
28773
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
28052
28774
  const markdown = await this.getReport(symbol, strategyName, columns);
28053
28775
  const timestamp = getContextTimestamp();
28054
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28776
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28055
28777
  await Markdown.writeData("strategy", markdown, {
28056
28778
  path,
28057
28779
  file: filename,
@@ -28120,7 +28842,7 @@ class StrategyMarkdownService {
28120
28842
  *
28121
28843
  * @internal
28122
28844
  */
28123
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
28845
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName));
28124
28846
  /**
28125
28847
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
28126
28848
  *
@@ -28688,7 +29410,7 @@ class StrategyMarkdownService {
28688
29410
  this.clear = async (payload) => {
28689
29411
  this.loggerService.log("strategyMarkdownService clear", { payload });
28690
29412
  if (payload) {
28691
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29413
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28692
29414
  this.getStorage.clear(key);
28693
29415
  }
28694
29416
  else {
@@ -28796,7 +29518,7 @@ class StrategyMarkdownService {
28796
29518
  * Creates a unique key for memoizing ReportStorage instances.
28797
29519
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28798
29520
  */
28799
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29521
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28800
29522
  const parts = [symbol, strategyName, exchangeName];
28801
29523
  if (frameName)
28802
29524
  parts.push(frameName);
@@ -28807,7 +29529,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
28807
29529
  * Creates a filename for the markdown report.
28808
29530
  * Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
28809
29531
  */
28810
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29532
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28811
29533
  const parts = [symbol, strategyName, exchangeName];
28812
29534
  if (frameName) {
28813
29535
  parts.push(frameName);
@@ -28818,12 +29540,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
28818
29540
  return `${parts.join("_")}-${timestamp}.md`;
28819
29541
  };
28820
29542
  /** Maximum number of events to store in sync reports */
28821
- const MAX_EVENTS = 250;
29543
+ const MAX_EVENTS$1 = 250;
28822
29544
  /**
28823
29545
  * Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
28824
29546
  * Maintains a chronological list of signal-open and signal-close events.
28825
29547
  */
28826
- class ReportStorage {
29548
+ let ReportStorage$1 = class ReportStorage {
28827
29549
  constructor(symbol, strategyName, exchangeName, frameName) {
28828
29550
  this.symbol = symbol;
28829
29551
  this.strategyName = strategyName;
@@ -28833,7 +29555,7 @@ class ReportStorage {
28833
29555
  }
28834
29556
  addEvent(event) {
28835
29557
  this._eventList.unshift(event);
28836
- if (this._eventList.length > MAX_EVENTS) {
29558
+ if (this._eventList.length > MAX_EVENTS$1) {
28837
29559
  this._eventList.pop();
28838
29560
  }
28839
29561
  }
@@ -28894,7 +29616,7 @@ class ReportStorage {
28894
29616
  async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
28895
29617
  const markdown = await this.getReport(symbol, strategyName, columns);
28896
29618
  const timestamp = getContextTimestamp();
28897
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29619
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28898
29620
  await Markdown.writeData("sync", markdown, {
28899
29621
  path,
28900
29622
  file: filename,
@@ -28905,7 +29627,7 @@ class ReportStorage {
28905
29627
  frameName: this.frameName,
28906
29628
  });
28907
29629
  }
28908
- }
29630
+ };
28909
29631
  /**
28910
29632
  * Service for generating and saving signal sync markdown reports.
28911
29633
  *
@@ -28928,7 +29650,7 @@ class ReportStorage {
28928
29650
  class SyncMarkdownService {
28929
29651
  constructor() {
28930
29652
  this.loggerService = inject(TYPES.loggerService);
28931
- this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
29653
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
28932
29654
  this.subscribe = singleshot(() => {
28933
29655
  this.loggerService.log("syncMarkdownService init");
28934
29656
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -29002,6 +29724,190 @@ class SyncMarkdownService {
29002
29724
  };
29003
29725
  this.clear = async (payload) => {
29004
29726
  this.loggerService.log("syncMarkdownService clear", { payload });
29727
+ if (payload) {
29728
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29729
+ this.getStorage.clear(key);
29730
+ }
29731
+ else {
29732
+ this.getStorage.clear();
29733
+ }
29734
+ };
29735
+ }
29736
+ }
29737
+
29738
+ /**
29739
+ * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
29740
+ */
29741
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29742
+ const parts = [symbol, strategyName, exchangeName];
29743
+ if (frameName)
29744
+ parts.push(frameName);
29745
+ parts.push(backtest ? "backtest" : "live");
29746
+ return parts.join(":");
29747
+ };
29748
+ /**
29749
+ * Creates a filename for the markdown report.
29750
+ */
29751
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29752
+ const parts = [symbol, strategyName, exchangeName];
29753
+ if (frameName) {
29754
+ parts.push(frameName);
29755
+ parts.push("backtest");
29756
+ }
29757
+ else
29758
+ parts.push("live");
29759
+ return `${parts.join("_")}-${timestamp}.md`;
29760
+ };
29761
+ /** Maximum number of events to store per combination */
29762
+ const MAX_EVENTS = 250;
29763
+ /**
29764
+ * Accumulates highest profit events per symbol-strategy-exchange-frame combination.
29765
+ */
29766
+ class ReportStorage {
29767
+ constructor(symbol, strategyName, exchangeName, frameName) {
29768
+ this.symbol = symbol;
29769
+ this.strategyName = strategyName;
29770
+ this.exchangeName = exchangeName;
29771
+ this.frameName = frameName;
29772
+ this._eventList = [];
29773
+ }
29774
+ /**
29775
+ * Adds a highest profit event to the storage.
29776
+ */
29777
+ addEvent(data, currentPrice, backtest, timestamp) {
29778
+ this._eventList.unshift({
29779
+ timestamp,
29780
+ symbol: data.symbol,
29781
+ strategyName: data.strategyName,
29782
+ signalId: data.id,
29783
+ position: data.position,
29784
+ pnl: data.pnl,
29785
+ currentPrice,
29786
+ priceOpen: data.priceOpen,
29787
+ priceTakeProfit: data.priceTakeProfit,
29788
+ priceStopLoss: data.priceStopLoss,
29789
+ backtest,
29790
+ });
29791
+ if (this._eventList.length > MAX_EVENTS) {
29792
+ this._eventList.pop();
29793
+ }
29794
+ }
29795
+ /**
29796
+ * Returns aggregated statistics from accumulated events.
29797
+ */
29798
+ async getData() {
29799
+ return {
29800
+ eventList: this._eventList,
29801
+ totalEvents: this._eventList.length,
29802
+ };
29803
+ }
29804
+ /**
29805
+ * Generates a markdown report table for this storage.
29806
+ */
29807
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
29808
+ const stats = await this.getData();
29809
+ if (stats.totalEvents === 0) {
29810
+ return [
29811
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29812
+ "",
29813
+ "No highest profit events recorded yet.",
29814
+ ].join("\n");
29815
+ }
29816
+ const visibleColumns = [];
29817
+ for (const col of columns) {
29818
+ if (await col.isVisible()) {
29819
+ visibleColumns.push(col);
29820
+ }
29821
+ }
29822
+ const header = visibleColumns.map((col) => col.label);
29823
+ const separator = visibleColumns.map(() => "---");
29824
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
29825
+ const tableData = [header, separator, ...rows];
29826
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
29827
+ return [
29828
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29829
+ "",
29830
+ table,
29831
+ "",
29832
+ `**Total events:** ${stats.totalEvents}`,
29833
+ ].join("\n");
29834
+ }
29835
+ /**
29836
+ * Saves the report to disk.
29837
+ */
29838
+ async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
29839
+ const markdown = await this.getReport(symbol, strategyName, columns);
29840
+ const timestamp = getContextTimestamp();
29841
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29842
+ await Markdown.writeData("highest_profit", markdown, {
29843
+ path,
29844
+ file: filename,
29845
+ symbol: this.symbol,
29846
+ signalId: "",
29847
+ strategyName: this.strategyName,
29848
+ exchangeName: this.exchangeName,
29849
+ frameName: this.frameName,
29850
+ });
29851
+ }
29852
+ }
29853
+ /**
29854
+ * Service for generating and saving highest profit markdown reports.
29855
+ *
29856
+ * Listens to highestProfitSubject and accumulates events per
29857
+ * symbol-strategy-exchange-frame combination. Provides getData(),
29858
+ * getReport(), and dump() methods matching the Partial pattern.
29859
+ */
29860
+ class HighestProfitMarkdownService {
29861
+ constructor() {
29862
+ this.loggerService = inject(TYPES.loggerService);
29863
+ this.getStorage = memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$4(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
29864
+ this.subscribe = singleshot(() => {
29865
+ this.loggerService.log("highestProfitMarkdownService init");
29866
+ const unsub = highestProfitSubject.subscribe(this.tick);
29867
+ return () => {
29868
+ this.subscribe.clear();
29869
+ this.clear();
29870
+ unsub();
29871
+ };
29872
+ });
29873
+ this.unsubscribe = async () => {
29874
+ this.loggerService.log("highestProfitMarkdownService unsubscribe");
29875
+ if (this.subscribe.hasValue()) {
29876
+ const lastSubscription = this.subscribe();
29877
+ lastSubscription();
29878
+ }
29879
+ };
29880
+ this.tick = async (data) => {
29881
+ this.loggerService.log("highestProfitMarkdownService tick", { data });
29882
+ const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
29883
+ storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
29884
+ };
29885
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
29886
+ this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
29887
+ if (!this.subscribe.hasValue()) {
29888
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
29889
+ }
29890
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29891
+ return storage.getData();
29892
+ };
29893
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
29894
+ this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
29895
+ if (!this.subscribe.hasValue()) {
29896
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
29897
+ }
29898
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29899
+ return storage.getReport(symbol, strategyName, columns);
29900
+ };
29901
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
29902
+ this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
29903
+ if (!this.subscribe.hasValue()) {
29904
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
29905
+ }
29906
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29907
+ await storage.dump(symbol, strategyName, path, columns);
29908
+ };
29909
+ this.clear = async (payload) => {
29910
+ this.loggerService.log("highestProfitMarkdownService clear", { payload });
29005
29911
  if (payload) {
29006
29912
  const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29007
29913
  this.getStorage.clear(key);
@@ -29351,6 +30257,7 @@ class TimeMetaService {
29351
30257
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
29352
30258
  provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
29353
30259
  provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
30260
+ provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
29354
30261
  }
29355
30262
  {
29356
30263
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -29364,6 +30271,7 @@ class TimeMetaService {
29364
30271
  provide(TYPES.riskReportService, () => new RiskReportService());
29365
30272
  provide(TYPES.strategyReportService, () => new StrategyReportService());
29366
30273
  provide(TYPES.syncReportService, () => new SyncReportService());
30274
+ provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
29367
30275
  }
29368
30276
  {
29369
30277
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -29446,6 +30354,7 @@ const markdownServices = {
29446
30354
  riskMarkdownService: inject(TYPES.riskMarkdownService),
29447
30355
  strategyMarkdownService: inject(TYPES.strategyMarkdownService),
29448
30356
  syncMarkdownService: inject(TYPES.syncMarkdownService),
30357
+ highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
29449
30358
  };
29450
30359
  const reportServices = {
29451
30360
  backtestReportService: inject(TYPES.backtestReportService),
@@ -29459,6 +30368,7 @@ const reportServices = {
29459
30368
  riskReportService: inject(TYPES.riskReportService),
29460
30369
  strategyReportService: inject(TYPES.strategyReportService),
29461
30370
  syncReportService: inject(TYPES.syncReportService),
30371
+ highestProfitReportService: inject(TYPES.highestProfitReportService),
29462
30372
  };
29463
30373
  const validationServices = {
29464
30374
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -31230,7 +32140,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
31230
32140
  *
31231
32141
  * @param newStopLossPrice - Desired absolute stop-loss price
31232
32142
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31233
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32143
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31234
32144
  * @returns percentShift to pass to `commitTrailingStop`
31235
32145
  *
31236
32146
  * @example
@@ -31252,7 +32162,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
31252
32162
  *
31253
32163
  * @param newTakeProfitPrice - Desired absolute take-profit price
31254
32164
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31255
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32165
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31256
32166
  * @returns percentShift to pass to `commitTrailingTake`
31257
32167
  *
31258
32168
  * @example
@@ -31277,7 +32187,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
31277
32187
  *
31278
32188
  * @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
31279
32189
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31280
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32190
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31281
32191
  * @param position - Position direction: "long" or "short"
31282
32192
  * @returns Absolute stop-loss price corresponding to the given percentShift
31283
32193
  *
@@ -31304,7 +32214,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
31304
32214
  *
31305
32215
  * @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
31306
32216
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31307
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32217
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31308
32218
  * @param position - Position direction: "long" or "short"
31309
32219
  * @returns Absolute take-profit price corresponding to the given percentShift
31310
32220
  *
@@ -31343,7 +32253,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
31343
32253
  * Breakeven moves the SL to the effective entry price (effectivePriceOpen).
31344
32254
  * The value is the same regardless of position direction.
31345
32255
  *
31346
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32256
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31347
32257
  * @returns New stop-loss price equal to the effective entry price
31348
32258
  *
31349
32259
  * @example
@@ -32265,13 +33175,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
32265
33175
  const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
32266
33176
  const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
32267
33177
  const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
32268
- const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
33178
+ const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
32269
33179
  const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
32270
33180
  const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
32271
33181
  const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
32272
33182
  const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
32273
33183
  const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
32274
33184
  const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
33185
+ const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
33186
+ const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
33187
+ const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
33188
+ const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
33189
+ const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
33190
+ const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
33191
+ const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
33192
+ const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
33193
+ const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
32275
33194
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
32276
33195
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
32277
33196
  /**
@@ -32538,7 +33457,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
32538
33457
  if (!signal) {
32539
33458
  return false;
32540
33459
  }
32541
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33460
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32542
33461
  if (effectivePriceOpen === null) {
32543
33462
  return false;
32544
33463
  }
@@ -32618,7 +33537,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
32618
33537
  if (!signal) {
32619
33538
  return false;
32620
33539
  }
32621
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33540
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32622
33541
  if (effectivePriceOpen === null) {
32623
33542
  return false;
32624
33543
  }
@@ -32668,7 +33587,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
32668
33587
  if (!signal) {
32669
33588
  return false;
32670
33589
  }
32671
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33590
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32672
33591
  if (effectivePriceOpen === null) {
32673
33592
  return false;
32674
33593
  }
@@ -32719,7 +33638,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
32719
33638
  if (!signal) {
32720
33639
  return false;
32721
33640
  }
32722
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33641
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32723
33642
  if (effectivePriceOpen === null) {
32724
33643
  return false;
32725
33644
  }
@@ -32781,7 +33700,7 @@ async function commitBreakeven(symbol) {
32781
33700
  if (!signal) {
32782
33701
  return false;
32783
33702
  }
32784
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33703
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32785
33704
  if (effectivePriceOpen === null) {
32786
33705
  return false;
32787
33706
  }
@@ -33077,26 +33996,26 @@ async function getBreakeven(symbol, currentPrice) {
33077
33996
  *
33078
33997
  * @example
33079
33998
  * ```typescript
33080
- * import { getPositionAveragePrice } from "backtest-kit";
33999
+ * import { getPositionEffectivePrice } from "backtest-kit";
33081
34000
  *
33082
- * const avgPrice = await getPositionAveragePrice("BTCUSDT");
34001
+ * const avgPrice = await getPositionEffectivePrice("BTCUSDT");
33083
34002
  * // No DCA: avgPrice === priceOpen
33084
34003
  * // After DCA at lower price: avgPrice < priceOpen
33085
34004
  * ```
33086
34005
  */
33087
- async function getPositionAveragePrice(symbol) {
34006
+ async function getPositionEffectivePrice(symbol) {
33088
34007
  bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
33089
34008
  symbol,
33090
34009
  });
33091
34010
  if (!ExecutionContextService.hasContext()) {
33092
- throw new Error("getPositionAveragePrice requires an execution context");
34011
+ throw new Error("getPositionEffectivePrice requires an execution context");
33093
34012
  }
33094
34013
  if (!MethodContextService.hasContext()) {
33095
- throw new Error("getPositionAveragePrice requires a method context");
34014
+ throw new Error("getPositionEffectivePrice requires a method context");
33096
34015
  }
33097
34016
  const { backtest: isBacktest } = bt.executionContextService.context;
33098
34017
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33099
- return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34018
+ return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33100
34019
  }
33101
34020
  /**
33102
34021
  * Returns the number of DCA entries made for the current pending signal.
@@ -33451,6 +34370,284 @@ async function getPositionPartials(symbol) {
33451
34370
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33452
34371
  return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
33453
34372
  }
34373
+ /**
34374
+ * Returns the list of DCA entry prices and costs for the current pending signal.
34375
+ *
34376
+ * Each element represents a single position entry — the initial open or a subsequent
34377
+ * DCA entry added via commitAverageBuy.
34378
+ *
34379
+ * Returns null if no pending signal exists.
34380
+ * Returns a single-element array if no DCA entries were made.
34381
+ *
34382
+ * Each entry contains:
34383
+ * - `price` — execution price of this entry
34384
+ * - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
34385
+ *
34386
+ * @param symbol - Trading pair symbol
34387
+ * @returns Promise resolving to array of entry records or null
34388
+ *
34389
+ * @example
34390
+ * ```typescript
34391
+ * import { getPositionEntries } from "backtest-kit";
34392
+ *
34393
+ * const entries = await getPositionEntries("BTCUSDT");
34394
+ * // No DCA: [{ price: 43000, cost: 100 }]
34395
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
34396
+ * ```
34397
+ */
34398
+ async function getPositionEntries(symbol) {
34399
+ bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
34400
+ if (!ExecutionContextService.hasContext()) {
34401
+ throw new Error("getPositionEntries requires an execution context");
34402
+ }
34403
+ if (!MethodContextService.hasContext()) {
34404
+ throw new Error("getPositionEntries requires a method context");
34405
+ }
34406
+ const { backtest: isBacktest } = bt.executionContextService.context;
34407
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34408
+ return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
34409
+ }
34410
+ /**
34411
+ * Returns the original estimated duration for the current pending signal.
34412
+ *
34413
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
34414
+ * number of minutes the position is expected to be active before `time_expired`.
34415
+ *
34416
+ * Returns null if no pending signal exists.
34417
+ *
34418
+ * @param symbol - Trading pair symbol
34419
+ * @returns Promise resolving to estimated duration in minutes or null
34420
+ *
34421
+ * @example
34422
+ * ```typescript
34423
+ * import { getPositionEstimateMinutes } from "backtest-kit";
34424
+ *
34425
+ * const estimate = await getPositionEstimateMinutes("BTCUSDT");
34426
+ * // e.g. 120 (2 hours)
34427
+ * ```
34428
+ */
34429
+ async function getPositionEstimateMinutes(symbol) {
34430
+ bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
34431
+ if (!ExecutionContextService.hasContext()) {
34432
+ throw new Error("getPositionEstimateMinutes requires an execution context");
34433
+ }
34434
+ if (!MethodContextService.hasContext()) {
34435
+ throw new Error("getPositionEstimateMinutes requires a method context");
34436
+ }
34437
+ const { backtest: isBacktest } = bt.executionContextService.context;
34438
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34439
+ return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34440
+ }
34441
+ /**
34442
+ * Returns the remaining time before the position expires, clamped to zero.
34443
+ *
34444
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
34445
+ * Returns 0 once the estimate is exceeded (never negative).
34446
+ *
34447
+ * Returns null if no pending signal exists.
34448
+ *
34449
+ * @param symbol - Trading pair symbol
34450
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
34451
+ *
34452
+ * @example
34453
+ * ```typescript
34454
+ * import { getPositionCountdownMinutes } from "backtest-kit";
34455
+ *
34456
+ * const remaining = await getPositionCountdownMinutes("BTCUSDT");
34457
+ * // e.g. 45 (45 minutes left)
34458
+ * // 0 when expired
34459
+ * ```
34460
+ */
34461
+ async function getPositionCountdownMinutes(symbol) {
34462
+ bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
34463
+ if (!ExecutionContextService.hasContext()) {
34464
+ throw new Error("getPositionCountdownMinutes requires an execution context");
34465
+ }
34466
+ if (!MethodContextService.hasContext()) {
34467
+ throw new Error("getPositionCountdownMinutes requires a method context");
34468
+ }
34469
+ const { backtest: isBacktest } = bt.executionContextService.context;
34470
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34471
+ return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34472
+ }
34473
+ /**
34474
+ * Returns the best price reached in the profit direction during this position's life.
34475
+ *
34476
+ * Initialized at position open with the entry price and timestamp.
34477
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
34478
+ * - LONG: tracks the highest price seen above effective entry
34479
+ * - SHORT: tracks the lowest price seen below effective entry
34480
+ *
34481
+ * Returns null if no pending signal exists.
34482
+ * Never returns null when a signal is active — always contains at least the entry price.
34483
+ *
34484
+ * @param symbol - Trading pair symbol
34485
+ * @returns Promise resolving to `{ price, timestamp }` record or null
34486
+ *
34487
+ * @example
34488
+ * ```typescript
34489
+ * import { getPositionHighestProfitPrice } from "backtest-kit";
34490
+ *
34491
+ * const peak = await getPositionHighestProfitPrice("BTCUSDT");
34492
+ * // e.g. { price: 44500, timestamp: 1700000000000 }
34493
+ * ```
34494
+ */
34495
+ async function getPositionHighestProfitPrice(symbol) {
34496
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
34497
+ if (!ExecutionContextService.hasContext()) {
34498
+ throw new Error("getPositionHighestProfitPrice requires an execution context");
34499
+ }
34500
+ if (!MethodContextService.hasContext()) {
34501
+ throw new Error("getPositionHighestProfitPrice requires a method context");
34502
+ }
34503
+ const { backtest: isBacktest } = bt.executionContextService.context;
34504
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34505
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34506
+ }
34507
+ /**
34508
+ * Returns the timestamp when the best profit price was recorded during this position's life.
34509
+ *
34510
+ * Returns null if no pending signal exists.
34511
+ *
34512
+ * @param symbol - Trading pair symbol
34513
+ * @returns Promise resolving to timestamp in milliseconds or null
34514
+ *
34515
+ * @example
34516
+ * ```typescript
34517
+ * import { getPositionHighestProfitTimestamp } from "backtest-kit";
34518
+ *
34519
+ * const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
34520
+ * // e.g. 1700000000000
34521
+ * ```
34522
+ */
34523
+ async function getPositionHighestProfitTimestamp(symbol) {
34524
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
34525
+ if (!ExecutionContextService.hasContext()) {
34526
+ throw new Error("getPositionHighestProfitTimestamp requires an execution context");
34527
+ }
34528
+ if (!MethodContextService.hasContext()) {
34529
+ throw new Error("getPositionHighestProfitTimestamp requires a method context");
34530
+ }
34531
+ const { backtest: isBacktest } = bt.executionContextService.context;
34532
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34533
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
34534
+ }
34535
+ /**
34536
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
34537
+ *
34538
+ * Returns null if no pending signal exists.
34539
+ *
34540
+ * @param symbol - Trading pair symbol
34541
+ * @returns Promise resolving to PnL percentage or null
34542
+ *
34543
+ * @example
34544
+ * ```typescript
34545
+ * import { getPositionHighestPnlPercentage } from "backtest-kit";
34546
+ *
34547
+ * const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
34548
+ * // e.g. 3.5
34549
+ * ```
34550
+ */
34551
+ async function getPositionHighestPnlPercentage(symbol) {
34552
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
34553
+ if (!ExecutionContextService.hasContext()) {
34554
+ throw new Error("getPositionHighestPnlPercentage requires an execution context");
34555
+ }
34556
+ if (!MethodContextService.hasContext()) {
34557
+ throw new Error("getPositionHighestPnlPercentage requires a method context");
34558
+ }
34559
+ const { backtest: isBacktest } = bt.executionContextService.context;
34560
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34561
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
34562
+ }
34563
+ /**
34564
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
34565
+ *
34566
+ * Returns null if no pending signal exists.
34567
+ *
34568
+ * @param symbol - Trading pair symbol
34569
+ * @returns Promise resolving to PnL cost or null
34570
+ *
34571
+ * @example
34572
+ * ```typescript
34573
+ * import { getPositionHighestPnlCost } from "backtest-kit";
34574
+ *
34575
+ * const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
34576
+ * // e.g. 35.5
34577
+ * ```
34578
+ */
34579
+ async function getPositionHighestPnlCost(symbol) {
34580
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
34581
+ if (!ExecutionContextService.hasContext()) {
34582
+ throw new Error("getPositionHighestPnlCost requires an execution context");
34583
+ }
34584
+ if (!MethodContextService.hasContext()) {
34585
+ throw new Error("getPositionHighestPnlCost requires a method context");
34586
+ }
34587
+ const { backtest: isBacktest } = bt.executionContextService.context;
34588
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34589
+ return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
34590
+ }
34591
+ /**
34592
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
34593
+ *
34594
+ * Returns null if no pending signal exists.
34595
+ *
34596
+ * @param symbol - Trading pair symbol
34597
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
34598
+ *
34599
+ * @example
34600
+ * ```typescript
34601
+ * import { getPositionHighestProfitBreakeven } from "backtest-kit";
34602
+ *
34603
+ * const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
34604
+ * // e.g. true (price reached the breakeven threshold at its peak)
34605
+ * ```
34606
+ */
34607
+ async function getPositionHighestProfitBreakeven(symbol) {
34608
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
34609
+ if (!ExecutionContextService.hasContext()) {
34610
+ throw new Error("getPositionHighestProfitBreakeven requires an execution context");
34611
+ }
34612
+ if (!MethodContextService.hasContext()) {
34613
+ throw new Error("getPositionHighestProfitBreakeven requires a method context");
34614
+ }
34615
+ const { backtest: isBacktest } = bt.executionContextService.context;
34616
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34617
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
34618
+ }
34619
+ /**
34620
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
34621
+ *
34622
+ * Measures how long the position has been pulling back from its peak profit level.
34623
+ * Zero when called at the exact moment the peak was set.
34624
+ * Grows continuously as price moves away from the peak without setting a new record.
34625
+ *
34626
+ * Returns null if no pending signal exists.
34627
+ *
34628
+ * @param symbol - Trading pair symbol
34629
+ * @returns Promise resolving to drawdown duration in minutes or null
34630
+ *
34631
+ * @example
34632
+ * ```typescript
34633
+ * import { getPositionDrawdownMinutes } from "backtest-kit";
34634
+ *
34635
+ * const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
34636
+ * // e.g. 30 (30 minutes since the highest profit price)
34637
+ * ```
34638
+ */
34639
+ async function getPositionDrawdownMinutes(symbol) {
34640
+ bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
34641
+ if (!ExecutionContextService.hasContext()) {
34642
+ throw new Error("getPositionDrawdownMinutes requires an execution context");
34643
+ }
34644
+ if (!MethodContextService.hasContext()) {
34645
+ throw new Error("getPositionDrawdownMinutes requires a method context");
34646
+ }
34647
+ const { backtest: isBacktest } = bt.executionContextService.context;
34648
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34649
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34650
+ }
33454
34651
  /**
33455
34652
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
33456
34653
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -33628,6 +34825,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
33628
34825
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
33629
34826
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
33630
34827
  const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
34828
+ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
34829
+ const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
33631
34830
  /**
33632
34831
  * Subscribes to all signal events with queued async processing.
33633
34832
  *
@@ -34754,6 +35953,33 @@ function listenSyncOnce(filterFn, fn) {
34754
35953
  bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
34755
35954
  return syncSubject.filter(filterFn).once(fn);
34756
35955
  }
35956
+ /**
35957
+ * Subscribes to highest profit events with queued async processing.
35958
+ * Emits when a signal reaches a new highest profit level during its lifecycle.
35959
+ * Events are processed sequentially in order received, even if callback is async.
35960
+ * Uses queued wrapper to prevent concurrent execution of the callback.
35961
+ * Useful for tracking profit milestones and implementing dynamic management logic.
35962
+ *
35963
+ * @param fn - Callback function to handle highest profit events
35964
+ * @return Unsubscribe function to stop listening to events
35965
+ */
35966
+ function listenHighestProfit(fn) {
35967
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
35968
+ return highestProfitSubject.subscribe(queued(async (event) => fn(event)));
35969
+ }
35970
+ /**
35971
+ * Subscribes to filtered highest profit events with one-time execution.
35972
+ * Listens for events matching the filter predicate, then executes callback once
35973
+ * and automatically unsubscribes. Useful for waiting for specific profit conditions.
35974
+ *
35975
+ * @param filterFn - Predicate to filter which events trigger the callback
35976
+ * @param fn - Callback function to handle the filtered event (called only once)
35977
+ * @returns Unsubscribe function to cancel the listener before it fires
35978
+ */
35979
+ function listenHighestProfitOnce(filterFn, fn) {
35980
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
35981
+ return highestProfitSubject.filter(filterFn).once(fn);
35982
+ }
34757
35983
 
34758
35984
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
34759
35985
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -34767,7 +35993,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
34767
35993
  const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
34768
35994
  const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
34769
35995
  const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
34770
- const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
35996
+ const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
34771
35997
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
34772
35998
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
34773
35999
  const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
@@ -34775,6 +36001,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
34775
36001
  const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
34776
36002
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
34777
36003
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
36004
+ const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
36005
+ const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
36006
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
36007
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
36008
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
36009
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
36010
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
36011
+ const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
34778
36012
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
34779
36013
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
34780
36014
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -35347,7 +36581,7 @@ class BacktestUtils {
35347
36581
  * @param context - Execution context with strategyName, exchangeName, and frameName
35348
36582
  * @returns Effective entry price, or null if no active position
35349
36583
  */
35350
- this.getPositionAveragePrice = async (symbol, context) => {
36584
+ this.getPositionEffectivePrice = async (symbol, context) => {
35351
36585
  bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
35352
36586
  symbol,
35353
36587
  context,
@@ -35363,7 +36597,7 @@ class BacktestUtils {
35363
36597
  actions &&
35364
36598
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
35365
36599
  }
35366
- return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
36600
+ return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
35367
36601
  };
35368
36602
  /**
35369
36603
  * Returns the total number of base-asset units currently held in the position.
@@ -35581,6 +36815,230 @@ class BacktestUtils {
35581
36815
  }
35582
36816
  return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
35583
36817
  };
36818
+ /**
36819
+ * Returns the original estimated duration for the current pending signal.
36820
+ *
36821
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
36822
+ * number of minutes the position is expected to be active before `time_expired`.
36823
+ *
36824
+ * Returns null if no pending signal exists.
36825
+ *
36826
+ * @param symbol - Trading pair symbol
36827
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36828
+ * @returns Estimated duration in minutes, or null if no active position
36829
+ */
36830
+ this.getPositionEstimateMinutes = async (symbol, context) => {
36831
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
36832
+ symbol,
36833
+ context,
36834
+ });
36835
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36836
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36837
+ {
36838
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36839
+ riskName &&
36840
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36841
+ riskList &&
36842
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36843
+ actions &&
36844
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36845
+ }
36846
+ return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
36847
+ };
36848
+ /**
36849
+ * Returns the remaining time before the position expires, clamped to zero.
36850
+ *
36851
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
36852
+ * Returns 0 once the estimate is exceeded (never negative).
36853
+ *
36854
+ * Returns null if no pending signal exists.
36855
+ *
36856
+ * @param symbol - Trading pair symbol
36857
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36858
+ * @returns Remaining minutes (≥ 0), or null if no active position
36859
+ */
36860
+ this.getPositionCountdownMinutes = async (symbol, context) => {
36861
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
36862
+ symbol,
36863
+ context,
36864
+ });
36865
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36866
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36867
+ {
36868
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36869
+ riskName &&
36870
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36871
+ riskList &&
36872
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36873
+ actions &&
36874
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36875
+ }
36876
+ return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
36877
+ };
36878
+ /**
36879
+ * Returns the best price reached in the profit direction during this position's life.
36880
+ *
36881
+ * Returns null if no pending signal exists.
36882
+ *
36883
+ * @param symbol - Trading pair symbol
36884
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36885
+ * @returns price or null if no active position
36886
+ */
36887
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
36888
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
36889
+ symbol,
36890
+ context,
36891
+ });
36892
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36893
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36894
+ {
36895
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36896
+ riskName &&
36897
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36898
+ riskList &&
36899
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36900
+ actions &&
36901
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36902
+ }
36903
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
36904
+ };
36905
+ /**
36906
+ * Returns the timestamp when the best profit price was recorded during this position's life.
36907
+ *
36908
+ * Returns null if no pending signal exists.
36909
+ *
36910
+ * @param symbol - Trading pair symbol
36911
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36912
+ * @returns timestamp in milliseconds or null if no active position
36913
+ */
36914
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
36915
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
36916
+ symbol,
36917
+ context,
36918
+ });
36919
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36920
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36921
+ {
36922
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36923
+ riskName &&
36924
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36925
+ riskList &&
36926
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36927
+ actions &&
36928
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36929
+ }
36930
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
36931
+ };
36932
+ /**
36933
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
36934
+ *
36935
+ * Returns null if no pending signal exists.
36936
+ *
36937
+ * @param symbol - Trading pair symbol
36938
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36939
+ * @returns PnL percentage or null if no active position
36940
+ */
36941
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
36942
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
36943
+ symbol,
36944
+ context,
36945
+ });
36946
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36947
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36948
+ {
36949
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36950
+ riskName &&
36951
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36952
+ riskList &&
36953
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36954
+ actions &&
36955
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36956
+ }
36957
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
36958
+ };
36959
+ /**
36960
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
36961
+ *
36962
+ * Returns null if no pending signal exists.
36963
+ *
36964
+ * @param symbol - Trading pair symbol
36965
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36966
+ * @returns PnL cost or null if no active position
36967
+ */
36968
+ this.getPositionHighestPnlCost = async (symbol, context) => {
36969
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
36970
+ symbol,
36971
+ context,
36972
+ });
36973
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36974
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36975
+ {
36976
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36977
+ riskName &&
36978
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36979
+ riskList &&
36980
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36981
+ actions &&
36982
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36983
+ }
36984
+ return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
36985
+ };
36986
+ /**
36987
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
36988
+ *
36989
+ * @param symbol - Trading pair symbol
36990
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36991
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
36992
+ */
36993
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
36994
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
36995
+ symbol,
36996
+ context,
36997
+ });
36998
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
36999
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37000
+ {
37001
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37002
+ riskName &&
37003
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37004
+ riskList &&
37005
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37006
+ actions &&
37007
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37008
+ }
37009
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
37010
+ };
37011
+ /**
37012
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
37013
+ *
37014
+ * Measures how long the position has been pulling back from its peak profit level.
37015
+ * Zero when called at the exact moment the peak was set.
37016
+ * Grows continuously as price moves away from the peak without setting a new record.
37017
+ *
37018
+ * Returns null if no pending signal exists.
37019
+ *
37020
+ * @param symbol - Trading pair symbol
37021
+ * @param context - Execution context with strategyName, exchangeName, and frameName
37022
+ * @returns Drawdown duration in minutes, or null if no active position
37023
+ */
37024
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
37025
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
37026
+ symbol,
37027
+ context,
37028
+ });
37029
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37030
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37031
+ {
37032
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37033
+ riskName &&
37034
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37035
+ riskList &&
37036
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37037
+ actions &&
37038
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37039
+ }
37040
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
37041
+ };
35584
37042
  /**
35585
37043
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
35586
37044
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -36142,7 +37600,7 @@ class BacktestUtils {
36142
37600
  if (!signal) {
36143
37601
  return false;
36144
37602
  }
36145
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37603
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36146
37604
  if (effectivePriceOpen === null) {
36147
37605
  return false;
36148
37606
  }
@@ -36227,7 +37685,7 @@ class BacktestUtils {
36227
37685
  if (!signal) {
36228
37686
  return false;
36229
37687
  }
36230
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37688
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36231
37689
  if (effectivePriceOpen === null) {
36232
37690
  return false;
36233
37691
  }
@@ -36280,7 +37738,7 @@ class BacktestUtils {
36280
37738
  if (!signal) {
36281
37739
  return false;
36282
37740
  }
36283
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37741
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36284
37742
  if (effectivePriceOpen === null) {
36285
37743
  return false;
36286
37744
  }
@@ -36334,7 +37792,7 @@ class BacktestUtils {
36334
37792
  if (!signal) {
36335
37793
  return false;
36336
37794
  }
36337
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37795
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36338
37796
  if (effectivePriceOpen === null) {
36339
37797
  return false;
36340
37798
  }
@@ -36396,7 +37854,7 @@ class BacktestUtils {
36396
37854
  if (!signal) {
36397
37855
  return false;
36398
37856
  }
36399
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37857
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36400
37858
  if (effectivePriceOpen === null) {
36401
37859
  return false;
36402
37860
  }
@@ -36684,7 +38142,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
36684
38142
  const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
36685
38143
  const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
36686
38144
  const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
36687
- const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
38145
+ const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
36688
38146
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
36689
38147
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
36690
38148
  const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
@@ -36692,6 +38150,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
36692
38150
  const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
36693
38151
  const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
36694
38152
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
38153
+ const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
38154
+ const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
38155
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
38156
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
38157
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
38158
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
38159
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
38160
+ const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
36695
38161
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
36696
38162
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
36697
38163
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -37295,7 +38761,7 @@ class LiveUtils {
37295
38761
  * @param context - Execution context with strategyName and exchangeName
37296
38762
  * @returns Effective entry price, or null if no active position
37297
38763
  */
37298
- this.getPositionAveragePrice = async (symbol, context) => {
38764
+ this.getPositionEffectivePrice = async (symbol, context) => {
37299
38765
  bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
37300
38766
  symbol,
37301
38767
  context,
@@ -37311,7 +38777,7 @@ class LiveUtils {
37311
38777
  actions &&
37312
38778
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
37313
38779
  }
37314
- return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
38780
+ return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
37315
38781
  strategyName: context.strategyName,
37316
38782
  exchangeName: context.exchangeName,
37317
38783
  frameName: "",
@@ -37561,6 +39027,262 @@ class LiveUtils {
37561
39027
  frameName: "",
37562
39028
  });
37563
39029
  };
39030
+ /**
39031
+ * Returns the original estimated duration for the current pending signal.
39032
+ *
39033
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
39034
+ * number of minutes the position is expected to be active before `time_expired`.
39035
+ *
39036
+ * Returns null if no pending signal exists.
39037
+ *
39038
+ * @param symbol - Trading pair symbol
39039
+ * @param context - Execution context with strategyName and exchangeName
39040
+ * @returns Estimated duration in minutes, or null if no active position
39041
+ */
39042
+ this.getPositionEstimateMinutes = async (symbol, context) => {
39043
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
39044
+ symbol,
39045
+ context,
39046
+ });
39047
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39048
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39049
+ {
39050
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39051
+ riskName &&
39052
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39053
+ riskList &&
39054
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39055
+ actions &&
39056
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39057
+ }
39058
+ return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
39059
+ strategyName: context.strategyName,
39060
+ exchangeName: context.exchangeName,
39061
+ frameName: "",
39062
+ });
39063
+ };
39064
+ /**
39065
+ * Returns the remaining time before the position expires, clamped to zero.
39066
+ *
39067
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
39068
+ * Returns 0 once the estimate is exceeded (never negative).
39069
+ *
39070
+ * Returns null if no pending signal exists.
39071
+ *
39072
+ * @param symbol - Trading pair symbol
39073
+ * @param context - Execution context with strategyName and exchangeName
39074
+ * @returns Remaining minutes (≥ 0), or null if no active position
39075
+ */
39076
+ this.getPositionCountdownMinutes = async (symbol, context) => {
39077
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
39078
+ symbol,
39079
+ context,
39080
+ });
39081
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39082
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39083
+ {
39084
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39085
+ riskName &&
39086
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39087
+ riskList &&
39088
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39089
+ actions &&
39090
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39091
+ }
39092
+ return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
39093
+ strategyName: context.strategyName,
39094
+ exchangeName: context.exchangeName,
39095
+ frameName: "",
39096
+ });
39097
+ };
39098
+ /**
39099
+ * Returns the best price reached in the profit direction during this position's life.
39100
+ *
39101
+ * Returns null if no pending signal exists.
39102
+ *
39103
+ * @param symbol - Trading pair symbol
39104
+ * @param context - Execution context with strategyName and exchangeName
39105
+ * @returns price or null if no active position
39106
+ */
39107
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
39108
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
39109
+ symbol,
39110
+ context,
39111
+ });
39112
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39113
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39114
+ {
39115
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39116
+ riskName &&
39117
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39118
+ riskList &&
39119
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39120
+ actions &&
39121
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39122
+ }
39123
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
39124
+ strategyName: context.strategyName,
39125
+ exchangeName: context.exchangeName,
39126
+ frameName: "",
39127
+ });
39128
+ };
39129
+ /**
39130
+ * Returns the timestamp when the best profit price was recorded during this position's life.
39131
+ *
39132
+ * Returns null if no pending signal exists.
39133
+ *
39134
+ * @param symbol - Trading pair symbol
39135
+ * @param context - Execution context with strategyName and exchangeName
39136
+ * @returns timestamp in milliseconds or null if no active position
39137
+ */
39138
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
39139
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
39140
+ symbol,
39141
+ context,
39142
+ });
39143
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39144
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39145
+ {
39146
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39147
+ riskName &&
39148
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39149
+ riskList &&
39150
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39151
+ actions &&
39152
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39153
+ }
39154
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
39155
+ strategyName: context.strategyName,
39156
+ exchangeName: context.exchangeName,
39157
+ frameName: "",
39158
+ });
39159
+ };
39160
+ /**
39161
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
39162
+ *
39163
+ * Returns null if no pending signal exists.
39164
+ *
39165
+ * @param symbol - Trading pair symbol
39166
+ * @param context - Execution context with strategyName and exchangeName
39167
+ * @returns PnL percentage or null if no active position
39168
+ */
39169
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
39170
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
39171
+ symbol,
39172
+ context,
39173
+ });
39174
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39175
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39176
+ {
39177
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39178
+ riskName &&
39179
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39180
+ riskList &&
39181
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39182
+ actions &&
39183
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39184
+ }
39185
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
39186
+ strategyName: context.strategyName,
39187
+ exchangeName: context.exchangeName,
39188
+ frameName: "",
39189
+ });
39190
+ };
39191
+ /**
39192
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
39193
+ *
39194
+ * Returns null if no pending signal exists.
39195
+ *
39196
+ * @param symbol - Trading pair symbol
39197
+ * @param context - Execution context with strategyName and exchangeName
39198
+ * @returns PnL cost or null if no active position
39199
+ */
39200
+ this.getPositionHighestPnlCost = async (symbol, context) => {
39201
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
39202
+ symbol,
39203
+ context,
39204
+ });
39205
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39206
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39207
+ {
39208
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39209
+ riskName &&
39210
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39211
+ riskList &&
39212
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39213
+ actions &&
39214
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39215
+ }
39216
+ return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
39217
+ strategyName: context.strategyName,
39218
+ exchangeName: context.exchangeName,
39219
+ frameName: "",
39220
+ });
39221
+ };
39222
+ /**
39223
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
39224
+ *
39225
+ * @param symbol - Trading pair symbol
39226
+ * @param context - Execution context with strategyName and exchangeName
39227
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
39228
+ */
39229
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
39230
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
39231
+ symbol,
39232
+ context,
39233
+ });
39234
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39235
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39236
+ {
39237
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39238
+ riskName &&
39239
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39240
+ riskList &&
39241
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39242
+ actions &&
39243
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39244
+ }
39245
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
39246
+ strategyName: context.strategyName,
39247
+ exchangeName: context.exchangeName,
39248
+ frameName: "",
39249
+ });
39250
+ };
39251
+ /**
39252
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
39253
+ *
39254
+ * Measures how long the position has been pulling back from its peak profit level.
39255
+ * Zero when called at the exact moment the peak was set.
39256
+ * Grows continuously as price moves away from the peak without setting a new record.
39257
+ *
39258
+ * Returns null if no pending signal exists.
39259
+ *
39260
+ * @param symbol - Trading pair symbol
39261
+ * @param context - Execution context with strategyName and exchangeName
39262
+ * @returns Drawdown duration in minutes, or null if no active position
39263
+ */
39264
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
39265
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
39266
+ symbol,
39267
+ context,
39268
+ });
39269
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39270
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39271
+ {
39272
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39273
+ riskName &&
39274
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39275
+ riskList &&
39276
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39277
+ actions &&
39278
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39279
+ }
39280
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
39281
+ strategyName: context.strategyName,
39282
+ exchangeName: context.exchangeName,
39283
+ frameName: "",
39284
+ });
39285
+ };
37564
39286
  /**
37565
39287
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
37566
39288
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38215,7 +39937,7 @@ class LiveUtils {
38215
39937
  if (!signal) {
38216
39938
  return false;
38217
39939
  }
38218
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
39940
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38219
39941
  strategyName: context.strategyName,
38220
39942
  exchangeName: context.exchangeName,
38221
39943
  frameName: "",
@@ -38315,7 +40037,7 @@ class LiveUtils {
38315
40037
  if (!signal) {
38316
40038
  return false;
38317
40039
  }
38318
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40040
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38319
40041
  strategyName: context.strategyName,
38320
40042
  exchangeName: context.exchangeName,
38321
40043
  frameName: "",
@@ -38384,7 +40106,7 @@ class LiveUtils {
38384
40106
  if (!signal) {
38385
40107
  return false;
38386
40108
  }
38387
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40109
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38388
40110
  strategyName: context.strategyName,
38389
40111
  exchangeName: context.exchangeName,
38390
40112
  frameName: "",
@@ -38454,7 +40176,7 @@ class LiveUtils {
38454
40176
  if (!signal) {
38455
40177
  return false;
38456
40178
  }
38457
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40179
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38458
40180
  strategyName: context.strategyName,
38459
40181
  exchangeName: context.exchangeName,
38460
40182
  frameName: "",
@@ -38532,7 +40254,7 @@ class LiveUtils {
38532
40254
  if (!signal) {
38533
40255
  return false;
38534
40256
  }
38535
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40257
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38536
40258
  strategyName: context.strategyName,
38537
40259
  exchangeName: context.exchangeName,
38538
40260
  frameName: "",
@@ -41956,6 +43678,98 @@ class PartialUtils {
41956
43678
  */
41957
43679
  const Partial = new PartialUtils();
41958
43680
 
43681
+ const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
43682
+ const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
43683
+ const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
43684
+ /**
43685
+ * Utility class for accessing highest profit reports and statistics.
43686
+ *
43687
+ * Provides static-like methods (via singleton instance) to retrieve data
43688
+ * accumulated by HighestProfitMarkdownService from highestProfitSubject events.
43689
+ *
43690
+ * @example
43691
+ * ```typescript
43692
+ * import { HighestProfit } from "backtest-kit";
43693
+ *
43694
+ * const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
43695
+ * const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
43696
+ * await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
43697
+ * ```
43698
+ */
43699
+ class HighestProfitUtils {
43700
+ constructor() {
43701
+ /**
43702
+ * Retrieves statistical data from accumulated highest profit events.
43703
+ *
43704
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43705
+ * @param context - Execution context
43706
+ * @param backtest - Whether to query backtest data
43707
+ * @returns Promise resolving to HighestProfitStatisticsModel
43708
+ */
43709
+ this.getData = async (symbol, context, backtest = false) => {
43710
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
43711
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43712
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43713
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43714
+ {
43715
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43716
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43717
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43718
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43719
+ }
43720
+ return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
43721
+ };
43722
+ /**
43723
+ * Generates a markdown report with all highest profit events for a symbol-strategy pair.
43724
+ *
43725
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43726
+ * @param context - Execution context
43727
+ * @param backtest - Whether to query backtest data
43728
+ * @param columns - Optional column configuration
43729
+ * @returns Promise resolving to markdown formatted report string
43730
+ */
43731
+ this.getReport = async (symbol, context, backtest = false, columns) => {
43732
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
43733
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43734
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43735
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43736
+ {
43737
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43738
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43739
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43740
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43741
+ }
43742
+ return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
43743
+ };
43744
+ /**
43745
+ * Generates and saves a markdown report to file.
43746
+ *
43747
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43748
+ * @param context - Execution context
43749
+ * @param backtest - Whether to query backtest data
43750
+ * @param path - Output directory path (default: "./dump/highest_profit")
43751
+ * @param columns - Optional column configuration
43752
+ */
43753
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
43754
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
43755
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43756
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43757
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43758
+ {
43759
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43760
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43761
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43762
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43763
+ }
43764
+ await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
43765
+ };
43766
+ }
43767
+ }
43768
+ /**
43769
+ * Global singleton instance of HighestProfitUtils.
43770
+ */
43771
+ const HighestProfit = new HighestProfitUtils();
43772
+
41959
43773
  /**
41960
43774
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
41961
43775
  *
@@ -46423,4 +48237,4 @@ const percentValue = (yesterdayValue, todayValue) => {
46423
48237
  return yesterdayValue / todayValue - 1;
46424
48238
  };
46425
48239
 
46426
- export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, 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, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionAveragePrice, getPositionEntryOverlap, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, 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, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };
48240
+ export { ActionBase, Backtest, Breakeven, Broker, BrokerBase, Cache, Constant, Exchange, ExecutionContextService, Heat, HighestProfit, Live, Log, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistLogAdapter, PersistMeasureAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, 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, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAggregatedTrades, getAveragePrice, getBacktestTimeframe, getBreakeven, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getEffectivePriceOpen, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getPendingSignal, getPositionCountdownMinutes, getPositionDrawdownMinutes, getPositionEffectivePrice, getPositionEntries, getPositionEntryOverlap, getPositionEstimateMinutes, getPositionHighestPnlCost, getPositionHighestPnlPercentage, getPositionHighestProfitBreakeven, getPositionHighestProfitPrice, getPositionHighestProfitTimestamp, getPositionInvestedCost, getPositionInvestedCount, getPositionLevels, getPositionPartialOverlap, getPositionPartials, getPositionPnlCost, getPositionPnlPercent, getRawCandles, getRiskSchema, getScheduledSignal, getSizingSchema, getStrategySchema, getSymbol, getTimestamp, getTotalClosed, getTotalCostClosed, getTotalPercentClosed, getWalkerSchema, hasTradeContext, investedCostToPercent, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenHighestProfit, listenHighestProfitOnce, 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, roundTicks, set, setColumns, setConfig, setLogger, shutdown, slPercentShiftToPrice, slPriceToPercentShift, stopStrategy, toProfitLossDto, tpPercentShiftToPrice, tpPriceToPercentShift, validate, waitForCandle, warmCandles };