backtest-kit 5.5.3 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/build/index.cjs +1979 -168
  2. package/build/index.mjs +1967 -168
  3. package/package.json +1 -1
  4. package/types.d.ts +1100 -140
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,78 @@ 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: "currentPrice",
18487
+ label: "Record Price",
18488
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
18489
+ isVisible: () => true,
18490
+ },
18491
+ {
18492
+ key: "priceOpen",
18493
+ label: "Entry Price",
18494
+ format: (data) => `${data.priceOpen.toFixed(8)} USD`,
18495
+ isVisible: () => true,
18496
+ },
18497
+ {
18498
+ key: "priceTakeProfit",
18499
+ label: "Take Profit",
18500
+ format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
18501
+ isVisible: () => true,
18502
+ },
18503
+ {
18504
+ key: "priceStopLoss",
18505
+ label: "Stop Loss",
18506
+ format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
18507
+ isVisible: () => true,
18508
+ },
18509
+ {
18510
+ key: "timestamp",
18511
+ label: "Timestamp",
18512
+ format: (data) => new Date(data.timestamp).toISOString(),
18513
+ isVisible: () => true,
18514
+ },
18515
+ {
18516
+ key: "mode",
18517
+ label: "Mode",
18518
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
18519
+ isVisible: () => true,
18520
+ },
18521
+ ];
18522
+
17887
18523
  /**
17888
18524
  * Column configuration for walker strategy comparison table in markdown reports.
17889
18525
  *
@@ -18101,6 +18737,8 @@ const COLUMN_CONFIG = {
18101
18737
  strategy_columns,
18102
18738
  /** Columns for signal sync lifecycle events (signal-open, signal-close) */
18103
18739
  sync_columns,
18740
+ /** Columns for highest profit milestone tracking events */
18741
+ highest_profit_columns,
18104
18742
  /** Walker: PnL summary columns */
18105
18743
  walker_pnl_columns,
18106
18744
  /** Walker: strategy-level summary columns */
@@ -18141,6 +18779,7 @@ const WILDCARD_TARGET$1 = {
18141
18779
  schedule: true,
18142
18780
  walker: true,
18143
18781
  sync: true,
18782
+ highest_profit: true,
18144
18783
  };
18145
18784
  /**
18146
18785
  * JSONL-based markdown adapter with append-only writes.
@@ -18368,7 +19007,7 @@ class MarkdownUtils {
18368
19007
  *
18369
19008
  * @returns Cleanup function that unsubscribes from all enabled services
18370
19009
  */
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) => {
19010
+ 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
19011
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
18373
19012
  backtest: bt$1,
18374
19013
  breakeven,
@@ -18381,6 +19020,7 @@ class MarkdownUtils {
18381
19020
  schedule,
18382
19021
  walker,
18383
19022
  sync,
19023
+ highest_profit,
18384
19024
  });
18385
19025
  const unList = [];
18386
19026
  if (bt$1) {
@@ -18416,6 +19056,9 @@ class MarkdownUtils {
18416
19056
  if (sync) {
18417
19057
  unList.push(bt.syncMarkdownService.subscribe());
18418
19058
  }
19059
+ if (highest_profit) {
19060
+ unList.push(bt.highestProfitMarkdownService.subscribe());
19061
+ }
18419
19062
  return compose(...unList.map((un) => () => void un()));
18420
19063
  };
18421
19064
  /**
@@ -18455,7 +19098,7 @@ class MarkdownUtils {
18455
19098
  * Markdown.disable();
18456
19099
  * ```
18457
19100
  */
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) => {
19101
+ 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
19102
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
18460
19103
  backtest: bt$1,
18461
19104
  breakeven,
@@ -18468,6 +19111,7 @@ class MarkdownUtils {
18468
19111
  schedule,
18469
19112
  walker,
18470
19113
  sync,
19114
+ highest_profit,
18471
19115
  });
18472
19116
  if (bt$1) {
18473
19117
  bt.backtestMarkdownService.unsubscribe();
@@ -18502,6 +19146,9 @@ class MarkdownUtils {
18502
19146
  if (sync) {
18503
19147
  bt.syncMarkdownService.unsubscribe();
18504
19148
  }
19149
+ if (highest_profit) {
19150
+ bt.highestProfitMarkdownService.unsubscribe();
19151
+ }
18505
19152
  };
18506
19153
  }
18507
19154
  }
@@ -18608,7 +19255,7 @@ const Markdown = new MarkdownAdapter();
18608
19255
  * @param backtest - Whether running in backtest mode
18609
19256
  * @returns Unique string key for memoization
18610
19257
  */
18611
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19258
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
18612
19259
  const parts = [symbol, strategyName, exchangeName];
18613
19260
  if (frameName)
18614
19261
  parts.push(frameName);
@@ -18625,7 +19272,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
18625
19272
  * @param timestamp - Unix timestamp in milliseconds
18626
19273
  * @returns Filename string
18627
19274
  */
18628
- const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19275
+ const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
18629
19276
  const parts = [symbol, strategyName, exchangeName];
18630
19277
  if (frameName) {
18631
19278
  parts.push(frameName);
@@ -18654,12 +19301,12 @@ function isUnsafe$3(value) {
18654
19301
  return false;
18655
19302
  }
18656
19303
  /** Maximum number of signals to store in backtest reports */
18657
- const MAX_EVENTS$9 = 250;
19304
+ const MAX_EVENTS$a = 250;
18658
19305
  /**
18659
19306
  * Storage class for accumulating closed signals per strategy.
18660
19307
  * Maintains a list of all closed signals and provides methods to generate reports.
18661
19308
  */
18662
- let ReportStorage$8 = class ReportStorage {
19309
+ let ReportStorage$9 = class ReportStorage {
18663
19310
  constructor(symbol, strategyName, exchangeName, frameName) {
18664
19311
  this.symbol = symbol;
18665
19312
  this.strategyName = strategyName;
@@ -18676,7 +19323,7 @@ let ReportStorage$8 = class ReportStorage {
18676
19323
  addSignal(data) {
18677
19324
  this._signalList.unshift(data);
18678
19325
  // Trim queue if exceeded MAX_EVENTS
18679
- if (this._signalList.length > MAX_EVENTS$9) {
19326
+ if (this._signalList.length > MAX_EVENTS$a) {
18680
19327
  this._signalList.pop();
18681
19328
  }
18682
19329
  }
@@ -18800,7 +19447,7 @@ let ReportStorage$8 = class ReportStorage {
18800
19447
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
18801
19448
  const markdown = await this.getReport(strategyName, columns);
18802
19449
  const timestamp = getContextTimestamp();
18803
- const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19450
+ const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18804
19451
  await Markdown.writeData("backtest", markdown, {
18805
19452
  path,
18806
19453
  file: filename,
@@ -18847,7 +19494,7 @@ class BacktestMarkdownService {
18847
19494
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18848
19495
  * Each combination gets its own isolated storage instance.
18849
19496
  */
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));
19497
+ 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
19498
  /**
18852
19499
  * Processes tick events and accumulates closed signals.
18853
19500
  * Should be called from IStrategyCallbacks.onTick.
@@ -19004,7 +19651,7 @@ class BacktestMarkdownService {
19004
19651
  payload,
19005
19652
  });
19006
19653
  if (payload) {
19007
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19654
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19008
19655
  this.getStorage.clear(key);
19009
19656
  }
19010
19657
  else {
@@ -19066,7 +19713,7 @@ class BacktestMarkdownService {
19066
19713
  * @param backtest - Whether running in backtest mode
19067
19714
  * @returns Unique string key for memoization
19068
19715
  */
19069
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19716
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19070
19717
  const parts = [symbol, strategyName, exchangeName];
19071
19718
  if (frameName)
19072
19719
  parts.push(frameName);
@@ -19083,7 +19730,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
19083
19730
  * @param timestamp - Unix timestamp in milliseconds
19084
19731
  * @returns Filename string
19085
19732
  */
19086
- const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19733
+ const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19087
19734
  const parts = [symbol, strategyName, exchangeName];
19088
19735
  if (frameName) {
19089
19736
  parts.push(frameName);
@@ -19112,12 +19759,12 @@ function isUnsafe$2(value) {
19112
19759
  return false;
19113
19760
  }
19114
19761
  /** Maximum number of events to store in live trading reports */
19115
- const MAX_EVENTS$8 = 250;
19762
+ const MAX_EVENTS$9 = 250;
19116
19763
  /**
19117
19764
  * Storage class for accumulating all tick events per strategy.
19118
19765
  * Maintains a chronological list of all events (idle, opened, active, closed).
19119
19766
  */
19120
- let ReportStorage$7 = class ReportStorage {
19767
+ let ReportStorage$8 = class ReportStorage {
19121
19768
  constructor(symbol, strategyName, exchangeName, frameName) {
19122
19769
  this.symbol = symbol;
19123
19770
  this.strategyName = strategyName;
@@ -19149,7 +19796,7 @@ let ReportStorage$7 = class ReportStorage {
19149
19796
  }
19150
19797
  {
19151
19798
  this._eventList.unshift(newEvent);
19152
- if (this._eventList.length > MAX_EVENTS$8) {
19799
+ if (this._eventList.length > MAX_EVENTS$9) {
19153
19800
  this._eventList.pop();
19154
19801
  }
19155
19802
  }
@@ -19179,7 +19826,7 @@ let ReportStorage$7 = class ReportStorage {
19179
19826
  scheduledAt: data.signal.scheduledAt,
19180
19827
  });
19181
19828
  // Trim queue if exceeded MAX_EVENTS
19182
- if (this._eventList.length > MAX_EVENTS$8) {
19829
+ if (this._eventList.length > MAX_EVENTS$9) {
19183
19830
  this._eventList.pop();
19184
19831
  }
19185
19832
  }
@@ -19223,7 +19870,7 @@ let ReportStorage$7 = class ReportStorage {
19223
19870
  // If no previous active event found, add new event
19224
19871
  this._eventList.unshift(newEvent);
19225
19872
  // Trim queue if exceeded MAX_EVENTS
19226
- if (this._eventList.length > MAX_EVENTS$8) {
19873
+ if (this._eventList.length > MAX_EVENTS$9) {
19227
19874
  this._eventList.pop();
19228
19875
  }
19229
19876
  }
@@ -19260,7 +19907,7 @@ let ReportStorage$7 = class ReportStorage {
19260
19907
  };
19261
19908
  this._eventList.unshift(newEvent);
19262
19909
  // Trim queue if exceeded MAX_EVENTS
19263
- if (this._eventList.length > MAX_EVENTS$8) {
19910
+ if (this._eventList.length > MAX_EVENTS$9) {
19264
19911
  this._eventList.pop();
19265
19912
  }
19266
19913
  }
@@ -19288,7 +19935,7 @@ let ReportStorage$7 = class ReportStorage {
19288
19935
  scheduledAt: data.signal.scheduledAt,
19289
19936
  });
19290
19937
  // Trim queue if exceeded MAX_EVENTS
19291
- if (this._eventList.length > MAX_EVENTS$8) {
19938
+ if (this._eventList.length > MAX_EVENTS$9) {
19292
19939
  this._eventList.pop();
19293
19940
  }
19294
19941
  }
@@ -19331,7 +19978,7 @@ let ReportStorage$7 = class ReportStorage {
19331
19978
  // If no previous waiting event found, add new event
19332
19979
  this._eventList.unshift(newEvent);
19333
19980
  // Trim queue if exceeded MAX_EVENTS
19334
- if (this._eventList.length > MAX_EVENTS$8) {
19981
+ if (this._eventList.length > MAX_EVENTS$9) {
19335
19982
  this._eventList.pop();
19336
19983
  }
19337
19984
  }
@@ -19360,7 +20007,7 @@ let ReportStorage$7 = class ReportStorage {
19360
20007
  scheduledAt: data.signal.scheduledAt,
19361
20008
  });
19362
20009
  // Trim queue if exceeded MAX_EVENTS
19363
- if (this._eventList.length > MAX_EVENTS$8) {
20010
+ if (this._eventList.length > MAX_EVENTS$9) {
19364
20011
  this._eventList.pop();
19365
20012
  }
19366
20013
  }
@@ -19499,7 +20146,7 @@ let ReportStorage$7 = class ReportStorage {
19499
20146
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
19500
20147
  const markdown = await this.getReport(strategyName, columns);
19501
20148
  const timestamp = getContextTimestamp();
19502
- const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20149
+ const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19503
20150
  await Markdown.writeData("live", markdown, {
19504
20151
  path,
19505
20152
  signalId: "",
@@ -19549,7 +20196,7 @@ class LiveMarkdownService {
19549
20196
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19550
20197
  * Each combination gets its own isolated storage instance.
19551
20198
  */
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));
20199
+ 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
20200
  /**
19554
20201
  * Subscribes to live signal emitter to receive tick events.
19555
20202
  * Protected against multiple subscriptions.
@@ -19767,7 +20414,7 @@ class LiveMarkdownService {
19767
20414
  payload,
19768
20415
  });
19769
20416
  if (payload) {
19770
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20417
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19771
20418
  this.getStorage.clear(key);
19772
20419
  }
19773
20420
  else {
@@ -19787,7 +20434,7 @@ class LiveMarkdownService {
19787
20434
  * @param backtest - Whether running in backtest mode
19788
20435
  * @returns Unique string key for memoization
19789
20436
  */
19790
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20437
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19791
20438
  const parts = [symbol, strategyName, exchangeName];
19792
20439
  if (frameName)
19793
20440
  parts.push(frameName);
@@ -19804,7 +20451,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
19804
20451
  * @param timestamp - Unix timestamp in milliseconds
19805
20452
  * @returns Filename string
19806
20453
  */
19807
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20454
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19808
20455
  const parts = [symbol, strategyName, exchangeName];
19809
20456
  if (frameName) {
19810
20457
  parts.push(frameName);
@@ -19815,12 +20462,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
19815
20462
  return `${parts.join("_")}-${timestamp}.md`;
19816
20463
  };
19817
20464
  /** Maximum number of events to store in schedule reports */
19818
- const MAX_EVENTS$7 = 250;
20465
+ const MAX_EVENTS$8 = 250;
19819
20466
  /**
19820
20467
  * Storage class for accumulating scheduled signal events per strategy.
19821
20468
  * Maintains a chronological list of scheduled and cancelled events.
19822
20469
  */
19823
- let ReportStorage$6 = class ReportStorage {
20470
+ let ReportStorage$7 = class ReportStorage {
19824
20471
  constructor(symbol, strategyName, exchangeName, frameName) {
19825
20472
  this.symbol = symbol;
19826
20473
  this.strategyName = strategyName;
@@ -19856,7 +20503,7 @@ let ReportStorage$6 = class ReportStorage {
19856
20503
  scheduledAt: data.signal.scheduledAt,
19857
20504
  });
19858
20505
  // Trim queue if exceeded MAX_EVENTS
19859
- if (this._eventList.length > MAX_EVENTS$7) {
20506
+ if (this._eventList.length > MAX_EVENTS$8) {
19860
20507
  this._eventList.pop();
19861
20508
  }
19862
20509
  }
@@ -19892,7 +20539,7 @@ let ReportStorage$6 = class ReportStorage {
19892
20539
  };
19893
20540
  this._eventList.unshift(newEvent);
19894
20541
  // Trim queue if exceeded MAX_EVENTS
19895
- if (this._eventList.length > MAX_EVENTS$7) {
20542
+ if (this._eventList.length > MAX_EVENTS$8) {
19896
20543
  this._eventList.pop();
19897
20544
  }
19898
20545
  }
@@ -19930,7 +20577,7 @@ let ReportStorage$6 = class ReportStorage {
19930
20577
  };
19931
20578
  this._eventList.unshift(newEvent);
19932
20579
  // Trim queue if exceeded MAX_EVENTS
19933
- if (this._eventList.length > MAX_EVENTS$7) {
20580
+ if (this._eventList.length > MAX_EVENTS$8) {
19934
20581
  this._eventList.pop();
19935
20582
  }
19936
20583
  }
@@ -20037,7 +20684,7 @@ let ReportStorage$6 = class ReportStorage {
20037
20684
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
20038
20685
  const markdown = await this.getReport(strategyName, columns);
20039
20686
  const timestamp = getContextTimestamp();
20040
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20687
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20041
20688
  await Markdown.writeData("schedule", markdown, {
20042
20689
  path,
20043
20690
  file: filename,
@@ -20078,7 +20725,7 @@ class ScheduleMarkdownService {
20078
20725
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20079
20726
  * Each combination gets its own isolated storage instance.
20080
20727
  */
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));
20728
+ 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
20729
  /**
20083
20730
  * Subscribes to signal emitter to receive scheduled signal events.
20084
20731
  * Protected against multiple subscriptions.
@@ -20281,7 +20928,7 @@ class ScheduleMarkdownService {
20281
20928
  payload,
20282
20929
  });
20283
20930
  if (payload) {
20284
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20931
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20285
20932
  this.getStorage.clear(key);
20286
20933
  }
20287
20934
  else {
@@ -20301,7 +20948,7 @@ class ScheduleMarkdownService {
20301
20948
  * @param backtest - Whether running in backtest mode
20302
20949
  * @returns Unique string key for memoization
20303
20950
  */
20304
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20951
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20305
20952
  const parts = [symbol, strategyName, exchangeName];
20306
20953
  if (frameName)
20307
20954
  parts.push(frameName);
@@ -20318,7 +20965,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
20318
20965
  * @param timestamp - Unix timestamp in milliseconds
20319
20966
  * @returns Filename string
20320
20967
  */
20321
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20968
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20322
20969
  const parts = [symbol, strategyName, exchangeName];
20323
20970
  if (frameName) {
20324
20971
  parts.push(frameName);
@@ -20338,7 +20985,7 @@ function percentile(sortedArray, p) {
20338
20985
  return sortedArray[Math.max(0, index)];
20339
20986
  }
20340
20987
  /** Maximum number of performance events to store per strategy */
20341
- const MAX_EVENTS$6 = 10000;
20988
+ const MAX_EVENTS$7 = 10000;
20342
20989
  /**
20343
20990
  * Storage class for accumulating performance metrics per strategy.
20344
20991
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -20360,7 +21007,7 @@ class PerformanceStorage {
20360
21007
  addEvent(event) {
20361
21008
  this._events.unshift(event);
20362
21009
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
20363
- if (this._events.length > MAX_EVENTS$6) {
21010
+ if (this._events.length > MAX_EVENTS$7) {
20364
21011
  this._events.pop();
20365
21012
  }
20366
21013
  }
@@ -20502,7 +21149,7 @@ class PerformanceStorage {
20502
21149
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
20503
21150
  const markdown = await this.getReport(strategyName, columns);
20504
21151
  const timestamp = getContextTimestamp();
20505
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21152
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20506
21153
  await Markdown.writeData("performance", markdown, {
20507
21154
  path,
20508
21155
  file: filename,
@@ -20549,7 +21196,7 @@ class PerformanceMarkdownService {
20549
21196
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20550
21197
  * Each combination gets its own isolated storage instance.
20551
21198
  */
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));
21199
+ 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
21200
  /**
20554
21201
  * Subscribes to performance emitter to receive performance events.
20555
21202
  * Protected against multiple subscriptions.
@@ -20716,7 +21363,7 @@ class PerformanceMarkdownService {
20716
21363
  payload,
20717
21364
  });
20718
21365
  if (payload) {
20719
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21366
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20720
21367
  this.getStorage.clear(key);
20721
21368
  }
20722
21369
  else {
@@ -20730,7 +21377,7 @@ class PerformanceMarkdownService {
20730
21377
  * Creates a filename for markdown report based on walker name.
20731
21378
  * Filename format: "walkerName-timestamp.md"
20732
21379
  */
20733
- const CREATE_FILE_NAME_FN$6 = (walkerName, timestamp) => {
21380
+ const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
20734
21381
  return `${walkerName}-${timestamp}.md`;
20735
21382
  };
20736
21383
  /**
@@ -20765,7 +21412,7 @@ function formatMetric(value) {
20765
21412
  * Storage class for accumulating walker results.
20766
21413
  * Maintains a list of all strategy results and provides methods to generate reports.
20767
21414
  */
20768
- let ReportStorage$5 = class ReportStorage {
21415
+ let ReportStorage$6 = class ReportStorage {
20769
21416
  constructor(walkerName) {
20770
21417
  this.walkerName = walkerName;
20771
21418
  /** Walker metadata (set from first addResult call) */
@@ -20953,7 +21600,7 @@ let ReportStorage$5 = class ReportStorage {
20953
21600
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
20954
21601
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
20955
21602
  const timestamp = getContextTimestamp();
20956
- const filename = CREATE_FILE_NAME_FN$6(this.walkerName, timestamp);
21603
+ const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
20957
21604
  await Markdown.writeData("walker", markdown, {
20958
21605
  path,
20959
21606
  file: filename,
@@ -20989,7 +21636,7 @@ class WalkerMarkdownService {
20989
21636
  * Memoized function to get or create ReportStorage for a walker.
20990
21637
  * Each walker gets its own isolated storage instance.
20991
21638
  */
20992
- this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$5(walkerName));
21639
+ this.getStorage = memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
20993
21640
  /**
20994
21641
  * Subscribes to walker emitter to receive walker progress events.
20995
21642
  * Protected against multiple subscriptions.
@@ -21186,7 +21833,7 @@ class WalkerMarkdownService {
21186
21833
  * @param backtest - Whether running in backtest mode
21187
21834
  * @returns Unique string key for memoization
21188
21835
  */
21189
- const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21836
+ const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
21190
21837
  const parts = [exchangeName];
21191
21838
  if (frameName)
21192
21839
  parts.push(frameName);
@@ -21197,7 +21844,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21197
21844
  * Creates a filename for markdown report based on memoization key components.
21198
21845
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
21199
21846
  */
21200
- const CREATE_FILE_NAME_FN$5 = (strategyName, exchangeName, frameName, timestamp) => {
21847
+ const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
21201
21848
  const parts = [strategyName, exchangeName];
21202
21849
  if (frameName) {
21203
21850
  parts.push(frameName);
@@ -21230,7 +21877,7 @@ function isUnsafe(value) {
21230
21877
  return false;
21231
21878
  }
21232
21879
  /** Maximum number of signals to store per symbol in heatmap reports */
21233
- const MAX_EVENTS$5 = 250;
21880
+ const MAX_EVENTS$6 = 250;
21234
21881
  /**
21235
21882
  * Storage class for accumulating closed signals per strategy and generating heatmap.
21236
21883
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -21256,7 +21903,7 @@ class HeatmapStorage {
21256
21903
  const signals = this.symbolData.get(symbol);
21257
21904
  signals.unshift(data);
21258
21905
  // Trim queue if exceeded MAX_EVENTS per symbol
21259
- if (signals.length > MAX_EVENTS$5) {
21906
+ if (signals.length > MAX_EVENTS$6) {
21260
21907
  signals.pop();
21261
21908
  }
21262
21909
  }
@@ -21507,7 +22154,7 @@ class HeatmapStorage {
21507
22154
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
21508
22155
  const markdown = await this.getReport(strategyName, columns);
21509
22156
  const timestamp = getContextTimestamp();
21510
- const filename = CREATE_FILE_NAME_FN$5(strategyName, this.exchangeName, this.frameName, timestamp);
22157
+ const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
21511
22158
  await Markdown.writeData("heat", markdown, {
21512
22159
  path,
21513
22160
  file: filename,
@@ -21553,7 +22200,7 @@ class HeatMarkdownService {
21553
22200
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21554
22201
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21555
22202
  */
21556
- this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
22203
+ this.getStorage = memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21557
22204
  /**
21558
22205
  * Subscribes to signal emitter to receive tick events.
21559
22206
  * Protected against multiple subscriptions.
@@ -21748,7 +22395,7 @@ class HeatMarkdownService {
21748
22395
  payload,
21749
22396
  });
21750
22397
  if (payload) {
21751
- const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
22398
+ const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
21752
22399
  this.getStorage.clear(key);
21753
22400
  }
21754
22401
  else {
@@ -22779,7 +23426,7 @@ class ClientPartial {
22779
23426
  * @param backtest - Whether running in backtest mode
22780
23427
  * @returns Unique string key for memoization
22781
23428
  */
22782
- const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23429
+ const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22783
23430
  /**
22784
23431
  * Creates a callback function for emitting profit events to partialProfitSubject.
22785
23432
  *
@@ -22901,7 +23548,7 @@ class PartialConnectionService {
22901
23548
  * Key format: "signalId:backtest" or "signalId:live"
22902
23549
  * Value: ClientPartial instance with logger and event emitters
22903
23550
  */
22904
- this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
23551
+ this.getPartial = memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
22905
23552
  return new ClientPartial({
22906
23553
  signalId,
22907
23554
  logger: this.loggerService,
@@ -22991,7 +23638,7 @@ class PartialConnectionService {
22991
23638
  const partial = this.getPartial(data.id, backtest);
22992
23639
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
22993
23640
  await partial.clear(symbol, data, priceClose, backtest);
22994
- const key = CREATE_KEY_FN$c(data.id, backtest);
23641
+ const key = CREATE_KEY_FN$d(data.id, backtest);
22995
23642
  this.getPartial.clear(key);
22996
23643
  };
22997
23644
  }
@@ -23007,7 +23654,7 @@ class PartialConnectionService {
23007
23654
  * @param backtest - Whether running in backtest mode
23008
23655
  * @returns Unique string key for memoization
23009
23656
  */
23010
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
23657
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
23011
23658
  const parts = [symbol, strategyName, exchangeName];
23012
23659
  if (frameName)
23013
23660
  parts.push(frameName);
@@ -23018,7 +23665,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
23018
23665
  * Creates a filename for markdown report based on memoization key components.
23019
23666
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
23020
23667
  */
23021
- const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23668
+ const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23022
23669
  const parts = [symbol, strategyName, exchangeName];
23023
23670
  if (frameName) {
23024
23671
  parts.push(frameName);
@@ -23029,12 +23676,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
23029
23676
  return `${parts.join("_")}-${timestamp}.md`;
23030
23677
  };
23031
23678
  /** Maximum number of events to store in partial reports */
23032
- const MAX_EVENTS$4 = 250;
23679
+ const MAX_EVENTS$5 = 250;
23033
23680
  /**
23034
23681
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
23035
23682
  * Maintains a chronological list of profit and loss level events.
23036
23683
  */
23037
- let ReportStorage$4 = class ReportStorage {
23684
+ let ReportStorage$5 = class ReportStorage {
23038
23685
  constructor(symbol, strategyName, exchangeName, frameName) {
23039
23686
  this.symbol = symbol;
23040
23687
  this.strategyName = strategyName;
@@ -23077,7 +23724,7 @@ let ReportStorage$4 = class ReportStorage {
23077
23724
  backtest,
23078
23725
  });
23079
23726
  // Trim queue if exceeded MAX_EVENTS
23080
- if (this._eventList.length > MAX_EVENTS$4) {
23727
+ if (this._eventList.length > MAX_EVENTS$5) {
23081
23728
  this._eventList.pop();
23082
23729
  }
23083
23730
  }
@@ -23115,7 +23762,7 @@ let ReportStorage$4 = class ReportStorage {
23115
23762
  backtest,
23116
23763
  });
23117
23764
  // Trim queue if exceeded MAX_EVENTS
23118
- if (this._eventList.length > MAX_EVENTS$4) {
23765
+ if (this._eventList.length > MAX_EVENTS$5) {
23119
23766
  this._eventList.pop();
23120
23767
  }
23121
23768
  }
@@ -23191,7 +23838,7 @@ let ReportStorage$4 = class ReportStorage {
23191
23838
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
23192
23839
  const markdown = await this.getReport(symbol, strategyName, columns);
23193
23840
  const timestamp = getContextTimestamp();
23194
- const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23841
+ const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23195
23842
  await Markdown.writeData("partial", markdown, {
23196
23843
  path,
23197
23844
  file: filename,
@@ -23232,7 +23879,7 @@ class PartialMarkdownService {
23232
23879
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23233
23880
  * Each combination gets its own isolated storage instance.
23234
23881
  */
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));
23882
+ 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
23883
  /**
23237
23884
  * Subscribes to partial profit/loss signal emitters to receive events.
23238
23885
  * Protected against multiple subscriptions.
@@ -23442,7 +24089,7 @@ class PartialMarkdownService {
23442
24089
  payload,
23443
24090
  });
23444
24091
  if (payload) {
23445
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24092
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23446
24093
  this.getStorage.clear(key);
23447
24094
  }
23448
24095
  else {
@@ -23458,7 +24105,7 @@ class PartialMarkdownService {
23458
24105
  * @param context - Context with strategyName, exchangeName, frameName
23459
24106
  * @returns Unique string key for memoization
23460
24107
  */
23461
- const CREATE_KEY_FN$a = (context) => {
24108
+ const CREATE_KEY_FN$b = (context) => {
23462
24109
  const parts = [context.strategyName, context.exchangeName];
23463
24110
  if (context.frameName)
23464
24111
  parts.push(context.frameName);
@@ -23532,7 +24179,7 @@ class PartialGlobalService {
23532
24179
  * @param context - Context with strategyName, exchangeName and frameName
23533
24180
  * @param methodName - Name of the calling method for error tracking
23534
24181
  */
23535
- this.validate = memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
24182
+ this.validate = memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
23536
24183
  this.loggerService.log("partialGlobalService validate", {
23537
24184
  context,
23538
24185
  methodName,
@@ -23987,7 +24634,7 @@ class ClientBreakeven {
23987
24634
  * @param backtest - Whether running in backtest mode
23988
24635
  * @returns Unique string key for memoization
23989
24636
  */
23990
- const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24637
+ const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23991
24638
  /**
23992
24639
  * Creates a callback function for emitting breakeven events to breakevenSubject.
23993
24640
  *
@@ -24073,7 +24720,7 @@ class BreakevenConnectionService {
24073
24720
  * Key format: "signalId:backtest" or "signalId:live"
24074
24721
  * Value: ClientBreakeven instance with logger and event emitter
24075
24722
  */
24076
- this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
24723
+ this.getBreakeven = memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
24077
24724
  return new ClientBreakeven({
24078
24725
  signalId,
24079
24726
  logger: this.loggerService,
@@ -24134,7 +24781,7 @@ class BreakevenConnectionService {
24134
24781
  const breakeven = this.getBreakeven(data.id, backtest);
24135
24782
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24136
24783
  await breakeven.clear(symbol, data, priceClose, backtest);
24137
- const key = CREATE_KEY_FN$9(data.id, backtest);
24784
+ const key = CREATE_KEY_FN$a(data.id, backtest);
24138
24785
  this.getBreakeven.clear(key);
24139
24786
  };
24140
24787
  }
@@ -24150,7 +24797,7 @@ class BreakevenConnectionService {
24150
24797
  * @param backtest - Whether running in backtest mode
24151
24798
  * @returns Unique string key for memoization
24152
24799
  */
24153
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24800
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24154
24801
  const parts = [symbol, strategyName, exchangeName];
24155
24802
  if (frameName)
24156
24803
  parts.push(frameName);
@@ -24161,7 +24808,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
24161
24808
  * Creates a filename for markdown report based on memoization key components.
24162
24809
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24163
24810
  */
24164
- const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24811
+ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24165
24812
  const parts = [symbol, strategyName, exchangeName];
24166
24813
  if (frameName) {
24167
24814
  parts.push(frameName);
@@ -24172,12 +24819,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
24172
24819
  return `${parts.join("_")}-${timestamp}.md`;
24173
24820
  };
24174
24821
  /** Maximum number of events to store in breakeven reports */
24175
- const MAX_EVENTS$3 = 250;
24822
+ const MAX_EVENTS$4 = 250;
24176
24823
  /**
24177
24824
  * Storage class for accumulating breakeven events per symbol-strategy pair.
24178
24825
  * Maintains a chronological list of breakeven events.
24179
24826
  */
24180
- let ReportStorage$3 = class ReportStorage {
24827
+ let ReportStorage$4 = class ReportStorage {
24181
24828
  constructor(symbol, strategyName, exchangeName, frameName) {
24182
24829
  this.symbol = symbol;
24183
24830
  this.strategyName = strategyName;
@@ -24218,7 +24865,7 @@ let ReportStorage$3 = class ReportStorage {
24218
24865
  backtest,
24219
24866
  });
24220
24867
  // Trim queue if exceeded MAX_EVENTS
24221
- if (this._eventList.length > MAX_EVENTS$3) {
24868
+ if (this._eventList.length > MAX_EVENTS$4) {
24222
24869
  this._eventList.pop();
24223
24870
  }
24224
24871
  }
@@ -24286,7 +24933,7 @@ let ReportStorage$3 = class ReportStorage {
24286
24933
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
24287
24934
  const markdown = await this.getReport(symbol, strategyName, columns);
24288
24935
  const timestamp = getContextTimestamp();
24289
- const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24936
+ const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24290
24937
  await Markdown.writeData("breakeven", markdown, {
24291
24938
  path,
24292
24939
  file: filename,
@@ -24327,7 +24974,7 @@ class BreakevenMarkdownService {
24327
24974
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24328
24975
  * Each combination gets its own isolated storage instance.
24329
24976
  */
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));
24977
+ 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
24978
  /**
24332
24979
  * Subscribes to breakeven signal emitter to receive events.
24333
24980
  * Protected against multiple subscriptions.
@@ -24516,7 +25163,7 @@ class BreakevenMarkdownService {
24516
25163
  payload,
24517
25164
  });
24518
25165
  if (payload) {
24519
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25166
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24520
25167
  this.getStorage.clear(key);
24521
25168
  }
24522
25169
  else {
@@ -24532,7 +25179,7 @@ class BreakevenMarkdownService {
24532
25179
  * @param context - Context with strategyName, exchangeName, frameName
24533
25180
  * @returns Unique string key for memoization
24534
25181
  */
24535
- const CREATE_KEY_FN$7 = (context) => {
25182
+ const CREATE_KEY_FN$8 = (context) => {
24536
25183
  const parts = [context.strategyName, context.exchangeName];
24537
25184
  if (context.frameName)
24538
25185
  parts.push(context.frameName);
@@ -24606,7 +25253,7 @@ class BreakevenGlobalService {
24606
25253
  * @param context - Context with strategyName, exchangeName and frameName
24607
25254
  * @param methodName - Name of the calling method for error tracking
24608
25255
  */
24609
- this.validate = memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
25256
+ this.validate = memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
24610
25257
  this.loggerService.log("breakevenGlobalService validate", {
24611
25258
  context,
24612
25259
  methodName,
@@ -24826,7 +25473,7 @@ class ConfigValidationService {
24826
25473
  * @param backtest - Whether running in backtest mode
24827
25474
  * @returns Unique string key for memoization
24828
25475
  */
24829
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
25476
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24830
25477
  const parts = [symbol, strategyName, exchangeName];
24831
25478
  if (frameName)
24832
25479
  parts.push(frameName);
@@ -24837,7 +25484,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
24837
25484
  * Creates a filename for markdown report based on memoization key components.
24838
25485
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24839
25486
  */
24840
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
25487
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24841
25488
  const parts = [symbol, strategyName, exchangeName];
24842
25489
  if (frameName) {
24843
25490
  parts.push(frameName);
@@ -24848,12 +25495,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
24848
25495
  return `${parts.join("_")}-${timestamp}.md`;
24849
25496
  };
24850
25497
  /** Maximum number of events to store in risk reports */
24851
- const MAX_EVENTS$2 = 250;
25498
+ const MAX_EVENTS$3 = 250;
24852
25499
  /**
24853
25500
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
24854
25501
  * Maintains a chronological list of rejected signals due to risk limits.
24855
25502
  */
24856
- let ReportStorage$2 = class ReportStorage {
25503
+ let ReportStorage$3 = class ReportStorage {
24857
25504
  constructor(symbol, strategyName, exchangeName, frameName) {
24858
25505
  this.symbol = symbol;
24859
25506
  this.strategyName = strategyName;
@@ -24870,7 +25517,7 @@ let ReportStorage$2 = class ReportStorage {
24870
25517
  addRejectionEvent(event) {
24871
25518
  this._eventList.unshift(event);
24872
25519
  // Trim queue if exceeded MAX_EVENTS
24873
- if (this._eventList.length > MAX_EVENTS$2) {
25520
+ if (this._eventList.length > MAX_EVENTS$3) {
24874
25521
  this._eventList.pop();
24875
25522
  }
24876
25523
  }
@@ -24954,7 +25601,7 @@ let ReportStorage$2 = class ReportStorage {
24954
25601
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
24955
25602
  const markdown = await this.getReport(symbol, strategyName, columns);
24956
25603
  const timestamp = getContextTimestamp();
24957
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25604
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24958
25605
  await Markdown.writeData("risk", markdown, {
24959
25606
  path,
24960
25607
  file: filename,
@@ -24995,7 +25642,7 @@ class RiskMarkdownService {
24995
25642
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24996
25643
  * Each combination gets its own isolated storage instance.
24997
25644
  */
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));
25645
+ 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
25646
  /**
25000
25647
  * Subscribes to risk rejection emitter to receive rejection events.
25001
25648
  * Protected against multiple subscriptions.
@@ -25184,7 +25831,7 @@ class RiskMarkdownService {
25184
25831
  payload,
25185
25832
  });
25186
25833
  if (payload) {
25187
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25834
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25188
25835
  this.getStorage.clear(key);
25189
25836
  }
25190
25837
  else {
@@ -25482,6 +26129,7 @@ const WILDCARD_TARGET = {
25482
26129
  schedule: true,
25483
26130
  walker: true,
25484
26131
  sync: true,
26132
+ highest_profit: true,
25485
26133
  };
25486
26134
  /**
25487
26135
  * Utility class for managing report services.
@@ -25519,7 +26167,7 @@ class ReportUtils {
25519
26167
  *
25520
26168
  * @returns Cleanup function that unsubscribes from all enabled services
25521
26169
  */
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) => {
26170
+ 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
26171
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
25524
26172
  backtest: bt$1,
25525
26173
  breakeven,
@@ -25567,6 +26215,9 @@ class ReportUtils {
25567
26215
  if (sync) {
25568
26216
  unList.push(bt.syncReportService.subscribe());
25569
26217
  }
26218
+ if (highest_profit) {
26219
+ unList.push(bt.highestProfitReportService.subscribe());
26220
+ }
25570
26221
  return compose(...unList.map((un) => () => void un()));
25571
26222
  };
25572
26223
  /**
@@ -25605,7 +26256,7 @@ class ReportUtils {
25605
26256
  * Report.disable();
25606
26257
  * ```
25607
26258
  */
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) => {
26259
+ 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
26260
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
25610
26261
  backtest: bt$1,
25611
26262
  breakeven,
@@ -25652,6 +26303,9 @@ class ReportUtils {
25652
26303
  if (sync) {
25653
26304
  bt.syncReportService.unsubscribe();
25654
26305
  }
26306
+ if (highest_profit) {
26307
+ bt.highestProfitReportService.unsubscribe();
26308
+ }
25655
26309
  };
25656
26310
  }
25657
26311
  }
@@ -27859,6 +28513,60 @@ class SyncReportService {
27859
28513
  }
27860
28514
  }
27861
28515
 
28516
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
28517
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
28518
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
28519
+ /**
28520
+ * Service for logging highest profit events to the JSONL report database.
28521
+ *
28522
+ * Listens to highestProfitSubject and writes each new price record to
28523
+ * Report.writeData() for persistence and analytics.
28524
+ */
28525
+ class HighestProfitReportService {
28526
+ constructor() {
28527
+ this.loggerService = inject(TYPES.loggerService);
28528
+ this.tick = async (data) => {
28529
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
28530
+ await Report.writeData("highest_profit", {
28531
+ timestamp: data.timestamp,
28532
+ symbol: data.symbol,
28533
+ strategyName: data.signal.strategyName,
28534
+ exchangeName: data.exchangeName,
28535
+ frameName: data.frameName,
28536
+ backtest: data.backtest,
28537
+ signalId: data.signal.id,
28538
+ position: data.signal.position,
28539
+ currentPrice: data.currentPrice,
28540
+ priceOpen: data.signal.priceOpen,
28541
+ priceTakeProfit: data.signal.priceTakeProfit,
28542
+ priceStopLoss: data.signal.priceStopLoss,
28543
+ }, {
28544
+ symbol: data.symbol,
28545
+ strategyName: data.signal.strategyName,
28546
+ exchangeName: data.exchangeName,
28547
+ frameName: data.frameName,
28548
+ signalId: data.signal.id,
28549
+ walkerName: "",
28550
+ });
28551
+ };
28552
+ this.subscribe = singleshot(() => {
28553
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
28554
+ const unsub = highestProfitSubject.subscribe(this.tick);
28555
+ return () => {
28556
+ this.subscribe.clear();
28557
+ unsub();
28558
+ };
28559
+ });
28560
+ this.unsubscribe = async () => {
28561
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
28562
+ if (this.subscribe.hasValue()) {
28563
+ const lastSubscription = this.subscribe();
28564
+ lastSubscription();
28565
+ }
28566
+ };
28567
+ }
28568
+ }
28569
+
27862
28570
  /**
27863
28571
  * Creates a unique key for memoizing ReportStorage instances.
27864
28572
  *
@@ -27872,7 +28580,7 @@ class SyncReportService {
27872
28580
  * @returns Colon-separated key string for memoization
27873
28581
  * @internal
27874
28582
  */
27875
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28583
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27876
28584
  const parts = [symbol, strategyName, exchangeName];
27877
28585
  if (frameName)
27878
28586
  parts.push(frameName);
@@ -27892,7 +28600,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
27892
28600
  * @returns Underscore-separated filename with .md extension
27893
28601
  * @internal
27894
28602
  */
27895
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28603
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
27896
28604
  const parts = [symbol, strategyName, exchangeName];
27897
28605
  if (frameName) {
27898
28606
  parts.push(frameName);
@@ -27907,7 +28615,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
27907
28615
  * Older events are discarded when this limit is exceeded.
27908
28616
  * @internal
27909
28617
  */
27910
- const MAX_EVENTS$1 = 250;
28618
+ const MAX_EVENTS$2 = 250;
27911
28619
  /**
27912
28620
  * In-memory storage for accumulating strategy events per symbol-strategy pair.
27913
28621
  *
@@ -27920,7 +28628,7 @@ const MAX_EVENTS$1 = 250;
27920
28628
  *
27921
28629
  * @internal
27922
28630
  */
27923
- let ReportStorage$1 = class ReportStorage {
28631
+ let ReportStorage$2 = class ReportStorage {
27924
28632
  /**
27925
28633
  * Creates a new ReportStorage instance.
27926
28634
  *
@@ -27946,7 +28654,7 @@ let ReportStorage$1 = class ReportStorage {
27946
28654
  */
27947
28655
  addEvent(event) {
27948
28656
  this._eventList.unshift(event);
27949
- if (this._eventList.length > MAX_EVENTS$1) {
28657
+ if (this._eventList.length > MAX_EVENTS$2) {
27950
28658
  this._eventList.pop();
27951
28659
  }
27952
28660
  }
@@ -28051,7 +28759,7 @@ let ReportStorage$1 = class ReportStorage {
28051
28759
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
28052
28760
  const markdown = await this.getReport(symbol, strategyName, columns);
28053
28761
  const timestamp = getContextTimestamp();
28054
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28762
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28055
28763
  await Markdown.writeData("strategy", markdown, {
28056
28764
  path,
28057
28765
  file: filename,
@@ -28120,7 +28828,7 @@ class StrategyMarkdownService {
28120
28828
  *
28121
28829
  * @internal
28122
28830
  */
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));
28831
+ 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
28832
  /**
28125
28833
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
28126
28834
  *
@@ -28688,7 +29396,7 @@ class StrategyMarkdownService {
28688
29396
  this.clear = async (payload) => {
28689
29397
  this.loggerService.log("strategyMarkdownService clear", { payload });
28690
29398
  if (payload) {
28691
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29399
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28692
29400
  this.getStorage.clear(key);
28693
29401
  }
28694
29402
  else {
@@ -28796,7 +29504,7 @@ class StrategyMarkdownService {
28796
29504
  * Creates a unique key for memoizing ReportStorage instances.
28797
29505
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28798
29506
  */
28799
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29507
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28800
29508
  const parts = [symbol, strategyName, exchangeName];
28801
29509
  if (frameName)
28802
29510
  parts.push(frameName);
@@ -28807,7 +29515,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
28807
29515
  * Creates a filename for the markdown report.
28808
29516
  * Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
28809
29517
  */
28810
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29518
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28811
29519
  const parts = [symbol, strategyName, exchangeName];
28812
29520
  if (frameName) {
28813
29521
  parts.push(frameName);
@@ -28818,12 +29526,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
28818
29526
  return `${parts.join("_")}-${timestamp}.md`;
28819
29527
  };
28820
29528
  /** Maximum number of events to store in sync reports */
28821
- const MAX_EVENTS = 250;
29529
+ const MAX_EVENTS$1 = 250;
28822
29530
  /**
28823
29531
  * Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
28824
29532
  * Maintains a chronological list of signal-open and signal-close events.
28825
29533
  */
28826
- class ReportStorage {
29534
+ let ReportStorage$1 = class ReportStorage {
28827
29535
  constructor(symbol, strategyName, exchangeName, frameName) {
28828
29536
  this.symbol = symbol;
28829
29537
  this.strategyName = strategyName;
@@ -28833,7 +29541,7 @@ class ReportStorage {
28833
29541
  }
28834
29542
  addEvent(event) {
28835
29543
  this._eventList.unshift(event);
28836
- if (this._eventList.length > MAX_EVENTS) {
29544
+ if (this._eventList.length > MAX_EVENTS$1) {
28837
29545
  this._eventList.pop();
28838
29546
  }
28839
29547
  }
@@ -28894,7 +29602,7 @@ class ReportStorage {
28894
29602
  async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
28895
29603
  const markdown = await this.getReport(symbol, strategyName, columns);
28896
29604
  const timestamp = getContextTimestamp();
28897
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29605
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28898
29606
  await Markdown.writeData("sync", markdown, {
28899
29607
  path,
28900
29608
  file: filename,
@@ -28905,7 +29613,7 @@ class ReportStorage {
28905
29613
  frameName: this.frameName,
28906
29614
  });
28907
29615
  }
28908
- }
29616
+ };
28909
29617
  /**
28910
29618
  * Service for generating and saving signal sync markdown reports.
28911
29619
  *
@@ -28928,7 +29636,7 @@ class ReportStorage {
28928
29636
  class SyncMarkdownService {
28929
29637
  constructor() {
28930
29638
  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));
29639
+ 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
29640
  this.subscribe = singleshot(() => {
28933
29641
  this.loggerService.log("syncMarkdownService init");
28934
29642
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -29002,6 +29710,189 @@ class SyncMarkdownService {
29002
29710
  };
29003
29711
  this.clear = async (payload) => {
29004
29712
  this.loggerService.log("syncMarkdownService clear", { payload });
29713
+ if (payload) {
29714
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29715
+ this.getStorage.clear(key);
29716
+ }
29717
+ else {
29718
+ this.getStorage.clear();
29719
+ }
29720
+ };
29721
+ }
29722
+ }
29723
+
29724
+ /**
29725
+ * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
29726
+ */
29727
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29728
+ const parts = [symbol, strategyName, exchangeName];
29729
+ if (frameName)
29730
+ parts.push(frameName);
29731
+ parts.push(backtest ? "backtest" : "live");
29732
+ return parts.join(":");
29733
+ };
29734
+ /**
29735
+ * Creates a filename for the markdown report.
29736
+ */
29737
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29738
+ const parts = [symbol, strategyName, exchangeName];
29739
+ if (frameName) {
29740
+ parts.push(frameName);
29741
+ parts.push("backtest");
29742
+ }
29743
+ else
29744
+ parts.push("live");
29745
+ return `${parts.join("_")}-${timestamp}.md`;
29746
+ };
29747
+ /** Maximum number of events to store per combination */
29748
+ const MAX_EVENTS = 250;
29749
+ /**
29750
+ * Accumulates highest profit events per symbol-strategy-exchange-frame combination.
29751
+ */
29752
+ class ReportStorage {
29753
+ constructor(symbol, strategyName, exchangeName, frameName) {
29754
+ this.symbol = symbol;
29755
+ this.strategyName = strategyName;
29756
+ this.exchangeName = exchangeName;
29757
+ this.frameName = frameName;
29758
+ this._eventList = [];
29759
+ }
29760
+ /**
29761
+ * Adds a highest profit event to the storage.
29762
+ */
29763
+ addEvent(data, currentPrice, backtest, timestamp) {
29764
+ this._eventList.unshift({
29765
+ timestamp,
29766
+ symbol: data.symbol,
29767
+ strategyName: data.strategyName,
29768
+ signalId: data.id,
29769
+ position: data.position,
29770
+ currentPrice,
29771
+ priceOpen: data.priceOpen,
29772
+ priceTakeProfit: data.priceTakeProfit,
29773
+ priceStopLoss: data.priceStopLoss,
29774
+ backtest,
29775
+ });
29776
+ if (this._eventList.length > MAX_EVENTS) {
29777
+ this._eventList.pop();
29778
+ }
29779
+ }
29780
+ /**
29781
+ * Returns aggregated statistics from accumulated events.
29782
+ */
29783
+ async getData() {
29784
+ return {
29785
+ eventList: this._eventList,
29786
+ totalEvents: this._eventList.length,
29787
+ };
29788
+ }
29789
+ /**
29790
+ * Generates a markdown report table for this storage.
29791
+ */
29792
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
29793
+ const stats = await this.getData();
29794
+ if (stats.totalEvents === 0) {
29795
+ return [
29796
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29797
+ "",
29798
+ "No highest profit events recorded yet.",
29799
+ ].join("\n");
29800
+ }
29801
+ const visibleColumns = [];
29802
+ for (const col of columns) {
29803
+ if (await col.isVisible()) {
29804
+ visibleColumns.push(col);
29805
+ }
29806
+ }
29807
+ const header = visibleColumns.map((col) => col.label);
29808
+ const separator = visibleColumns.map(() => "---");
29809
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
29810
+ const tableData = [header, separator, ...rows];
29811
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
29812
+ return [
29813
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29814
+ "",
29815
+ table,
29816
+ "",
29817
+ `**Total events:** ${stats.totalEvents}`,
29818
+ ].join("\n");
29819
+ }
29820
+ /**
29821
+ * Saves the report to disk.
29822
+ */
29823
+ async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
29824
+ const markdown = await this.getReport(symbol, strategyName, columns);
29825
+ const timestamp = getContextTimestamp();
29826
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29827
+ await Markdown.writeData("highest_profit", markdown, {
29828
+ path,
29829
+ file: filename,
29830
+ symbol: this.symbol,
29831
+ signalId: "",
29832
+ strategyName: this.strategyName,
29833
+ exchangeName: this.exchangeName,
29834
+ frameName: this.frameName,
29835
+ });
29836
+ }
29837
+ }
29838
+ /**
29839
+ * Service for generating and saving highest profit markdown reports.
29840
+ *
29841
+ * Listens to highestProfitSubject and accumulates events per
29842
+ * symbol-strategy-exchange-frame combination. Provides getData(),
29843
+ * getReport(), and dump() methods matching the Partial pattern.
29844
+ */
29845
+ class HighestProfitMarkdownService {
29846
+ constructor() {
29847
+ this.loggerService = inject(TYPES.loggerService);
29848
+ 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));
29849
+ this.subscribe = singleshot(() => {
29850
+ this.loggerService.log("highestProfitMarkdownService init");
29851
+ const unsub = highestProfitSubject.subscribe(this.tick);
29852
+ return () => {
29853
+ this.subscribe.clear();
29854
+ this.clear();
29855
+ unsub();
29856
+ };
29857
+ });
29858
+ this.unsubscribe = async () => {
29859
+ this.loggerService.log("highestProfitMarkdownService unsubscribe");
29860
+ if (this.subscribe.hasValue()) {
29861
+ const lastSubscription = this.subscribe();
29862
+ lastSubscription();
29863
+ }
29864
+ };
29865
+ this.tick = async (data) => {
29866
+ this.loggerService.log("highestProfitMarkdownService tick", { data });
29867
+ const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
29868
+ storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
29869
+ };
29870
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
29871
+ this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
29872
+ if (!this.subscribe.hasValue()) {
29873
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
29874
+ }
29875
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29876
+ return storage.getData();
29877
+ };
29878
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
29879
+ this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
29880
+ if (!this.subscribe.hasValue()) {
29881
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
29882
+ }
29883
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29884
+ return storage.getReport(symbol, strategyName, columns);
29885
+ };
29886
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
29887
+ this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
29888
+ if (!this.subscribe.hasValue()) {
29889
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
29890
+ }
29891
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29892
+ await storage.dump(symbol, strategyName, path, columns);
29893
+ };
29894
+ this.clear = async (payload) => {
29895
+ this.loggerService.log("highestProfitMarkdownService clear", { payload });
29005
29896
  if (payload) {
29006
29897
  const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29007
29898
  this.getStorage.clear(key);
@@ -29351,6 +30242,7 @@ class TimeMetaService {
29351
30242
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
29352
30243
  provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
29353
30244
  provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
30245
+ provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
29354
30246
  }
29355
30247
  {
29356
30248
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -29364,6 +30256,7 @@ class TimeMetaService {
29364
30256
  provide(TYPES.riskReportService, () => new RiskReportService());
29365
30257
  provide(TYPES.strategyReportService, () => new StrategyReportService());
29366
30258
  provide(TYPES.syncReportService, () => new SyncReportService());
30259
+ provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
29367
30260
  }
29368
30261
  {
29369
30262
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -29446,6 +30339,7 @@ const markdownServices = {
29446
30339
  riskMarkdownService: inject(TYPES.riskMarkdownService),
29447
30340
  strategyMarkdownService: inject(TYPES.strategyMarkdownService),
29448
30341
  syncMarkdownService: inject(TYPES.syncMarkdownService),
30342
+ highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
29449
30343
  };
29450
30344
  const reportServices = {
29451
30345
  backtestReportService: inject(TYPES.backtestReportService),
@@ -29459,6 +30353,7 @@ const reportServices = {
29459
30353
  riskReportService: inject(TYPES.riskReportService),
29460
30354
  strategyReportService: inject(TYPES.strategyReportService),
29461
30355
  syncReportService: inject(TYPES.syncReportService),
30356
+ highestProfitReportService: inject(TYPES.highestProfitReportService),
29462
30357
  };
29463
30358
  const validationServices = {
29464
30359
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -31230,7 +32125,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
31230
32125
  *
31231
32126
  * @param newStopLossPrice - Desired absolute stop-loss price
31232
32127
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31233
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32128
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31234
32129
  * @returns percentShift to pass to `commitTrailingStop`
31235
32130
  *
31236
32131
  * @example
@@ -31252,7 +32147,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
31252
32147
  *
31253
32148
  * @param newTakeProfitPrice - Desired absolute take-profit price
31254
32149
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31255
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32150
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31256
32151
  * @returns percentShift to pass to `commitTrailingTake`
31257
32152
  *
31258
32153
  * @example
@@ -31277,7 +32172,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
31277
32172
  *
31278
32173
  * @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
31279
32174
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31280
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32175
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31281
32176
  * @param position - Position direction: "long" or "short"
31282
32177
  * @returns Absolute stop-loss price corresponding to the given percentShift
31283
32178
  *
@@ -31304,7 +32199,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
31304
32199
  *
31305
32200
  * @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
31306
32201
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31307
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32202
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31308
32203
  * @param position - Position direction: "long" or "short"
31309
32204
  * @returns Absolute take-profit price corresponding to the given percentShift
31310
32205
  *
@@ -31343,7 +32238,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
31343
32238
  * Breakeven moves the SL to the effective entry price (effectivePriceOpen).
31344
32239
  * The value is the same regardless of position direction.
31345
32240
  *
31346
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32241
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31347
32242
  * @returns New stop-loss price equal to the effective entry price
31348
32243
  *
31349
32244
  * @example
@@ -32265,13 +33160,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
32265
33160
  const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
32266
33161
  const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
32267
33162
  const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
32268
- const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
33163
+ const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
32269
33164
  const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
32270
33165
  const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
32271
33166
  const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
32272
33167
  const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
32273
33168
  const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
32274
33169
  const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
33170
+ const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
33171
+ const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
33172
+ const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
33173
+ const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
33174
+ const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
33175
+ const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
33176
+ const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
33177
+ const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
33178
+ const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
32275
33179
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
32276
33180
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
32277
33181
  /**
@@ -32538,7 +33442,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
32538
33442
  if (!signal) {
32539
33443
  return false;
32540
33444
  }
32541
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33445
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32542
33446
  if (effectivePriceOpen === null) {
32543
33447
  return false;
32544
33448
  }
@@ -32618,7 +33522,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
32618
33522
  if (!signal) {
32619
33523
  return false;
32620
33524
  }
32621
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33525
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32622
33526
  if (effectivePriceOpen === null) {
32623
33527
  return false;
32624
33528
  }
@@ -32668,7 +33572,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
32668
33572
  if (!signal) {
32669
33573
  return false;
32670
33574
  }
32671
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33575
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32672
33576
  if (effectivePriceOpen === null) {
32673
33577
  return false;
32674
33578
  }
@@ -32719,7 +33623,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
32719
33623
  if (!signal) {
32720
33624
  return false;
32721
33625
  }
32722
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33626
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32723
33627
  if (effectivePriceOpen === null) {
32724
33628
  return false;
32725
33629
  }
@@ -32781,7 +33685,7 @@ async function commitBreakeven(symbol) {
32781
33685
  if (!signal) {
32782
33686
  return false;
32783
33687
  }
32784
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33688
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32785
33689
  if (effectivePriceOpen === null) {
32786
33690
  return false;
32787
33691
  }
@@ -33077,26 +33981,26 @@ async function getBreakeven(symbol, currentPrice) {
33077
33981
  *
33078
33982
  * @example
33079
33983
  * ```typescript
33080
- * import { getPositionAveragePrice } from "backtest-kit";
33984
+ * import { getPositionEffectivePrice } from "backtest-kit";
33081
33985
  *
33082
- * const avgPrice = await getPositionAveragePrice("BTCUSDT");
33986
+ * const avgPrice = await getPositionEffectivePrice("BTCUSDT");
33083
33987
  * // No DCA: avgPrice === priceOpen
33084
33988
  * // After DCA at lower price: avgPrice < priceOpen
33085
33989
  * ```
33086
33990
  */
33087
- async function getPositionAveragePrice(symbol) {
33991
+ async function getPositionEffectivePrice(symbol) {
33088
33992
  bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
33089
33993
  symbol,
33090
33994
  });
33091
33995
  if (!ExecutionContextService.hasContext()) {
33092
- throw new Error("getPositionAveragePrice requires an execution context");
33996
+ throw new Error("getPositionEffectivePrice requires an execution context");
33093
33997
  }
33094
33998
  if (!MethodContextService.hasContext()) {
33095
- throw new Error("getPositionAveragePrice requires a method context");
33999
+ throw new Error("getPositionEffectivePrice requires a method context");
33096
34000
  }
33097
34001
  const { backtest: isBacktest } = bt.executionContextService.context;
33098
34002
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33099
- return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34003
+ return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33100
34004
  }
33101
34005
  /**
33102
34006
  * Returns the number of DCA entries made for the current pending signal.
@@ -33451,6 +34355,284 @@ async function getPositionPartials(symbol) {
33451
34355
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33452
34356
  return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
33453
34357
  }
34358
+ /**
34359
+ * Returns the list of DCA entry prices and costs for the current pending signal.
34360
+ *
34361
+ * Each element represents a single position entry — the initial open or a subsequent
34362
+ * DCA entry added via commitAverageBuy.
34363
+ *
34364
+ * Returns null if no pending signal exists.
34365
+ * Returns a single-element array if no DCA entries were made.
34366
+ *
34367
+ * Each entry contains:
34368
+ * - `price` — execution price of this entry
34369
+ * - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
34370
+ *
34371
+ * @param symbol - Trading pair symbol
34372
+ * @returns Promise resolving to array of entry records or null
34373
+ *
34374
+ * @example
34375
+ * ```typescript
34376
+ * import { getPositionEntries } from "backtest-kit";
34377
+ *
34378
+ * const entries = await getPositionEntries("BTCUSDT");
34379
+ * // No DCA: [{ price: 43000, cost: 100 }]
34380
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
34381
+ * ```
34382
+ */
34383
+ async function getPositionEntries(symbol) {
34384
+ bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
34385
+ if (!ExecutionContextService.hasContext()) {
34386
+ throw new Error("getPositionEntries requires an execution context");
34387
+ }
34388
+ if (!MethodContextService.hasContext()) {
34389
+ throw new Error("getPositionEntries requires a method context");
34390
+ }
34391
+ const { backtest: isBacktest } = bt.executionContextService.context;
34392
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34393
+ return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
34394
+ }
34395
+ /**
34396
+ * Returns the original estimated duration for the current pending signal.
34397
+ *
34398
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
34399
+ * number of minutes the position is expected to be active before `time_expired`.
34400
+ *
34401
+ * Returns null if no pending signal exists.
34402
+ *
34403
+ * @param symbol - Trading pair symbol
34404
+ * @returns Promise resolving to estimated duration in minutes or null
34405
+ *
34406
+ * @example
34407
+ * ```typescript
34408
+ * import { getPositionEstimateMinutes } from "backtest-kit";
34409
+ *
34410
+ * const estimate = await getPositionEstimateMinutes("BTCUSDT");
34411
+ * // e.g. 120 (2 hours)
34412
+ * ```
34413
+ */
34414
+ async function getPositionEstimateMinutes(symbol) {
34415
+ bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
34416
+ if (!ExecutionContextService.hasContext()) {
34417
+ throw new Error("getPositionEstimateMinutes requires an execution context");
34418
+ }
34419
+ if (!MethodContextService.hasContext()) {
34420
+ throw new Error("getPositionEstimateMinutes requires a method context");
34421
+ }
34422
+ const { backtest: isBacktest } = bt.executionContextService.context;
34423
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34424
+ return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34425
+ }
34426
+ /**
34427
+ * Returns the remaining time before the position expires, clamped to zero.
34428
+ *
34429
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
34430
+ * Returns 0 once the estimate is exceeded (never negative).
34431
+ *
34432
+ * Returns null if no pending signal exists.
34433
+ *
34434
+ * @param symbol - Trading pair symbol
34435
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
34436
+ *
34437
+ * @example
34438
+ * ```typescript
34439
+ * import { getPositionCountdownMinutes } from "backtest-kit";
34440
+ *
34441
+ * const remaining = await getPositionCountdownMinutes("BTCUSDT");
34442
+ * // e.g. 45 (45 minutes left)
34443
+ * // 0 when expired
34444
+ * ```
34445
+ */
34446
+ async function getPositionCountdownMinutes(symbol) {
34447
+ bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
34448
+ if (!ExecutionContextService.hasContext()) {
34449
+ throw new Error("getPositionCountdownMinutes requires an execution context");
34450
+ }
34451
+ if (!MethodContextService.hasContext()) {
34452
+ throw new Error("getPositionCountdownMinutes requires a method context");
34453
+ }
34454
+ const { backtest: isBacktest } = bt.executionContextService.context;
34455
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34456
+ return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34457
+ }
34458
+ /**
34459
+ * Returns the best price reached in the profit direction during this position's life.
34460
+ *
34461
+ * Initialized at position open with the entry price and timestamp.
34462
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
34463
+ * - LONG: tracks the highest price seen above effective entry
34464
+ * - SHORT: tracks the lowest price seen below effective entry
34465
+ *
34466
+ * Returns null if no pending signal exists.
34467
+ * Never returns null when a signal is active — always contains at least the entry price.
34468
+ *
34469
+ * @param symbol - Trading pair symbol
34470
+ * @returns Promise resolving to `{ price, timestamp }` record or null
34471
+ *
34472
+ * @example
34473
+ * ```typescript
34474
+ * import { getPositionHighestProfitPrice } from "backtest-kit";
34475
+ *
34476
+ * const peak = await getPositionHighestProfitPrice("BTCUSDT");
34477
+ * // e.g. { price: 44500, timestamp: 1700000000000 }
34478
+ * ```
34479
+ */
34480
+ async function getPositionHighestProfitPrice(symbol) {
34481
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
34482
+ if (!ExecutionContextService.hasContext()) {
34483
+ throw new Error("getPositionHighestProfitPrice requires an execution context");
34484
+ }
34485
+ if (!MethodContextService.hasContext()) {
34486
+ throw new Error("getPositionHighestProfitPrice requires a method context");
34487
+ }
34488
+ const { backtest: isBacktest } = bt.executionContextService.context;
34489
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34490
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34491
+ }
34492
+ /**
34493
+ * Returns the timestamp when the best profit price was recorded during this position's life.
34494
+ *
34495
+ * Returns null if no pending signal exists.
34496
+ *
34497
+ * @param symbol - Trading pair symbol
34498
+ * @returns Promise resolving to timestamp in milliseconds or null
34499
+ *
34500
+ * @example
34501
+ * ```typescript
34502
+ * import { getPositionHighestProfitTimestamp } from "backtest-kit";
34503
+ *
34504
+ * const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
34505
+ * // e.g. 1700000000000
34506
+ * ```
34507
+ */
34508
+ async function getPositionHighestProfitTimestamp(symbol) {
34509
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
34510
+ if (!ExecutionContextService.hasContext()) {
34511
+ throw new Error("getPositionHighestProfitTimestamp requires an execution context");
34512
+ }
34513
+ if (!MethodContextService.hasContext()) {
34514
+ throw new Error("getPositionHighestProfitTimestamp requires a method context");
34515
+ }
34516
+ const { backtest: isBacktest } = bt.executionContextService.context;
34517
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34518
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
34519
+ }
34520
+ /**
34521
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
34522
+ *
34523
+ * Returns null if no pending signal exists.
34524
+ *
34525
+ * @param symbol - Trading pair symbol
34526
+ * @returns Promise resolving to PnL percentage or null
34527
+ *
34528
+ * @example
34529
+ * ```typescript
34530
+ * import { getPositionHighestPnlPercentage } from "backtest-kit";
34531
+ *
34532
+ * const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
34533
+ * // e.g. 3.5
34534
+ * ```
34535
+ */
34536
+ async function getPositionHighestPnlPercentage(symbol) {
34537
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
34538
+ if (!ExecutionContextService.hasContext()) {
34539
+ throw new Error("getPositionHighestPnlPercentage requires an execution context");
34540
+ }
34541
+ if (!MethodContextService.hasContext()) {
34542
+ throw new Error("getPositionHighestPnlPercentage requires a method context");
34543
+ }
34544
+ const { backtest: isBacktest } = bt.executionContextService.context;
34545
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34546
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
34547
+ }
34548
+ /**
34549
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
34550
+ *
34551
+ * Returns null if no pending signal exists.
34552
+ *
34553
+ * @param symbol - Trading pair symbol
34554
+ * @returns Promise resolving to PnL cost or null
34555
+ *
34556
+ * @example
34557
+ * ```typescript
34558
+ * import { getPositionHighestPnlCost } from "backtest-kit";
34559
+ *
34560
+ * const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
34561
+ * // e.g. 35.5
34562
+ * ```
34563
+ */
34564
+ async function getPositionHighestPnlCost(symbol) {
34565
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
34566
+ if (!ExecutionContextService.hasContext()) {
34567
+ throw new Error("getPositionHighestPnlCost requires an execution context");
34568
+ }
34569
+ if (!MethodContextService.hasContext()) {
34570
+ throw new Error("getPositionHighestPnlCost requires a method context");
34571
+ }
34572
+ const { backtest: isBacktest } = bt.executionContextService.context;
34573
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34574
+ return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
34575
+ }
34576
+ /**
34577
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
34578
+ *
34579
+ * Returns null if no pending signal exists.
34580
+ *
34581
+ * @param symbol - Trading pair symbol
34582
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
34583
+ *
34584
+ * @example
34585
+ * ```typescript
34586
+ * import { getPositionHighestProfitBreakeven } from "backtest-kit";
34587
+ *
34588
+ * const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
34589
+ * // e.g. true (price reached the breakeven threshold at its peak)
34590
+ * ```
34591
+ */
34592
+ async function getPositionHighestProfitBreakeven(symbol) {
34593
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
34594
+ if (!ExecutionContextService.hasContext()) {
34595
+ throw new Error("getPositionHighestProfitBreakeven requires an execution context");
34596
+ }
34597
+ if (!MethodContextService.hasContext()) {
34598
+ throw new Error("getPositionHighestProfitBreakeven requires a method context");
34599
+ }
34600
+ const { backtest: isBacktest } = bt.executionContextService.context;
34601
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34602
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
34603
+ }
34604
+ /**
34605
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
34606
+ *
34607
+ * Measures how long the position has been pulling back from its peak profit level.
34608
+ * Zero when called at the exact moment the peak was set.
34609
+ * Grows continuously as price moves away from the peak without setting a new record.
34610
+ *
34611
+ * Returns null if no pending signal exists.
34612
+ *
34613
+ * @param symbol - Trading pair symbol
34614
+ * @returns Promise resolving to drawdown duration in minutes or null
34615
+ *
34616
+ * @example
34617
+ * ```typescript
34618
+ * import { getPositionDrawdownMinutes } from "backtest-kit";
34619
+ *
34620
+ * const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
34621
+ * // e.g. 30 (30 minutes since the highest profit price)
34622
+ * ```
34623
+ */
34624
+ async function getPositionDrawdownMinutes(symbol) {
34625
+ bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
34626
+ if (!ExecutionContextService.hasContext()) {
34627
+ throw new Error("getPositionDrawdownMinutes requires an execution context");
34628
+ }
34629
+ if (!MethodContextService.hasContext()) {
34630
+ throw new Error("getPositionDrawdownMinutes requires a method context");
34631
+ }
34632
+ const { backtest: isBacktest } = bt.executionContextService.context;
34633
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34634
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34635
+ }
33454
34636
  /**
33455
34637
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
33456
34638
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -33628,6 +34810,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
33628
34810
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
33629
34811
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
33630
34812
  const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
34813
+ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
34814
+ const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
33631
34815
  /**
33632
34816
  * Subscribes to all signal events with queued async processing.
33633
34817
  *
@@ -34754,6 +35938,33 @@ function listenSyncOnce(filterFn, fn) {
34754
35938
  bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
34755
35939
  return syncSubject.filter(filterFn).once(fn);
34756
35940
  }
35941
+ /**
35942
+ * Subscribes to highest profit events with queued async processing.
35943
+ * Emits when a signal reaches a new highest profit level during its lifecycle.
35944
+ * Events are processed sequentially in order received, even if callback is async.
35945
+ * Uses queued wrapper to prevent concurrent execution of the callback.
35946
+ * Useful for tracking profit milestones and implementing dynamic management logic.
35947
+ *
35948
+ * @param fn - Callback function to handle highest profit events
35949
+ * @return Unsubscribe function to stop listening to events
35950
+ */
35951
+ function listenHighestProfit(fn) {
35952
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
35953
+ return highestProfitSubject.subscribe(queued(async (event) => fn(event)));
35954
+ }
35955
+ /**
35956
+ * Subscribes to filtered highest profit events with one-time execution.
35957
+ * Listens for events matching the filter predicate, then executes callback once
35958
+ * and automatically unsubscribes. Useful for waiting for specific profit conditions.
35959
+ *
35960
+ * @param filterFn - Predicate to filter which events trigger the callback
35961
+ * @param fn - Callback function to handle the filtered event (called only once)
35962
+ * @returns Unsubscribe function to cancel the listener before it fires
35963
+ */
35964
+ function listenHighestProfitOnce(filterFn, fn) {
35965
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
35966
+ return highestProfitSubject.filter(filterFn).once(fn);
35967
+ }
34757
35968
 
34758
35969
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
34759
35970
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -34767,7 +35978,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
34767
35978
  const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
34768
35979
  const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
34769
35980
  const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
34770
- const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
35981
+ const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
34771
35982
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
34772
35983
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
34773
35984
  const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
@@ -34775,6 +35986,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
34775
35986
  const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
34776
35987
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
34777
35988
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
35989
+ const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
35990
+ const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
35991
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
35992
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
35993
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
35994
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
35995
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
35996
+ const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
34778
35997
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
34779
35998
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
34780
35999
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -35347,7 +36566,7 @@ class BacktestUtils {
35347
36566
  * @param context - Execution context with strategyName, exchangeName, and frameName
35348
36567
  * @returns Effective entry price, or null if no active position
35349
36568
  */
35350
- this.getPositionAveragePrice = async (symbol, context) => {
36569
+ this.getPositionEffectivePrice = async (symbol, context) => {
35351
36570
  bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
35352
36571
  symbol,
35353
36572
  context,
@@ -35363,7 +36582,7 @@ class BacktestUtils {
35363
36582
  actions &&
35364
36583
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
35365
36584
  }
35366
- return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
36585
+ return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
35367
36586
  };
35368
36587
  /**
35369
36588
  * Returns the total number of base-asset units currently held in the position.
@@ -35581,6 +36800,230 @@ class BacktestUtils {
35581
36800
  }
35582
36801
  return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
35583
36802
  };
36803
+ /**
36804
+ * Returns the original estimated duration for the current pending signal.
36805
+ *
36806
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
36807
+ * number of minutes the position is expected to be active before `time_expired`.
36808
+ *
36809
+ * Returns null if no pending signal exists.
36810
+ *
36811
+ * @param symbol - Trading pair symbol
36812
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36813
+ * @returns Estimated duration in minutes, or null if no active position
36814
+ */
36815
+ this.getPositionEstimateMinutes = async (symbol, context) => {
36816
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
36817
+ symbol,
36818
+ context,
36819
+ });
36820
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36821
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36822
+ {
36823
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36824
+ riskName &&
36825
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36826
+ riskList &&
36827
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36828
+ actions &&
36829
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36830
+ }
36831
+ return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
36832
+ };
36833
+ /**
36834
+ * Returns the remaining time before the position expires, clamped to zero.
36835
+ *
36836
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
36837
+ * Returns 0 once the estimate is exceeded (never negative).
36838
+ *
36839
+ * Returns null if no pending signal exists.
36840
+ *
36841
+ * @param symbol - Trading pair symbol
36842
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36843
+ * @returns Remaining minutes (≥ 0), or null if no active position
36844
+ */
36845
+ this.getPositionCountdownMinutes = async (symbol, context) => {
36846
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
36847
+ symbol,
36848
+ context,
36849
+ });
36850
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36851
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36852
+ {
36853
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36854
+ riskName &&
36855
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36856
+ riskList &&
36857
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36858
+ actions &&
36859
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36860
+ }
36861
+ return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
36862
+ };
36863
+ /**
36864
+ * Returns the best price reached in the profit direction during this position's life.
36865
+ *
36866
+ * Returns null if no pending signal exists.
36867
+ *
36868
+ * @param symbol - Trading pair symbol
36869
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36870
+ * @returns price or null if no active position
36871
+ */
36872
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
36873
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
36874
+ symbol,
36875
+ context,
36876
+ });
36877
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36878
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36879
+ {
36880
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36881
+ riskName &&
36882
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36883
+ riskList &&
36884
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36885
+ actions &&
36886
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36887
+ }
36888
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
36889
+ };
36890
+ /**
36891
+ * Returns the timestamp when the best profit price was recorded during this position's life.
36892
+ *
36893
+ * Returns null if no pending signal exists.
36894
+ *
36895
+ * @param symbol - Trading pair symbol
36896
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36897
+ * @returns timestamp in milliseconds or null if no active position
36898
+ */
36899
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
36900
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
36901
+ symbol,
36902
+ context,
36903
+ });
36904
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36905
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36906
+ {
36907
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36908
+ riskName &&
36909
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36910
+ riskList &&
36911
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36912
+ actions &&
36913
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36914
+ }
36915
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
36916
+ };
36917
+ /**
36918
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
36919
+ *
36920
+ * Returns null if no pending signal exists.
36921
+ *
36922
+ * @param symbol - Trading pair symbol
36923
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36924
+ * @returns PnL percentage or null if no active position
36925
+ */
36926
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
36927
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
36928
+ symbol,
36929
+ context,
36930
+ });
36931
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36932
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36933
+ {
36934
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36935
+ riskName &&
36936
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36937
+ riskList &&
36938
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36939
+ actions &&
36940
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36941
+ }
36942
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
36943
+ };
36944
+ /**
36945
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
36946
+ *
36947
+ * Returns null if no pending signal exists.
36948
+ *
36949
+ * @param symbol - Trading pair symbol
36950
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36951
+ * @returns PnL cost or null if no active position
36952
+ */
36953
+ this.getPositionHighestPnlCost = async (symbol, context) => {
36954
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
36955
+ symbol,
36956
+ context,
36957
+ });
36958
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36959
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36960
+ {
36961
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36962
+ riskName &&
36963
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36964
+ riskList &&
36965
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36966
+ actions &&
36967
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36968
+ }
36969
+ return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
36970
+ };
36971
+ /**
36972
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
36973
+ *
36974
+ * @param symbol - Trading pair symbol
36975
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36976
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
36977
+ */
36978
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
36979
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
36980
+ symbol,
36981
+ context,
36982
+ });
36983
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
36984
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
36985
+ {
36986
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36987
+ riskName &&
36988
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
36989
+ riskList &&
36990
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
36991
+ actions &&
36992
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
36993
+ }
36994
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
36995
+ };
36996
+ /**
36997
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
36998
+ *
36999
+ * Measures how long the position has been pulling back from its peak profit level.
37000
+ * Zero when called at the exact moment the peak was set.
37001
+ * Grows continuously as price moves away from the peak without setting a new record.
37002
+ *
37003
+ * Returns null if no pending signal exists.
37004
+ *
37005
+ * @param symbol - Trading pair symbol
37006
+ * @param context - Execution context with strategyName, exchangeName, and frameName
37007
+ * @returns Drawdown duration in minutes, or null if no active position
37008
+ */
37009
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
37010
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
37011
+ symbol,
37012
+ context,
37013
+ });
37014
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37015
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37016
+ {
37017
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37018
+ riskName &&
37019
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37020
+ riskList &&
37021
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37022
+ actions &&
37023
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37024
+ }
37025
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
37026
+ };
35584
37027
  /**
35585
37028
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
35586
37029
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -36142,7 +37585,7 @@ class BacktestUtils {
36142
37585
  if (!signal) {
36143
37586
  return false;
36144
37587
  }
36145
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37588
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36146
37589
  if (effectivePriceOpen === null) {
36147
37590
  return false;
36148
37591
  }
@@ -36227,7 +37670,7 @@ class BacktestUtils {
36227
37670
  if (!signal) {
36228
37671
  return false;
36229
37672
  }
36230
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37673
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36231
37674
  if (effectivePriceOpen === null) {
36232
37675
  return false;
36233
37676
  }
@@ -36280,7 +37723,7 @@ class BacktestUtils {
36280
37723
  if (!signal) {
36281
37724
  return false;
36282
37725
  }
36283
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37726
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36284
37727
  if (effectivePriceOpen === null) {
36285
37728
  return false;
36286
37729
  }
@@ -36334,7 +37777,7 @@ class BacktestUtils {
36334
37777
  if (!signal) {
36335
37778
  return false;
36336
37779
  }
36337
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37780
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36338
37781
  if (effectivePriceOpen === null) {
36339
37782
  return false;
36340
37783
  }
@@ -36396,7 +37839,7 @@ class BacktestUtils {
36396
37839
  if (!signal) {
36397
37840
  return false;
36398
37841
  }
36399
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37842
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36400
37843
  if (effectivePriceOpen === null) {
36401
37844
  return false;
36402
37845
  }
@@ -36684,7 +38127,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
36684
38127
  const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
36685
38128
  const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
36686
38129
  const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
36687
- const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
38130
+ const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
36688
38131
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
36689
38132
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
36690
38133
  const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
@@ -36692,6 +38135,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
36692
38135
  const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
36693
38136
  const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
36694
38137
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
38138
+ const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
38139
+ const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
38140
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
38141
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
38142
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
38143
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
38144
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
38145
+ const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
36695
38146
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
36696
38147
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
36697
38148
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -37295,7 +38746,7 @@ class LiveUtils {
37295
38746
  * @param context - Execution context with strategyName and exchangeName
37296
38747
  * @returns Effective entry price, or null if no active position
37297
38748
  */
37298
- this.getPositionAveragePrice = async (symbol, context) => {
38749
+ this.getPositionEffectivePrice = async (symbol, context) => {
37299
38750
  bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
37300
38751
  symbol,
37301
38752
  context,
@@ -37311,7 +38762,7 @@ class LiveUtils {
37311
38762
  actions &&
37312
38763
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
37313
38764
  }
37314
- return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
38765
+ return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
37315
38766
  strategyName: context.strategyName,
37316
38767
  exchangeName: context.exchangeName,
37317
38768
  frameName: "",
@@ -37561,6 +39012,262 @@ class LiveUtils {
37561
39012
  frameName: "",
37562
39013
  });
37563
39014
  };
39015
+ /**
39016
+ * Returns the original estimated duration for the current pending signal.
39017
+ *
39018
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
39019
+ * number of minutes the position is expected to be active before `time_expired`.
39020
+ *
39021
+ * Returns null if no pending signal exists.
39022
+ *
39023
+ * @param symbol - Trading pair symbol
39024
+ * @param context - Execution context with strategyName and exchangeName
39025
+ * @returns Estimated duration in minutes, or null if no active position
39026
+ */
39027
+ this.getPositionEstimateMinutes = async (symbol, context) => {
39028
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
39029
+ symbol,
39030
+ context,
39031
+ });
39032
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39033
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39034
+ {
39035
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39036
+ riskName &&
39037
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39038
+ riskList &&
39039
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39040
+ actions &&
39041
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39042
+ }
39043
+ return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
39044
+ strategyName: context.strategyName,
39045
+ exchangeName: context.exchangeName,
39046
+ frameName: "",
39047
+ });
39048
+ };
39049
+ /**
39050
+ * Returns the remaining time before the position expires, clamped to zero.
39051
+ *
39052
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
39053
+ * Returns 0 once the estimate is exceeded (never negative).
39054
+ *
39055
+ * Returns null if no pending signal exists.
39056
+ *
39057
+ * @param symbol - Trading pair symbol
39058
+ * @param context - Execution context with strategyName and exchangeName
39059
+ * @returns Remaining minutes (≥ 0), or null if no active position
39060
+ */
39061
+ this.getPositionCountdownMinutes = async (symbol, context) => {
39062
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
39063
+ symbol,
39064
+ context,
39065
+ });
39066
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39067
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39068
+ {
39069
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39070
+ riskName &&
39071
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39072
+ riskList &&
39073
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39074
+ actions &&
39075
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39076
+ }
39077
+ return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
39078
+ strategyName: context.strategyName,
39079
+ exchangeName: context.exchangeName,
39080
+ frameName: "",
39081
+ });
39082
+ };
39083
+ /**
39084
+ * Returns the best price reached in the profit direction during this position's life.
39085
+ *
39086
+ * Returns null if no pending signal exists.
39087
+ *
39088
+ * @param symbol - Trading pair symbol
39089
+ * @param context - Execution context with strategyName and exchangeName
39090
+ * @returns price or null if no active position
39091
+ */
39092
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
39093
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
39094
+ symbol,
39095
+ context,
39096
+ });
39097
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39098
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39099
+ {
39100
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39101
+ riskName &&
39102
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39103
+ riskList &&
39104
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39105
+ actions &&
39106
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39107
+ }
39108
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
39109
+ strategyName: context.strategyName,
39110
+ exchangeName: context.exchangeName,
39111
+ frameName: "",
39112
+ });
39113
+ };
39114
+ /**
39115
+ * Returns the timestamp when the best profit price was recorded during this position's life.
39116
+ *
39117
+ * Returns null if no pending signal exists.
39118
+ *
39119
+ * @param symbol - Trading pair symbol
39120
+ * @param context - Execution context with strategyName and exchangeName
39121
+ * @returns timestamp in milliseconds or null if no active position
39122
+ */
39123
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
39124
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
39125
+ symbol,
39126
+ context,
39127
+ });
39128
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39129
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39130
+ {
39131
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39132
+ riskName &&
39133
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39134
+ riskList &&
39135
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39136
+ actions &&
39137
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39138
+ }
39139
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
39140
+ strategyName: context.strategyName,
39141
+ exchangeName: context.exchangeName,
39142
+ frameName: "",
39143
+ });
39144
+ };
39145
+ /**
39146
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
39147
+ *
39148
+ * Returns null if no pending signal exists.
39149
+ *
39150
+ * @param symbol - Trading pair symbol
39151
+ * @param context - Execution context with strategyName and exchangeName
39152
+ * @returns PnL percentage or null if no active position
39153
+ */
39154
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
39155
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
39156
+ symbol,
39157
+ context,
39158
+ });
39159
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39160
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39161
+ {
39162
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39163
+ riskName &&
39164
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39165
+ riskList &&
39166
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39167
+ actions &&
39168
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39169
+ }
39170
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
39171
+ strategyName: context.strategyName,
39172
+ exchangeName: context.exchangeName,
39173
+ frameName: "",
39174
+ });
39175
+ };
39176
+ /**
39177
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
39178
+ *
39179
+ * Returns null if no pending signal exists.
39180
+ *
39181
+ * @param symbol - Trading pair symbol
39182
+ * @param context - Execution context with strategyName and exchangeName
39183
+ * @returns PnL cost or null if no active position
39184
+ */
39185
+ this.getPositionHighestPnlCost = async (symbol, context) => {
39186
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
39187
+ symbol,
39188
+ context,
39189
+ });
39190
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39191
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39192
+ {
39193
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39194
+ riskName &&
39195
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39196
+ riskList &&
39197
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39198
+ actions &&
39199
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39200
+ }
39201
+ return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
39202
+ strategyName: context.strategyName,
39203
+ exchangeName: context.exchangeName,
39204
+ frameName: "",
39205
+ });
39206
+ };
39207
+ /**
39208
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
39209
+ *
39210
+ * @param symbol - Trading pair symbol
39211
+ * @param context - Execution context with strategyName and exchangeName
39212
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
39213
+ */
39214
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
39215
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
39216
+ symbol,
39217
+ context,
39218
+ });
39219
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39220
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39221
+ {
39222
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39223
+ riskName &&
39224
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39225
+ riskList &&
39226
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39227
+ actions &&
39228
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39229
+ }
39230
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
39231
+ strategyName: context.strategyName,
39232
+ exchangeName: context.exchangeName,
39233
+ frameName: "",
39234
+ });
39235
+ };
39236
+ /**
39237
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
39238
+ *
39239
+ * Measures how long the position has been pulling back from its peak profit level.
39240
+ * Zero when called at the exact moment the peak was set.
39241
+ * Grows continuously as price moves away from the peak without setting a new record.
39242
+ *
39243
+ * Returns null if no pending signal exists.
39244
+ *
39245
+ * @param symbol - Trading pair symbol
39246
+ * @param context - Execution context with strategyName and exchangeName
39247
+ * @returns Drawdown duration in minutes, or null if no active position
39248
+ */
39249
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
39250
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
39251
+ symbol,
39252
+ context,
39253
+ });
39254
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39255
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39256
+ {
39257
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39258
+ riskName &&
39259
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39260
+ riskList &&
39261
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39262
+ actions &&
39263
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39264
+ }
39265
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
39266
+ strategyName: context.strategyName,
39267
+ exchangeName: context.exchangeName,
39268
+ frameName: "",
39269
+ });
39270
+ };
37564
39271
  /**
37565
39272
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
37566
39273
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38215,7 +39922,7 @@ class LiveUtils {
38215
39922
  if (!signal) {
38216
39923
  return false;
38217
39924
  }
38218
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
39925
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38219
39926
  strategyName: context.strategyName,
38220
39927
  exchangeName: context.exchangeName,
38221
39928
  frameName: "",
@@ -38315,7 +40022,7 @@ class LiveUtils {
38315
40022
  if (!signal) {
38316
40023
  return false;
38317
40024
  }
38318
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40025
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38319
40026
  strategyName: context.strategyName,
38320
40027
  exchangeName: context.exchangeName,
38321
40028
  frameName: "",
@@ -38384,7 +40091,7 @@ class LiveUtils {
38384
40091
  if (!signal) {
38385
40092
  return false;
38386
40093
  }
38387
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40094
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38388
40095
  strategyName: context.strategyName,
38389
40096
  exchangeName: context.exchangeName,
38390
40097
  frameName: "",
@@ -38454,7 +40161,7 @@ class LiveUtils {
38454
40161
  if (!signal) {
38455
40162
  return false;
38456
40163
  }
38457
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40164
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38458
40165
  strategyName: context.strategyName,
38459
40166
  exchangeName: context.exchangeName,
38460
40167
  frameName: "",
@@ -38532,7 +40239,7 @@ class LiveUtils {
38532
40239
  if (!signal) {
38533
40240
  return false;
38534
40241
  }
38535
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40242
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38536
40243
  strategyName: context.strategyName,
38537
40244
  exchangeName: context.exchangeName,
38538
40245
  frameName: "",
@@ -41956,6 +43663,98 @@ class PartialUtils {
41956
43663
  */
41957
43664
  const Partial = new PartialUtils();
41958
43665
 
43666
+ const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
43667
+ const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
43668
+ const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
43669
+ /**
43670
+ * Utility class for accessing highest profit reports and statistics.
43671
+ *
43672
+ * Provides static-like methods (via singleton instance) to retrieve data
43673
+ * accumulated by HighestProfitMarkdownService from highestProfitSubject events.
43674
+ *
43675
+ * @example
43676
+ * ```typescript
43677
+ * import { HighestProfit } from "backtest-kit";
43678
+ *
43679
+ * const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
43680
+ * const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
43681
+ * await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
43682
+ * ```
43683
+ */
43684
+ class HighestProfitUtils {
43685
+ constructor() {
43686
+ /**
43687
+ * Retrieves statistical data from accumulated highest profit events.
43688
+ *
43689
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43690
+ * @param context - Execution context
43691
+ * @param backtest - Whether to query backtest data
43692
+ * @returns Promise resolving to HighestProfitStatisticsModel
43693
+ */
43694
+ this.getData = async (symbol, context, backtest = false) => {
43695
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
43696
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43697
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43698
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43699
+ {
43700
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43701
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43702
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43703
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43704
+ }
43705
+ return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
43706
+ };
43707
+ /**
43708
+ * Generates a markdown report with all highest profit events for a symbol-strategy pair.
43709
+ *
43710
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43711
+ * @param context - Execution context
43712
+ * @param backtest - Whether to query backtest data
43713
+ * @param columns - Optional column configuration
43714
+ * @returns Promise resolving to markdown formatted report string
43715
+ */
43716
+ this.getReport = async (symbol, context, backtest = false, columns) => {
43717
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
43718
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43719
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43720
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43721
+ {
43722
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43723
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43724
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43725
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43726
+ }
43727
+ return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
43728
+ };
43729
+ /**
43730
+ * Generates and saves a markdown report to file.
43731
+ *
43732
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43733
+ * @param context - Execution context
43734
+ * @param backtest - Whether to query backtest data
43735
+ * @param path - Output directory path (default: "./dump/highest_profit")
43736
+ * @param columns - Optional column configuration
43737
+ */
43738
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
43739
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
43740
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43741
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43742
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43743
+ {
43744
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43745
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43746
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43747
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43748
+ }
43749
+ await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
43750
+ };
43751
+ }
43752
+ }
43753
+ /**
43754
+ * Global singleton instance of HighestProfitUtils.
43755
+ */
43756
+ const HighestProfit = new HighestProfitUtils();
43757
+
41959
43758
  /**
41960
43759
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
41961
43760
  *
@@ -46423,4 +48222,4 @@ const percentValue = (yesterdayValue, todayValue) => {
46423
48222
  return yesterdayValue / todayValue - 1;
46424
48223
  };
46425
48224
 
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 };
48225
+ 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 };