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.cjs CHANGED
@@ -127,6 +127,7 @@ const markdownServices$1 = {
127
127
  riskMarkdownService: Symbol('riskMarkdownService'),
128
128
  strategyMarkdownService: Symbol('strategyMarkdownService'),
129
129
  syncMarkdownService: Symbol('syncMarkdownService'),
130
+ highestProfitMarkdownService: Symbol('highestProfitMarkdownService'),
130
131
  };
131
132
  const reportServices$1 = {
132
133
  backtestReportService: Symbol('backtestReportService'),
@@ -140,6 +141,7 @@ const reportServices$1 = {
140
141
  riskReportService: Symbol('riskReportService'),
141
142
  strategyReportService: Symbol('strategyReportService'),
142
143
  syncReportService: Symbol('syncReportService'),
144
+ highestProfitReportService: Symbol('highestProfitReportService'),
143
145
  };
144
146
  const validationServices$1 = {
145
147
  exchangeValidationService: Symbol('exchangeValidationService'),
@@ -642,6 +644,12 @@ const strategyCommitSubject = new functoolsKit.Subject();
642
644
  * BacktestLogicPrivateService::*run
643
645
  */
644
646
  const backtestScheduleOpenSubject = new functoolsKit.Subject();
647
+ /**
648
+ * Highest profit emitter for real-time profit tracking.
649
+ * Emits updates on the highest profit achieved for an open position.
650
+ * Allows users to track profit milestones and implement custom management logic based on profit levels.
651
+ */
652
+ const highestProfitSubject = new functoolsKit.Subject();
645
653
 
646
654
  var emitters = /*#__PURE__*/Object.freeze({
647
655
  __proto__: null,
@@ -653,6 +661,7 @@ var emitters = /*#__PURE__*/Object.freeze({
653
661
  doneWalkerSubject: doneWalkerSubject,
654
662
  errorEmitter: errorEmitter,
655
663
  exitEmitter: exitEmitter,
664
+ highestProfitSubject: highestProfitSubject,
656
665
  partialLossSubject: partialLossSubject,
657
666
  partialProfitSubject: partialProfitSubject,
658
667
  performanceEmitter: performanceEmitter,
@@ -4271,6 +4280,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4271
4280
  timestamp: currentTime,
4272
4281
  _isScheduled: false,
4273
4282
  _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4283
+ _peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4274
4284
  };
4275
4285
  // Валидируем сигнал перед возвратом
4276
4286
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -4295,6 +4305,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4295
4305
  timestamp: currentTime,
4296
4306
  _isScheduled: true,
4297
4307
  _entry: [{ price: signal.priceOpen, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4308
+ _peak: { price: signal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4298
4309
  };
4299
4310
  // Валидируем сигнал перед возвратом
4300
4311
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -4315,6 +4326,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
4315
4326
  timestamp: currentTime,
4316
4327
  _isScheduled: false,
4317
4328
  _entry: [{ price: currentPrice, cost: signal.cost ?? GLOBAL_CONFIG.CC_POSITION_ENTRY_COST, timestamp: currentTime }],
4329
+ _peak: { price: currentPrice, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
4318
4330
  };
4319
4331
  // Валидируем сигнал перед возвратом
4320
4332
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -5017,6 +5029,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
5017
5029
  ...scheduled,
5018
5030
  pendingAt: activationTime,
5019
5031
  _isScheduled: false,
5032
+ _peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
5020
5033
  };
5021
5034
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5022
5035
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
@@ -5653,7 +5666,7 @@ const CLOSE_PENDING_SIGNAL_FN = async (self, signal, currentPrice, closeReason)
5653
5666
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
5654
5667
  return result;
5655
5668
  };
5656
- const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5669
+ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice, backtest) => {
5657
5670
  let percentTp = 0;
5658
5671
  let percentSl = 0;
5659
5672
  const currentTime = self.params.execution.context.when.getTime();
@@ -5674,6 +5687,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5674
5687
  const tpDistance = effectiveTakeProfit - effectivePriceOpen;
5675
5688
  const progressPercent = (currentDistance / tpDistance) * 100;
5676
5689
  percentTp = Math.min(progressPercent, 100);
5690
+ if (currentPrice > signal._peak.price) {
5691
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
5692
+ signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
5693
+ if (self.params.callbacks?.onWrite) {
5694
+ self.params.callbacks.onWrite(signal.symbol, signal, backtest);
5695
+ }
5696
+ !backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
5697
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
5698
+ }
5677
5699
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
5678
5700
  }
5679
5701
  else if (currentDistance < 0) {
@@ -5698,6 +5720,15 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5698
5720
  const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5699
5721
  const progressPercent = (currentDistance / tpDistance) * 100;
5700
5722
  percentTp = Math.min(progressPercent, 100);
5723
+ if (currentPrice < signal._peak.price) {
5724
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, currentPrice);
5725
+ signal._peak = { price: currentPrice, timestamp: currentTime, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
5726
+ if (self.params.callbacks?.onWrite) {
5727
+ self.params.callbacks.onWrite(signal.symbol, signal, backtest);
5728
+ }
5729
+ !backtest && await PersistSignalAdapter.writeSignalData(signal, self.params.execution.context.symbol, self.params.strategyName, self.params.exchangeName);
5730
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, currentPrice), currentPrice, currentTime);
5731
+ }
5701
5732
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
5702
5733
  }
5703
5734
  if (currentDistance < 0) {
@@ -5745,7 +5776,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
5745
5776
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
5746
5777
  return result;
5747
5778
  };
5748
- const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason) => {
5779
+ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
5749
5780
  self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
5750
5781
  symbol: self.params.execution.context.symbol,
5751
5782
  signalId: scheduled.id,
@@ -5755,6 +5786,23 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
5755
5786
  reason,
5756
5787
  });
5757
5788
  await self.setScheduledSignal(null);
5789
+ if (reason === "user") {
5790
+ await CALL_COMMIT_FN(self, {
5791
+ action: "cancel-scheduled",
5792
+ symbol: self.params.execution.context.symbol,
5793
+ strategyName: self.params.strategyName,
5794
+ exchangeName: self.params.exchangeName,
5795
+ frameName: self.params.frameName,
5796
+ signalId: scheduled.id,
5797
+ backtest: true,
5798
+ cancelId,
5799
+ timestamp: closeTimestamp,
5800
+ totalEntries: scheduled._entry?.length ?? 1,
5801
+ totalPartials: scheduled._partial?.length ?? 0,
5802
+ originalPriceOpen: scheduled.priceOpen,
5803
+ pnl: toProfitLossDto(scheduled, averagePrice),
5804
+ });
5805
+ }
5758
5806
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
5759
5807
  const result = {
5760
5808
  action: "cancelled",
@@ -5767,6 +5815,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
5767
5815
  symbol: self.params.execution.context.symbol,
5768
5816
  backtest: self.params.execution.context.backtest,
5769
5817
  reason,
5818
+ cancelId,
5770
5819
  createdAt: closeTimestamp,
5771
5820
  };
5772
5821
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, closeTimestamp, self.params.execution.context.backtest);
@@ -5806,6 +5855,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
5806
5855
  ...scheduled,
5807
5856
  pendingAt: activationTime,
5808
5857
  _isScheduled: false,
5858
+ _peak: { price: scheduled.priceOpen, timestamp: activationTime, pnlPercentage: 0, pnlCost: 0 },
5809
5859
  };
5810
5860
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5811
5861
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(activationTime, activatedSignal.priceOpen, activatedSignal, self);
@@ -5955,7 +6005,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5955
6005
  // КРИТИЧНО: Проверяем был ли сигнал отменен пользователем через cancel()
5956
6006
  if (self._cancelledSignal) {
5957
6007
  // Сигнал был отменен через cancel() в onSchedulePing
5958
- const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user");
6008
+ const cancelId = self._cancelledSignal.cancelId;
6009
+ const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
5959
6010
  return { outcome: "cancelled", result };
5960
6011
  }
5961
6012
  // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
@@ -5985,6 +6036,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5985
6036
  ...activatedSignal,
5986
6037
  pendingAt: candle.timestamp,
5987
6038
  _isScheduled: false,
6039
+ _peak: { price: activatedSignal.priceOpen, timestamp: candle.timestamp, pnlPercentage: 0, pnlCost: 0 },
5988
6040
  };
5989
6041
  // Sync open: if external system rejects — cancel scheduled signal instead of opening
5990
6042
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(candle.timestamp, pendingSignal.priceOpen, pendingSignal, self);
@@ -6186,6 +6238,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
6186
6238
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6187
6239
  const tpDistance = effectiveTakeProfit - effectivePriceOpen;
6188
6240
  const progressPercent = (currentDistance / tpDistance) * 100;
6241
+ if (averagePrice > signal._peak.price) {
6242
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
6243
+ signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
6244
+ if (self.params.callbacks?.onWrite) {
6245
+ self.params.callbacks.onWrite(signal.symbol, signal, true);
6246
+ }
6247
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
6248
+ }
6189
6249
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
6190
6250
  }
6191
6251
  else if (currentDistance < 0) {
@@ -6208,6 +6268,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
6208
6268
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6209
6269
  const tpDistance = effectivePriceOpen - effectiveTakeProfit;
6210
6270
  const progressPercent = (currentDistance / tpDistance) * 100;
6271
+ if (averagePrice < signal._peak.price) {
6272
+ const { pnl } = TO_PUBLIC_SIGNAL(signal, averagePrice);
6273
+ signal._peak = { price: averagePrice, timestamp: currentCandleTimestamp, pnlCost: pnl.pnlCost, pnlPercentage: pnl.pnlPercentage };
6274
+ if (self.params.callbacks?.onWrite) {
6275
+ self.params.callbacks.onWrite(signal.symbol, signal, true);
6276
+ }
6277
+ await self.params.onHighestProfit(TO_PUBLIC_SIGNAL(signal, averagePrice), averagePrice, currentCandleTimestamp);
6278
+ }
6211
6279
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
6212
6280
  }
6213
6281
  if (currentDistance < 0) {
@@ -6584,8 +6652,8 @@ class ClientStrategy {
6584
6652
  * @param symbol - Trading pair symbol
6585
6653
  * @returns Promise resolving to effective entry price or null
6586
6654
  */
6587
- async getPositionAveragePrice(symbol) {
6588
- this.params.logger.debug("ClientStrategy getPositionAveragePrice", { symbol });
6655
+ async getPositionEffectivePrice(symbol) {
6656
+ this.params.logger.debug("ClientStrategy getPositionEffectivePrice", { symbol });
6589
6657
  if (!this._pendingSignal) {
6590
6658
  return null;
6591
6659
  }
@@ -6742,6 +6810,170 @@ class ClientStrategy {
6742
6810
  }
6743
6811
  return entries.map(({ price, cost, timestamp }) => ({ price, cost, timestamp }));
6744
6812
  }
6813
+ /**
6814
+ * Returns the original estimated duration for the current pending signal.
6815
+ *
6816
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
6817
+ * number of minutes the position is expected to be active before `time_expired`.
6818
+ *
6819
+ * Returns null if no pending signal exists.
6820
+ *
6821
+ * @param symbol - Trading pair symbol
6822
+ * @returns Promise resolving to estimated duration in minutes or null
6823
+ */
6824
+ async getPositionEstimateMinutes(symbol) {
6825
+ this.params.logger.debug("ClientStrategy getPositionEstimateMinutes", { symbol });
6826
+ if (!this._pendingSignal) {
6827
+ return null;
6828
+ }
6829
+ return this._pendingSignal.minuteEstimatedTime;
6830
+ }
6831
+ /**
6832
+ * Returns the remaining time before the position expires, clamped to zero.
6833
+ *
6834
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
6835
+ * Returns 0 once the estimate is exceeded (never negative).
6836
+ *
6837
+ * Returns null if no pending signal exists.
6838
+ *
6839
+ * @param symbol - Trading pair symbol
6840
+ * @param timestamp - Current Unix timestamp in milliseconds
6841
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
6842
+ */
6843
+ async getPositionCountdownMinutes(symbol, timestamp) {
6844
+ this.params.logger.debug("ClientStrategy getPositionCountdownMinutes", { symbol });
6845
+ if (!this._pendingSignal) {
6846
+ return null;
6847
+ }
6848
+ const elapsed = Math.floor((timestamp - this._pendingSignal.pendingAt) / 60000);
6849
+ return Math.max(0, this._pendingSignal.minuteEstimatedTime - elapsed);
6850
+ }
6851
+ /**
6852
+ * Returns the best price reached in the profit direction during this position's life.
6853
+ *
6854
+ * Initialized at position open with the entry price and timestamp.
6855
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6856
+ * - LONG: tracks the highest price seen above effective entry
6857
+ * - SHORT: tracks the lowest price seen below effective entry
6858
+ *
6859
+ * Returns null if no pending signal exists.
6860
+ * Never returns null when a signal is active — always contains at least the entry price.
6861
+ *
6862
+ * @param symbol - Trading pair symbol
6863
+ * @returns Promise resolving to price or null
6864
+ */
6865
+ async getPositionHighestProfitPrice(symbol) {
6866
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitPrice", { symbol });
6867
+ if (!this._pendingSignal) {
6868
+ return null;
6869
+ }
6870
+ return this._pendingSignal._peak.price;
6871
+ }
6872
+ /**
6873
+ * Returns the timestamp when the best profit price was recorded during this position's life.
6874
+ *
6875
+ * Returns null if no pending signal exists.
6876
+ *
6877
+ * @param symbol - Trading pair symbol
6878
+ * @returns Promise resolving to timestamp in milliseconds or null
6879
+ */
6880
+ async getPositionHighestProfitTimestamp(symbol) {
6881
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitTimestamp", { symbol });
6882
+ if (!this._pendingSignal) {
6883
+ return null;
6884
+ }
6885
+ return this._pendingSignal._peak.timestamp;
6886
+ }
6887
+ /**
6888
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
6889
+ *
6890
+ * Initialized at position open with 0.
6891
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6892
+ * - LONG: tracks the PnL percentage at the highest price seen above effective entry
6893
+ * - SHORT: tracks the PnL percentage at the lowest price seen below effective entry
6894
+ *
6895
+ * Returns null if no pending signal exists.
6896
+ * Never returns null when a signal is active — always contains at least 0.
6897
+ *
6898
+ * @param symbol - Trading pair symbol
6899
+ * @returns Promise resolving to PnL percentage or null
6900
+ */
6901
+ async getPositionHighestPnlPercentage(symbol) {
6902
+ this.params.logger.debug("ClientStrategy getPositionHighestPnlPercentage", { symbol });
6903
+ if (!this._pendingSignal) {
6904
+ return null;
6905
+ }
6906
+ return this._pendingSignal._peak.pnlPercentage;
6907
+ }
6908
+ /**
6909
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
6910
+ *
6911
+ * Initialized at position open with 0.
6912
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
6913
+ * - LONG: tracks the PnL cost at the highest price seen above effective entry
6914
+ * - SHORT: tracks the PnL cost at the lowest price seen below effective entry
6915
+ *
6916
+ * Returns null if no pending signal exists.
6917
+ * Never returns null when a signal is active — always contains at least 0.
6918
+ *
6919
+ * @param symbol - Trading pair symbol
6920
+ * @returns Promise resolving to PnL cost or null
6921
+ */
6922
+ async getPositionHighestPnlCost(symbol) {
6923
+ this.params.logger.debug("ClientStrategy getPositionHighestPnlCost", { symbol });
6924
+ if (!this._pendingSignal) {
6925
+ return null;
6926
+ }
6927
+ return this._pendingSignal._peak.pnlCost;
6928
+ }
6929
+ /**
6930
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
6931
+ *
6932
+ * Measures how long the position has been pulling back from its peak profit level.
6933
+ * Zero when called at the exact moment the peak was set.
6934
+ * Grows continuously as price moves away from the peak without setting a new record.
6935
+ *
6936
+ * Returns null if no pending signal exists.
6937
+ *
6938
+ * @param symbol - Trading pair symbol
6939
+ * @param timestamp - Current Unix timestamp in milliseconds
6940
+ * @returns Promise resolving to drawdown duration in minutes or null
6941
+ */
6942
+ async getPositionHighestProfitBreakeven(symbol) {
6943
+ this.params.logger.debug("ClientStrategy getPositionHighestProfitBreakeven", { symbol });
6944
+ if (!this._pendingSignal) {
6945
+ return null;
6946
+ }
6947
+ const signal = this._pendingSignal;
6948
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
6949
+ const peakPrice = signal._peak.price;
6950
+ const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
6951
+ if (signal.position === "long") {
6952
+ return peakPrice >= effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
6953
+ }
6954
+ else {
6955
+ return peakPrice <= effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
6956
+ }
6957
+ }
6958
+ /**
6959
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
6960
+ *
6961
+ * Measures how long the position has been pulling back from its peak.
6962
+ * Zero when called at the exact moment the peak was set.
6963
+ *
6964
+ * Returns null if no pending signal exists.
6965
+ *
6966
+ * @param symbol - Trading pair symbol
6967
+ * @param timestamp - Current Unix timestamp in milliseconds
6968
+ * @returns Promise resolving to drawdown duration in minutes or null
6969
+ */
6970
+ async getPositionDrawdownMinutes(symbol, timestamp) {
6971
+ this.params.logger.debug("ClientStrategy getPositionDrawdownMinutes", { symbol });
6972
+ if (!this._pendingSignal) {
6973
+ return null;
6974
+ }
6975
+ return Math.floor((timestamp - this._pendingSignal._peak.timestamp) / 60000);
6976
+ }
6745
6977
  /**
6746
6978
  * Performs a single tick of strategy execution.
6747
6979
  *
@@ -6917,6 +7149,7 @@ class ClientStrategy {
6917
7149
  ...activatedSignal,
6918
7150
  pendingAt: currentTime,
6919
7151
  _isScheduled: false,
7152
+ _peak: { price: activatedSignal.priceOpen, timestamp: currentTime, pnlPercentage: 0, pnlCost: 0 },
6920
7153
  };
6921
7154
  const syncOpenAllowed = await CALL_SIGNAL_SYNC_OPEN_FN(currentTime, currentPrice, pendingSignal, this);
6922
7155
  if (!syncOpenAllowed) {
@@ -7039,7 +7272,7 @@ class ClientStrategy {
7039
7272
  if (closedResult) {
7040
7273
  return closedResult;
7041
7274
  }
7042
- return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice);
7275
+ return await RETURN_PENDING_SIGNAL_ACTIVE_FN(this, this._pendingSignal, averagePrice, false);
7043
7276
  }
7044
7277
  /**
7045
7278
  * Fast backtests a signal using historical candle data.
@@ -9026,7 +9259,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
9026
9259
  * @param backtest - Whether running in backtest mode
9027
9260
  * @returns Unique string key for memoization
9028
9261
  */
9029
- const CREATE_KEY_FN$o = (symbol, strategyName, exchangeName, frameName, backtest) => {
9262
+ const CREATE_KEY_FN$p = (symbol, strategyName, exchangeName, frameName, backtest) => {
9030
9263
  const parts = [symbol, strategyName, exchangeName];
9031
9264
  if (frameName)
9032
9265
  parts.push(frameName);
@@ -9150,6 +9383,43 @@ const CREATE_COMMIT_FN = (self) => functoolsKit.trycatch(async (event) => {
9150
9383
  },
9151
9384
  defaultValue: null,
9152
9385
  });
9386
+ /**
9387
+ * Creates a callback function for emitting highest profit updates to highestProfitSubject.
9388
+ * Called by ClientStrategy when the highest profit for an open position is updated.
9389
+ * Emits HighestProfitContract event to all subscribers with the current price and timestamp.
9390
+ * Used for real-time profit tracking and management logic based on profit levels.
9391
+ *
9392
+ * @param self - Reference to StrategyConnectionService instance
9393
+ * @param strategyName - Name of the strategy
9394
+ * @param exchangeName - Name of the exchange
9395
+ * @param frameName - Name of the frame
9396
+ * @param isBacktest - Flag indicating if the operation is for backtesting
9397
+ * @returns Callback function for highest profit updates
9398
+ */
9399
+ const CREATE_HIGHEST_PROFIT_FN = (self, strategyName, exchangeName, frameName, isBacktest) => functoolsKit.trycatch(async (signal, currentPrice, timestamp) => {
9400
+ await highestProfitSubject.next({
9401
+ symbol: signal.symbol,
9402
+ signal,
9403
+ currentPrice,
9404
+ timestamp,
9405
+ strategyName,
9406
+ exchangeName,
9407
+ frameName,
9408
+ backtest: isBacktest,
9409
+ });
9410
+ }, {
9411
+ fallback: (error) => {
9412
+ const message = "StrategyConnectionService CREATE_HIGHEST_PROFIT_FN thrown";
9413
+ const payload = {
9414
+ error: functoolsKit.errorData(error),
9415
+ message: functoolsKit.getErrorMessage(error),
9416
+ };
9417
+ bt.loggerService.warn(message, payload);
9418
+ console.warn(message, payload);
9419
+ errorEmitter.next(error);
9420
+ },
9421
+ defaultValue: null,
9422
+ });
9153
9423
  /**
9154
9424
  * Creates a callback function for emitting dispose events.
9155
9425
  *
@@ -9220,7 +9490,7 @@ class StrategyConnectionService {
9220
9490
  * @param backtest - Whether running in backtest mode
9221
9491
  * @returns Configured ClientStrategy instance
9222
9492
  */
9223
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9493
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$p(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
9224
9494
  const { riskName = "", riskList = [], getSignal, interval, callbacks, } = this.strategySchemaService.get(strategyName);
9225
9495
  return new ClientStrategy({
9226
9496
  symbol,
@@ -9248,6 +9518,7 @@ class StrategyConnectionService {
9248
9518
  onDispose: CREATE_COMMIT_DISPOSE_FN(this),
9249
9519
  onCommit: CREATE_COMMIT_FN(),
9250
9520
  onSignalSync: CREATE_SYNC_FN(this, strategyName, exchangeName, frameName, backtest),
9521
+ onHighestProfit: CREATE_HIGHEST_PROFIT_FN(this, strategyName, exchangeName, frameName, backtest),
9251
9522
  });
9252
9523
  });
9253
9524
  /**
@@ -9321,14 +9592,14 @@ class StrategyConnectionService {
9321
9592
  * @param context - Execution context with strategyName, exchangeName, frameName
9322
9593
  * @returns Promise resolving to effective entry price or null
9323
9594
  */
9324
- this.getPositionAveragePrice = async (backtest, symbol, context) => {
9325
- this.loggerService.log("strategyConnectionService getPositionAveragePrice", {
9595
+ this.getPositionEffectivePrice = async (backtest, symbol, context) => {
9596
+ this.loggerService.log("strategyConnectionService getPositionEffectivePrice", {
9326
9597
  symbol,
9327
9598
  context,
9328
9599
  backtest,
9329
9600
  });
9330
9601
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9331
- return await strategy.getPositionAveragePrice(symbol);
9602
+ return await strategy.getPositionEffectivePrice(symbol);
9332
9603
  };
9333
9604
  /**
9334
9605
  * Returns the number of DCA entries made for the current pending signal.
@@ -9678,6 +9949,162 @@ class StrategyConnectionService {
9678
9949
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9679
9950
  return await strategy.hasPendingSignal(symbol);
9680
9951
  };
9952
+ /**
9953
+ * Returns the original estimated duration for the current pending signal.
9954
+ *
9955
+ * Delegates to ClientStrategy.getPositionEstimateMinutes().
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 estimated duration in minutes or null
9962
+ */
9963
+ this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
9964
+ this.loggerService.log("strategyConnectionService getPositionEstimateMinutes", {
9965
+ symbol,
9966
+ context,
9967
+ });
9968
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9969
+ return await strategy.getPositionEstimateMinutes(symbol);
9970
+ };
9971
+ /**
9972
+ * Returns the remaining time before the position expires, clamped to zero.
9973
+ *
9974
+ * Resolves current timestamp via timeMetaService and delegates to
9975
+ * ClientStrategy.getPositionCountdownMinutes().
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 remaining minutes (≥ 0) or null
9982
+ */
9983
+ this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
9984
+ this.loggerService.log("strategyConnectionService getPositionCountdownMinutes", {
9985
+ symbol,
9986
+ context,
9987
+ });
9988
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
9989
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
9990
+ return await strategy.getPositionCountdownMinutes(symbol, timestamp);
9991
+ };
9992
+ /**
9993
+ * Returns the best price reached in the profit direction during this position's life.
9994
+ *
9995
+ * Delegates to ClientStrategy.getPositionHighestProfitPrice().
9996
+ * Returns null if no pending signal exists.
9997
+ *
9998
+ * @param backtest - Whether running in backtest mode
9999
+ * @param symbol - Trading pair symbol
10000
+ * @param context - Execution context with strategyName, exchangeName, frameName
10001
+ * @returns Promise resolving to price or null
10002
+ */
10003
+ this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
10004
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitPrice", {
10005
+ symbol,
10006
+ context,
10007
+ });
10008
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10009
+ return await strategy.getPositionHighestProfitPrice(symbol);
10010
+ };
10011
+ /**
10012
+ * Returns the timestamp when the best profit price was recorded during this position's life.
10013
+ *
10014
+ * Delegates to ClientStrategy.getPositionHighestProfitTimestamp().
10015
+ * Returns null if no pending signal exists.
10016
+ *
10017
+ * @param backtest - Whether running in backtest mode
10018
+ * @param symbol - Trading pair symbol
10019
+ * @param context - Execution context with strategyName, exchangeName, frameName
10020
+ * @returns Promise resolving to timestamp in milliseconds or null
10021
+ */
10022
+ this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
10023
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitTimestamp", {
10024
+ symbol,
10025
+ context,
10026
+ });
10027
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10028
+ return await strategy.getPositionHighestProfitTimestamp(symbol);
10029
+ };
10030
+ /**
10031
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
10032
+ *
10033
+ * Delegates to ClientStrategy.getPositionHighestPnlPercentage().
10034
+ * Returns null if no pending signal exists.
10035
+ *
10036
+ * @param backtest - Whether running in backtest mode
10037
+ * @param symbol - Trading pair symbol
10038
+ * @param context - Execution context with strategyName, exchangeName, frameName
10039
+ * @returns Promise resolving to PnL percentage or null
10040
+ */
10041
+ this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
10042
+ this.loggerService.log("strategyConnectionService getPositionHighestPnlPercentage", {
10043
+ symbol,
10044
+ context,
10045
+ });
10046
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10047
+ return await strategy.getPositionHighestPnlPercentage(symbol);
10048
+ };
10049
+ /**
10050
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
10051
+ *
10052
+ * Delegates to ClientStrategy.getPositionHighestPnlCost().
10053
+ * Returns null if no pending signal exists.
10054
+ *
10055
+ * @param backtest - Whether running in backtest mode
10056
+ * @param symbol - Trading pair symbol
10057
+ * @param context - Execution context with strategyName, exchangeName, frameName
10058
+ * @returns Promise resolving to PnL cost or null
10059
+ */
10060
+ this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
10061
+ this.loggerService.log("strategyConnectionService getPositionHighestPnlCost", {
10062
+ symbol,
10063
+ context,
10064
+ });
10065
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10066
+ return await strategy.getPositionHighestPnlCost(symbol);
10067
+ };
10068
+ /**
10069
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
10070
+ *
10071
+ * Delegates to ClientStrategy.getPositionHighestProfitBreakeven().
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 true if breakeven was reachable at peak, false otherwise, or null
10078
+ */
10079
+ this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
10080
+ this.loggerService.log("strategyConnectionService getPositionHighestProfitBreakeven", {
10081
+ symbol,
10082
+ context,
10083
+ });
10084
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10085
+ return await strategy.getPositionHighestProfitBreakeven(symbol);
10086
+ };
10087
+ /**
10088
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
10089
+ *
10090
+ * Resolves current timestamp via timeMetaService and delegates to
10091
+ * ClientStrategy.getPositionDrawdownMinutes().
10092
+ * Returns null if no pending signal exists.
10093
+ *
10094
+ * @param backtest - Whether running in backtest mode
10095
+ * @param symbol - Trading pair symbol
10096
+ * @param context - Execution context with strategyName, exchangeName, frameName
10097
+ * @returns Promise resolving to drawdown duration in minutes or null
10098
+ */
10099
+ this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
10100
+ this.loggerService.log("strategyConnectionService getPositionDrawdownMinutes", {
10101
+ symbol,
10102
+ context,
10103
+ });
10104
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
10105
+ const timestamp = await this.timeMetaService.getTimestamp(symbol, context, backtest);
10106
+ return await strategy.getPositionDrawdownMinutes(symbol, timestamp);
10107
+ };
9681
10108
  /**
9682
10109
  * Disposes the ClientStrategy instance for the given context.
9683
10110
  *
@@ -9716,7 +10143,7 @@ class StrategyConnectionService {
9716
10143
  }
9717
10144
  return;
9718
10145
  }
9719
- const key = CREATE_KEY_FN$o(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
10146
+ const key = CREATE_KEY_FN$p(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
9720
10147
  if (!this.getStrategy.has(key)) {
9721
10148
  return;
9722
10149
  }
@@ -10889,7 +11316,7 @@ class ClientRisk {
10889
11316
  * @param backtest - Whether running in backtest mode
10890
11317
  * @returns Unique string key for memoization
10891
11318
  */
10892
- const CREATE_KEY_FN$n = (riskName, exchangeName, frameName, backtest) => {
11319
+ const CREATE_KEY_FN$o = (riskName, exchangeName, frameName, backtest) => {
10893
11320
  const parts = [riskName, exchangeName];
10894
11321
  if (frameName)
10895
11322
  parts.push(frameName);
@@ -10988,7 +11415,7 @@ class RiskConnectionService {
10988
11415
  * @param backtest - True if backtest mode, false if live mode
10989
11416
  * @returns Configured ClientRisk instance
10990
11417
  */
10991
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
11418
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$o(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
10992
11419
  const schema = this.riskSchemaService.get(riskName);
10993
11420
  return new ClientRisk({
10994
11421
  ...schema,
@@ -11056,7 +11483,7 @@ class RiskConnectionService {
11056
11483
  payload,
11057
11484
  });
11058
11485
  if (payload) {
11059
- const key = CREATE_KEY_FN$n(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
11486
+ const key = CREATE_KEY_FN$o(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
11060
11487
  this.getRisk.clear(key);
11061
11488
  }
11062
11489
  else {
@@ -12523,7 +12950,7 @@ class ClientAction {
12523
12950
  * @param backtest - Whether running in backtest mode
12524
12951
  * @returns Unique string key for memoization
12525
12952
  */
12526
- const CREATE_KEY_FN$m = (actionName, strategyName, exchangeName, frameName, backtest) => {
12953
+ const CREATE_KEY_FN$n = (actionName, strategyName, exchangeName, frameName, backtest) => {
12527
12954
  const parts = [actionName, strategyName, exchangeName];
12528
12955
  if (frameName)
12529
12956
  parts.push(frameName);
@@ -12574,7 +13001,7 @@ class ActionConnectionService {
12574
13001
  * @param backtest - True if backtest mode, false if live mode
12575
13002
  * @returns Configured ClientAction instance
12576
13003
  */
12577
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13004
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
12578
13005
  const schema = this.actionSchemaService.get(actionName);
12579
13006
  return new ClientAction({
12580
13007
  ...schema,
@@ -12784,7 +13211,7 @@ class ActionConnectionService {
12784
13211
  await Promise.all(actions.map(async (action) => await action.dispose()));
12785
13212
  return;
12786
13213
  }
12787
- const key = CREATE_KEY_FN$m(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13214
+ const key = CREATE_KEY_FN$n(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
12788
13215
  if (!this.getAction.has(key)) {
12789
13216
  return;
12790
13217
  }
@@ -12802,7 +13229,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
12802
13229
  * @param exchangeName - Exchange name
12803
13230
  * @returns Unique string key for memoization
12804
13231
  */
12805
- const CREATE_KEY_FN$l = (exchangeName) => {
13232
+ const CREATE_KEY_FN$m = (exchangeName) => {
12806
13233
  return exchangeName;
12807
13234
  };
12808
13235
  /**
@@ -12826,7 +13253,7 @@ class ExchangeCoreService {
12826
13253
  * @param exchangeName - Name of the exchange to validate
12827
13254
  * @returns Promise that resolves when validation is complete
12828
13255
  */
12829
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$l(exchangeName), async (exchangeName) => {
13256
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$m(exchangeName), async (exchangeName) => {
12830
13257
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
12831
13258
  exchangeName,
12832
13259
  });
@@ -13078,7 +13505,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
13078
13505
  * @param context - Execution context with strategyName, exchangeName, frameName
13079
13506
  * @returns Unique string key for memoization
13080
13507
  */
13081
- const CREATE_KEY_FN$k = (context) => {
13508
+ const CREATE_KEY_FN$l = (context) => {
13082
13509
  const parts = [context.strategyName, context.exchangeName];
13083
13510
  if (context.frameName)
13084
13511
  parts.push(context.frameName);
@@ -13110,7 +13537,7 @@ class StrategyCoreService {
13110
13537
  * @param context - Execution context with strategyName, exchangeName, frameName
13111
13538
  * @returns Promise that resolves when validation is complete
13112
13539
  */
13113
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
13540
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$l(context), async (context) => {
13114
13541
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
13115
13542
  context,
13116
13543
  });
@@ -13174,13 +13601,13 @@ class StrategyCoreService {
13174
13601
  await this.validate(context);
13175
13602
  return await this.strategyConnectionService.getTotalCostClosed(backtest, symbol, context);
13176
13603
  };
13177
- this.getPositionAveragePrice = async (backtest, symbol, context) => {
13178
- this.loggerService.log("strategyCoreService getPositionAveragePrice", {
13604
+ this.getPositionEffectivePrice = async (backtest, symbol, context) => {
13605
+ this.loggerService.log("strategyCoreService getPositionEffectivePrice", {
13179
13606
  symbol,
13180
13607
  context,
13181
13608
  });
13182
13609
  await this.validate(context);
13183
- return await this.strategyConnectionService.getPositionAveragePrice(backtest, symbol, context);
13610
+ return await this.strategyConnectionService.getPositionEffectivePrice(backtest, symbol, context);
13184
13611
  };
13185
13612
  this.getPositionInvestedCount = async (backtest, symbol, context) => {
13186
13613
  this.loggerService.log("strategyCoreService getPositionInvestedCount", {
@@ -13873,6 +14300,143 @@ class StrategyCoreService {
13873
14300
  await this.validate(context);
13874
14301
  return await this.strategyConnectionService.hasPendingSignal(backtest, symbol, context);
13875
14302
  };
14303
+ /**
14304
+ * Returns the original estimated duration for the current pending signal.
14305
+ *
14306
+ * Validates strategy existence and delegates to connection service.
14307
+ * Returns null if no pending signal exists.
14308
+ *
14309
+ * @param backtest - Whether running in backtest mode
14310
+ * @param symbol - Trading pair symbol
14311
+ * @param context - Execution context with strategyName, exchangeName, frameName
14312
+ * @returns Promise resolving to estimated duration in minutes or null
14313
+ */
14314
+ this.getPositionEstimateMinutes = async (backtest, symbol, context) => {
14315
+ this.loggerService.log("strategyCoreService getPositionEstimateMinutes", {
14316
+ symbol,
14317
+ context,
14318
+ });
14319
+ await this.validate(context);
14320
+ return await this.strategyConnectionService.getPositionEstimateMinutes(backtest, symbol, context);
14321
+ };
14322
+ /**
14323
+ * Returns the remaining time before the position expires, clamped to zero.
14324
+ *
14325
+ * Validates strategy existence and delegates to connection service.
14326
+ * Returns null if no pending signal exists.
14327
+ *
14328
+ * @param backtest - Whether running in backtest mode
14329
+ * @param symbol - Trading pair symbol
14330
+ * @param context - Execution context with strategyName, exchangeName, frameName
14331
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
14332
+ */
14333
+ this.getPositionCountdownMinutes = async (backtest, symbol, context) => {
14334
+ this.loggerService.log("strategyCoreService getPositionCountdownMinutes", {
14335
+ symbol,
14336
+ context,
14337
+ });
14338
+ await this.validate(context);
14339
+ return await this.strategyConnectionService.getPositionCountdownMinutes(backtest, symbol, context);
14340
+ };
14341
+ /**
14342
+ * Returns the best price reached in the profit direction during this position's life.
14343
+ *
14344
+ * @param backtest - Whether running in backtest mode
14345
+ * @param symbol - Trading pair symbol
14346
+ * @param context - Execution context with strategyName, exchangeName, frameName
14347
+ * @returns Promise resolving to price or null
14348
+ */
14349
+ this.getPositionHighestProfitPrice = async (backtest, symbol, context) => {
14350
+ this.loggerService.log("strategyCoreService getPositionHighestProfitPrice", {
14351
+ symbol,
14352
+ context,
14353
+ });
14354
+ await this.validate(context);
14355
+ return await this.strategyConnectionService.getPositionHighestProfitPrice(backtest, symbol, context);
14356
+ };
14357
+ /**
14358
+ * Returns the timestamp when the best profit price was recorded during this position's life.
14359
+ *
14360
+ * @param backtest - Whether running in backtest mode
14361
+ * @param symbol - Trading pair symbol
14362
+ * @param context - Execution context with strategyName, exchangeName, frameName
14363
+ * @returns Promise resolving to timestamp in milliseconds or null
14364
+ */
14365
+ this.getPositionHighestProfitTimestamp = async (backtest, symbol, context) => {
14366
+ this.loggerService.log("strategyCoreService getPositionHighestProfitTimestamp", {
14367
+ symbol,
14368
+ context,
14369
+ });
14370
+ await this.validate(context);
14371
+ return await this.strategyConnectionService.getPositionHighestProfitTimestamp(backtest, symbol, context);
14372
+ };
14373
+ /**
14374
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
14375
+ *
14376
+ * @param backtest - Whether running in backtest mode
14377
+ * @param symbol - Trading pair symbol
14378
+ * @param context - Execution context with strategyName, exchangeName, frameName
14379
+ * @returns Promise resolving to PnL percentage or null
14380
+ */
14381
+ this.getPositionHighestPnlPercentage = async (backtest, symbol, context) => {
14382
+ this.loggerService.log("strategyCoreService getPositionHighestPnlPercentage", {
14383
+ symbol,
14384
+ context,
14385
+ });
14386
+ await this.validate(context);
14387
+ return await this.strategyConnectionService.getPositionHighestPnlPercentage(backtest, symbol, context);
14388
+ };
14389
+ /**
14390
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
14391
+ *
14392
+ * @param backtest - Whether running in backtest mode
14393
+ * @param symbol - Trading pair symbol
14394
+ * @param context - Execution context with strategyName, exchangeName, frameName
14395
+ * @returns Promise resolving to PnL cost or null
14396
+ */
14397
+ this.getPositionHighestPnlCost = async (backtest, symbol, context) => {
14398
+ this.loggerService.log("strategyCoreService getPositionHighestPnlCost", {
14399
+ symbol,
14400
+ context,
14401
+ });
14402
+ await this.validate(context);
14403
+ return await this.strategyConnectionService.getPositionHighestPnlCost(backtest, symbol, context);
14404
+ };
14405
+ /**
14406
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
14407
+ *
14408
+ * @param backtest - Whether running in backtest mode
14409
+ * @param symbol - Trading pair symbol
14410
+ * @param context - Execution context with strategyName, exchangeName, frameName
14411
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
14412
+ */
14413
+ this.getPositionHighestProfitBreakeven = async (backtest, symbol, context) => {
14414
+ this.loggerService.log("strategyCoreService getPositionHighestProfitBreakeven", {
14415
+ symbol,
14416
+ context,
14417
+ });
14418
+ await this.validate(context);
14419
+ return await this.strategyConnectionService.getPositionHighestProfitBreakeven(backtest, symbol, context);
14420
+ };
14421
+ /**
14422
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
14423
+ *
14424
+ * Validates strategy existence and delegates to connection service.
14425
+ * Returns null if no pending signal exists.
14426
+ *
14427
+ * @param backtest - Whether running in backtest mode
14428
+ * @param symbol - Trading pair symbol
14429
+ * @param context - Execution context with strategyName, exchangeName, frameName
14430
+ * @returns Promise resolving to drawdown duration in minutes or null
14431
+ */
14432
+ this.getPositionDrawdownMinutes = async (backtest, symbol, context) => {
14433
+ this.loggerService.log("strategyCoreService getPositionDrawdownMinutes", {
14434
+ symbol,
14435
+ context,
14436
+ });
14437
+ await this.validate(context);
14438
+ return await this.strategyConnectionService.getPositionDrawdownMinutes(backtest, symbol, context);
14439
+ };
13876
14440
  }
13877
14441
  }
13878
14442
 
@@ -13945,7 +14509,7 @@ class SizingGlobalService {
13945
14509
  * @param context - Context with riskName, exchangeName, frameName
13946
14510
  * @returns Unique string key for memoization
13947
14511
  */
13948
- const CREATE_KEY_FN$j = (context) => {
14512
+ const CREATE_KEY_FN$k = (context) => {
13949
14513
  const parts = [context.riskName, context.exchangeName];
13950
14514
  if (context.frameName)
13951
14515
  parts.push(context.frameName);
@@ -13971,7 +14535,7 @@ class RiskGlobalService {
13971
14535
  * @param payload - Payload with riskName, exchangeName and frameName
13972
14536
  * @returns Promise that resolves when validation is complete
13973
14537
  */
13974
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
14538
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$k(context), async (context) => {
13975
14539
  this.loggerService.log("riskGlobalService validate", {
13976
14540
  context,
13977
14541
  });
@@ -14049,7 +14613,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
14049
14613
  * @param context - Execution context with strategyName, exchangeName, frameName
14050
14614
  * @returns Unique string key for memoization
14051
14615
  */
14052
- const CREATE_KEY_FN$i = (context) => {
14616
+ const CREATE_KEY_FN$j = (context) => {
14053
14617
  const parts = [context.strategyName, context.exchangeName];
14054
14618
  if (context.frameName)
14055
14619
  parts.push(context.frameName);
@@ -14093,7 +14657,7 @@ class ActionCoreService {
14093
14657
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
14094
14658
  * @returns Promise that resolves when all validations complete
14095
14659
  */
14096
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$i(context), async (context) => {
14660
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$j(context), async (context) => {
14097
14661
  this.loggerService.log(METHOD_NAME_VALIDATE, {
14098
14662
  context,
14099
14663
  });
@@ -17904,6 +18468,78 @@ const sync_columns = [
17904
18468
  },
17905
18469
  ];
17906
18470
 
18471
+ /**
18472
+ * Column configuration for highest profit markdown reports.
18473
+ *
18474
+ * Defines the table structure for displaying highest-profit-record events.
18475
+ *
18476
+ * @see HighestProfitMarkdownService
18477
+ * @see ColumnModel
18478
+ * @see HighestProfitEvent
18479
+ */
18480
+ const highest_profit_columns = [
18481
+ {
18482
+ key: "symbol",
18483
+ label: "Symbol",
18484
+ format: (data) => data.symbol,
18485
+ isVisible: () => true,
18486
+ },
18487
+ {
18488
+ key: "strategyName",
18489
+ label: "Strategy",
18490
+ format: (data) => data.strategyName,
18491
+ isVisible: () => true,
18492
+ },
18493
+ {
18494
+ key: "signalId",
18495
+ label: "Signal ID",
18496
+ format: (data) => data.signalId,
18497
+ isVisible: () => true,
18498
+ },
18499
+ {
18500
+ key: "position",
18501
+ label: "Position",
18502
+ format: (data) => data.position.toUpperCase(),
18503
+ isVisible: () => true,
18504
+ },
18505
+ {
18506
+ key: "currentPrice",
18507
+ label: "Record Price",
18508
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
18509
+ isVisible: () => true,
18510
+ },
18511
+ {
18512
+ key: "priceOpen",
18513
+ label: "Entry Price",
18514
+ format: (data) => `${data.priceOpen.toFixed(8)} USD`,
18515
+ isVisible: () => true,
18516
+ },
18517
+ {
18518
+ key: "priceTakeProfit",
18519
+ label: "Take Profit",
18520
+ format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
18521
+ isVisible: () => true,
18522
+ },
18523
+ {
18524
+ key: "priceStopLoss",
18525
+ label: "Stop Loss",
18526
+ format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
18527
+ isVisible: () => true,
18528
+ },
18529
+ {
18530
+ key: "timestamp",
18531
+ label: "Timestamp",
18532
+ format: (data) => new Date(data.timestamp).toISOString(),
18533
+ isVisible: () => true,
18534
+ },
18535
+ {
18536
+ key: "mode",
18537
+ label: "Mode",
18538
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
18539
+ isVisible: () => true,
18540
+ },
18541
+ ];
18542
+
17907
18543
  /**
17908
18544
  * Column configuration for walker strategy comparison table in markdown reports.
17909
18545
  *
@@ -18121,6 +18757,8 @@ const COLUMN_CONFIG = {
18121
18757
  strategy_columns,
18122
18758
  /** Columns for signal sync lifecycle events (signal-open, signal-close) */
18123
18759
  sync_columns,
18760
+ /** Columns for highest profit milestone tracking events */
18761
+ highest_profit_columns,
18124
18762
  /** Walker: PnL summary columns */
18125
18763
  walker_pnl_columns,
18126
18764
  /** Walker: strategy-level summary columns */
@@ -18161,6 +18799,7 @@ const WILDCARD_TARGET$1 = {
18161
18799
  schedule: true,
18162
18800
  walker: true,
18163
18801
  sync: true,
18802
+ highest_profit: true,
18164
18803
  };
18165
18804
  /**
18166
18805
  * JSONL-based markdown adapter with append-only writes.
@@ -18388,7 +19027,7 @@ class MarkdownUtils {
18388
19027
  *
18389
19028
  * @returns Cleanup function that unsubscribes from all enabled services
18390
19029
  */
18391
- 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) => {
19030
+ 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) => {
18392
19031
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
18393
19032
  backtest: bt$1,
18394
19033
  breakeven,
@@ -18401,6 +19040,7 @@ class MarkdownUtils {
18401
19040
  schedule,
18402
19041
  walker,
18403
19042
  sync,
19043
+ highest_profit,
18404
19044
  });
18405
19045
  const unList = [];
18406
19046
  if (bt$1) {
@@ -18436,6 +19076,9 @@ class MarkdownUtils {
18436
19076
  if (sync) {
18437
19077
  unList.push(bt.syncMarkdownService.subscribe());
18438
19078
  }
19079
+ if (highest_profit) {
19080
+ unList.push(bt.highestProfitMarkdownService.subscribe());
19081
+ }
18439
19082
  return functoolsKit.compose(...unList.map((un) => () => void un()));
18440
19083
  };
18441
19084
  /**
@@ -18475,7 +19118,7 @@ class MarkdownUtils {
18475
19118
  * Markdown.disable();
18476
19119
  * ```
18477
19120
  */
18478
- 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) => {
19121
+ 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) => {
18479
19122
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
18480
19123
  backtest: bt$1,
18481
19124
  breakeven,
@@ -18488,6 +19131,7 @@ class MarkdownUtils {
18488
19131
  schedule,
18489
19132
  walker,
18490
19133
  sync,
19134
+ highest_profit,
18491
19135
  });
18492
19136
  if (bt$1) {
18493
19137
  bt.backtestMarkdownService.unsubscribe();
@@ -18522,6 +19166,9 @@ class MarkdownUtils {
18522
19166
  if (sync) {
18523
19167
  bt.syncMarkdownService.unsubscribe();
18524
19168
  }
19169
+ if (highest_profit) {
19170
+ bt.highestProfitMarkdownService.unsubscribe();
19171
+ }
18525
19172
  };
18526
19173
  }
18527
19174
  }
@@ -18628,7 +19275,7 @@ const Markdown = new MarkdownAdapter();
18628
19275
  * @param backtest - Whether running in backtest mode
18629
19276
  * @returns Unique string key for memoization
18630
19277
  */
18631
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19278
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
18632
19279
  const parts = [symbol, strategyName, exchangeName];
18633
19280
  if (frameName)
18634
19281
  parts.push(frameName);
@@ -18645,7 +19292,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
18645
19292
  * @param timestamp - Unix timestamp in milliseconds
18646
19293
  * @returns Filename string
18647
19294
  */
18648
- const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19295
+ const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
18649
19296
  const parts = [symbol, strategyName, exchangeName];
18650
19297
  if (frameName) {
18651
19298
  parts.push(frameName);
@@ -18674,12 +19321,12 @@ function isUnsafe$3(value) {
18674
19321
  return false;
18675
19322
  }
18676
19323
  /** Maximum number of signals to store in backtest reports */
18677
- const MAX_EVENTS$9 = 250;
19324
+ const MAX_EVENTS$a = 250;
18678
19325
  /**
18679
19326
  * Storage class for accumulating closed signals per strategy.
18680
19327
  * Maintains a list of all closed signals and provides methods to generate reports.
18681
19328
  */
18682
- let ReportStorage$8 = class ReportStorage {
19329
+ let ReportStorage$9 = class ReportStorage {
18683
19330
  constructor(symbol, strategyName, exchangeName, frameName) {
18684
19331
  this.symbol = symbol;
18685
19332
  this.strategyName = strategyName;
@@ -18696,7 +19343,7 @@ let ReportStorage$8 = class ReportStorage {
18696
19343
  addSignal(data) {
18697
19344
  this._signalList.unshift(data);
18698
19345
  // Trim queue if exceeded MAX_EVENTS
18699
- if (this._signalList.length > MAX_EVENTS$9) {
19346
+ if (this._signalList.length > MAX_EVENTS$a) {
18700
19347
  this._signalList.pop();
18701
19348
  }
18702
19349
  }
@@ -18820,7 +19467,7 @@ let ReportStorage$8 = class ReportStorage {
18820
19467
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
18821
19468
  const markdown = await this.getReport(strategyName, columns);
18822
19469
  const timestamp = getContextTimestamp();
18823
- const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19470
+ const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18824
19471
  await Markdown.writeData("backtest", markdown, {
18825
19472
  path,
18826
19473
  file: filename,
@@ -18867,7 +19514,7 @@ class BacktestMarkdownService {
18867
19514
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18868
19515
  * Each combination gets its own isolated storage instance.
18869
19516
  */
18870
- this.getStorage = functoolsKit.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));
19517
+ this.getStorage = functoolsKit.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));
18871
19518
  /**
18872
19519
  * Processes tick events and accumulates closed signals.
18873
19520
  * Should be called from IStrategyCallbacks.onTick.
@@ -19024,7 +19671,7 @@ class BacktestMarkdownService {
19024
19671
  payload,
19025
19672
  });
19026
19673
  if (payload) {
19027
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19674
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19028
19675
  this.getStorage.clear(key);
19029
19676
  }
19030
19677
  else {
@@ -19086,7 +19733,7 @@ class BacktestMarkdownService {
19086
19733
  * @param backtest - Whether running in backtest mode
19087
19734
  * @returns Unique string key for memoization
19088
19735
  */
19089
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19736
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19090
19737
  const parts = [symbol, strategyName, exchangeName];
19091
19738
  if (frameName)
19092
19739
  parts.push(frameName);
@@ -19103,7 +19750,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
19103
19750
  * @param timestamp - Unix timestamp in milliseconds
19104
19751
  * @returns Filename string
19105
19752
  */
19106
- const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19753
+ const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19107
19754
  const parts = [symbol, strategyName, exchangeName];
19108
19755
  if (frameName) {
19109
19756
  parts.push(frameName);
@@ -19132,12 +19779,12 @@ function isUnsafe$2(value) {
19132
19779
  return false;
19133
19780
  }
19134
19781
  /** Maximum number of events to store in live trading reports */
19135
- const MAX_EVENTS$8 = 250;
19782
+ const MAX_EVENTS$9 = 250;
19136
19783
  /**
19137
19784
  * Storage class for accumulating all tick events per strategy.
19138
19785
  * Maintains a chronological list of all events (idle, opened, active, closed).
19139
19786
  */
19140
- let ReportStorage$7 = class ReportStorage {
19787
+ let ReportStorage$8 = class ReportStorage {
19141
19788
  constructor(symbol, strategyName, exchangeName, frameName) {
19142
19789
  this.symbol = symbol;
19143
19790
  this.strategyName = strategyName;
@@ -19169,7 +19816,7 @@ let ReportStorage$7 = class ReportStorage {
19169
19816
  }
19170
19817
  {
19171
19818
  this._eventList.unshift(newEvent);
19172
- if (this._eventList.length > MAX_EVENTS$8) {
19819
+ if (this._eventList.length > MAX_EVENTS$9) {
19173
19820
  this._eventList.pop();
19174
19821
  }
19175
19822
  }
@@ -19199,7 +19846,7 @@ let ReportStorage$7 = class ReportStorage {
19199
19846
  scheduledAt: data.signal.scheduledAt,
19200
19847
  });
19201
19848
  // Trim queue if exceeded MAX_EVENTS
19202
- if (this._eventList.length > MAX_EVENTS$8) {
19849
+ if (this._eventList.length > MAX_EVENTS$9) {
19203
19850
  this._eventList.pop();
19204
19851
  }
19205
19852
  }
@@ -19243,7 +19890,7 @@ let ReportStorage$7 = class ReportStorage {
19243
19890
  // If no previous active event found, add new event
19244
19891
  this._eventList.unshift(newEvent);
19245
19892
  // Trim queue if exceeded MAX_EVENTS
19246
- if (this._eventList.length > MAX_EVENTS$8) {
19893
+ if (this._eventList.length > MAX_EVENTS$9) {
19247
19894
  this._eventList.pop();
19248
19895
  }
19249
19896
  }
@@ -19280,7 +19927,7 @@ let ReportStorage$7 = class ReportStorage {
19280
19927
  };
19281
19928
  this._eventList.unshift(newEvent);
19282
19929
  // Trim queue if exceeded MAX_EVENTS
19283
- if (this._eventList.length > MAX_EVENTS$8) {
19930
+ if (this._eventList.length > MAX_EVENTS$9) {
19284
19931
  this._eventList.pop();
19285
19932
  }
19286
19933
  }
@@ -19308,7 +19955,7 @@ let ReportStorage$7 = class ReportStorage {
19308
19955
  scheduledAt: data.signal.scheduledAt,
19309
19956
  });
19310
19957
  // Trim queue if exceeded MAX_EVENTS
19311
- if (this._eventList.length > MAX_EVENTS$8) {
19958
+ if (this._eventList.length > MAX_EVENTS$9) {
19312
19959
  this._eventList.pop();
19313
19960
  }
19314
19961
  }
@@ -19351,7 +19998,7 @@ let ReportStorage$7 = class ReportStorage {
19351
19998
  // If no previous waiting event found, add new event
19352
19999
  this._eventList.unshift(newEvent);
19353
20000
  // Trim queue if exceeded MAX_EVENTS
19354
- if (this._eventList.length > MAX_EVENTS$8) {
20001
+ if (this._eventList.length > MAX_EVENTS$9) {
19355
20002
  this._eventList.pop();
19356
20003
  }
19357
20004
  }
@@ -19380,7 +20027,7 @@ let ReportStorage$7 = class ReportStorage {
19380
20027
  scheduledAt: data.signal.scheduledAt,
19381
20028
  });
19382
20029
  // Trim queue if exceeded MAX_EVENTS
19383
- if (this._eventList.length > MAX_EVENTS$8) {
20030
+ if (this._eventList.length > MAX_EVENTS$9) {
19384
20031
  this._eventList.pop();
19385
20032
  }
19386
20033
  }
@@ -19519,7 +20166,7 @@ let ReportStorage$7 = class ReportStorage {
19519
20166
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
19520
20167
  const markdown = await this.getReport(strategyName, columns);
19521
20168
  const timestamp = getContextTimestamp();
19522
- const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20169
+ const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19523
20170
  await Markdown.writeData("live", markdown, {
19524
20171
  path,
19525
20172
  signalId: "",
@@ -19569,7 +20216,7 @@ class LiveMarkdownService {
19569
20216
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19570
20217
  * Each combination gets its own isolated storage instance.
19571
20218
  */
19572
- this.getStorage = functoolsKit.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));
20219
+ this.getStorage = functoolsKit.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));
19573
20220
  /**
19574
20221
  * Subscribes to live signal emitter to receive tick events.
19575
20222
  * Protected against multiple subscriptions.
@@ -19787,7 +20434,7 @@ class LiveMarkdownService {
19787
20434
  payload,
19788
20435
  });
19789
20436
  if (payload) {
19790
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20437
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19791
20438
  this.getStorage.clear(key);
19792
20439
  }
19793
20440
  else {
@@ -19807,7 +20454,7 @@ class LiveMarkdownService {
19807
20454
  * @param backtest - Whether running in backtest mode
19808
20455
  * @returns Unique string key for memoization
19809
20456
  */
19810
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20457
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19811
20458
  const parts = [symbol, strategyName, exchangeName];
19812
20459
  if (frameName)
19813
20460
  parts.push(frameName);
@@ -19824,7 +20471,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
19824
20471
  * @param timestamp - Unix timestamp in milliseconds
19825
20472
  * @returns Filename string
19826
20473
  */
19827
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20474
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19828
20475
  const parts = [symbol, strategyName, exchangeName];
19829
20476
  if (frameName) {
19830
20477
  parts.push(frameName);
@@ -19835,12 +20482,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
19835
20482
  return `${parts.join("_")}-${timestamp}.md`;
19836
20483
  };
19837
20484
  /** Maximum number of events to store in schedule reports */
19838
- const MAX_EVENTS$7 = 250;
20485
+ const MAX_EVENTS$8 = 250;
19839
20486
  /**
19840
20487
  * Storage class for accumulating scheduled signal events per strategy.
19841
20488
  * Maintains a chronological list of scheduled and cancelled events.
19842
20489
  */
19843
- let ReportStorage$6 = class ReportStorage {
20490
+ let ReportStorage$7 = class ReportStorage {
19844
20491
  constructor(symbol, strategyName, exchangeName, frameName) {
19845
20492
  this.symbol = symbol;
19846
20493
  this.strategyName = strategyName;
@@ -19876,7 +20523,7 @@ let ReportStorage$6 = class ReportStorage {
19876
20523
  scheduledAt: data.signal.scheduledAt,
19877
20524
  });
19878
20525
  // Trim queue if exceeded MAX_EVENTS
19879
- if (this._eventList.length > MAX_EVENTS$7) {
20526
+ if (this._eventList.length > MAX_EVENTS$8) {
19880
20527
  this._eventList.pop();
19881
20528
  }
19882
20529
  }
@@ -19912,7 +20559,7 @@ let ReportStorage$6 = class ReportStorage {
19912
20559
  };
19913
20560
  this._eventList.unshift(newEvent);
19914
20561
  // Trim queue if exceeded MAX_EVENTS
19915
- if (this._eventList.length > MAX_EVENTS$7) {
20562
+ if (this._eventList.length > MAX_EVENTS$8) {
19916
20563
  this._eventList.pop();
19917
20564
  }
19918
20565
  }
@@ -19950,7 +20597,7 @@ let ReportStorage$6 = class ReportStorage {
19950
20597
  };
19951
20598
  this._eventList.unshift(newEvent);
19952
20599
  // Trim queue if exceeded MAX_EVENTS
19953
- if (this._eventList.length > MAX_EVENTS$7) {
20600
+ if (this._eventList.length > MAX_EVENTS$8) {
19954
20601
  this._eventList.pop();
19955
20602
  }
19956
20603
  }
@@ -20057,7 +20704,7 @@ let ReportStorage$6 = class ReportStorage {
20057
20704
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
20058
20705
  const markdown = await this.getReport(strategyName, columns);
20059
20706
  const timestamp = getContextTimestamp();
20060
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20707
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20061
20708
  await Markdown.writeData("schedule", markdown, {
20062
20709
  path,
20063
20710
  file: filename,
@@ -20098,7 +20745,7 @@ class ScheduleMarkdownService {
20098
20745
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20099
20746
  * Each combination gets its own isolated storage instance.
20100
20747
  */
20101
- this.getStorage = functoolsKit.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));
20748
+ this.getStorage = functoolsKit.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));
20102
20749
  /**
20103
20750
  * Subscribes to signal emitter to receive scheduled signal events.
20104
20751
  * Protected against multiple subscriptions.
@@ -20301,7 +20948,7 @@ class ScheduleMarkdownService {
20301
20948
  payload,
20302
20949
  });
20303
20950
  if (payload) {
20304
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20951
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20305
20952
  this.getStorage.clear(key);
20306
20953
  }
20307
20954
  else {
@@ -20321,7 +20968,7 @@ class ScheduleMarkdownService {
20321
20968
  * @param backtest - Whether running in backtest mode
20322
20969
  * @returns Unique string key for memoization
20323
20970
  */
20324
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20971
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20325
20972
  const parts = [symbol, strategyName, exchangeName];
20326
20973
  if (frameName)
20327
20974
  parts.push(frameName);
@@ -20338,7 +20985,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
20338
20985
  * @param timestamp - Unix timestamp in milliseconds
20339
20986
  * @returns Filename string
20340
20987
  */
20341
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20988
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20342
20989
  const parts = [symbol, strategyName, exchangeName];
20343
20990
  if (frameName) {
20344
20991
  parts.push(frameName);
@@ -20358,7 +21005,7 @@ function percentile(sortedArray, p) {
20358
21005
  return sortedArray[Math.max(0, index)];
20359
21006
  }
20360
21007
  /** Maximum number of performance events to store per strategy */
20361
- const MAX_EVENTS$6 = 10000;
21008
+ const MAX_EVENTS$7 = 10000;
20362
21009
  /**
20363
21010
  * Storage class for accumulating performance metrics per strategy.
20364
21011
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -20380,7 +21027,7 @@ class PerformanceStorage {
20380
21027
  addEvent(event) {
20381
21028
  this._events.unshift(event);
20382
21029
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
20383
- if (this._events.length > MAX_EVENTS$6) {
21030
+ if (this._events.length > MAX_EVENTS$7) {
20384
21031
  this._events.pop();
20385
21032
  }
20386
21033
  }
@@ -20522,7 +21169,7 @@ class PerformanceStorage {
20522
21169
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
20523
21170
  const markdown = await this.getReport(strategyName, columns);
20524
21171
  const timestamp = getContextTimestamp();
20525
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21172
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20526
21173
  await Markdown.writeData("performance", markdown, {
20527
21174
  path,
20528
21175
  file: filename,
@@ -20569,7 +21216,7 @@ class PerformanceMarkdownService {
20569
21216
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20570
21217
  * Each combination gets its own isolated storage instance.
20571
21218
  */
20572
- this.getStorage = functoolsKit.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));
21219
+ this.getStorage = functoolsKit.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));
20573
21220
  /**
20574
21221
  * Subscribes to performance emitter to receive performance events.
20575
21222
  * Protected against multiple subscriptions.
@@ -20736,7 +21383,7 @@ class PerformanceMarkdownService {
20736
21383
  payload,
20737
21384
  });
20738
21385
  if (payload) {
20739
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21386
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20740
21387
  this.getStorage.clear(key);
20741
21388
  }
20742
21389
  else {
@@ -20750,7 +21397,7 @@ class PerformanceMarkdownService {
20750
21397
  * Creates a filename for markdown report based on walker name.
20751
21398
  * Filename format: "walkerName-timestamp.md"
20752
21399
  */
20753
- const CREATE_FILE_NAME_FN$6 = (walkerName, timestamp) => {
21400
+ const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
20754
21401
  return `${walkerName}-${timestamp}.md`;
20755
21402
  };
20756
21403
  /**
@@ -20785,7 +21432,7 @@ function formatMetric(value) {
20785
21432
  * Storage class for accumulating walker results.
20786
21433
  * Maintains a list of all strategy results and provides methods to generate reports.
20787
21434
  */
20788
- let ReportStorage$5 = class ReportStorage {
21435
+ let ReportStorage$6 = class ReportStorage {
20789
21436
  constructor(walkerName) {
20790
21437
  this.walkerName = walkerName;
20791
21438
  /** Walker metadata (set from first addResult call) */
@@ -20973,7 +21620,7 @@ let ReportStorage$5 = class ReportStorage {
20973
21620
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
20974
21621
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
20975
21622
  const timestamp = getContextTimestamp();
20976
- const filename = CREATE_FILE_NAME_FN$6(this.walkerName, timestamp);
21623
+ const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
20977
21624
  await Markdown.writeData("walker", markdown, {
20978
21625
  path,
20979
21626
  file: filename,
@@ -21009,7 +21656,7 @@ class WalkerMarkdownService {
21009
21656
  * Memoized function to get or create ReportStorage for a walker.
21010
21657
  * Each walker gets its own isolated storage instance.
21011
21658
  */
21012
- this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$5(walkerName));
21659
+ this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
21013
21660
  /**
21014
21661
  * Subscribes to walker emitter to receive walker progress events.
21015
21662
  * Protected against multiple subscriptions.
@@ -21206,7 +21853,7 @@ class WalkerMarkdownService {
21206
21853
  * @param backtest - Whether running in backtest mode
21207
21854
  * @returns Unique string key for memoization
21208
21855
  */
21209
- const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21856
+ const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
21210
21857
  const parts = [exchangeName];
21211
21858
  if (frameName)
21212
21859
  parts.push(frameName);
@@ -21217,7 +21864,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21217
21864
  * Creates a filename for markdown report based on memoization key components.
21218
21865
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
21219
21866
  */
21220
- const CREATE_FILE_NAME_FN$5 = (strategyName, exchangeName, frameName, timestamp) => {
21867
+ const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
21221
21868
  const parts = [strategyName, exchangeName];
21222
21869
  if (frameName) {
21223
21870
  parts.push(frameName);
@@ -21250,7 +21897,7 @@ function isUnsafe(value) {
21250
21897
  return false;
21251
21898
  }
21252
21899
  /** Maximum number of signals to store per symbol in heatmap reports */
21253
- const MAX_EVENTS$5 = 250;
21900
+ const MAX_EVENTS$6 = 250;
21254
21901
  /**
21255
21902
  * Storage class for accumulating closed signals per strategy and generating heatmap.
21256
21903
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -21276,7 +21923,7 @@ class HeatmapStorage {
21276
21923
  const signals = this.symbolData.get(symbol);
21277
21924
  signals.unshift(data);
21278
21925
  // Trim queue if exceeded MAX_EVENTS per symbol
21279
- if (signals.length > MAX_EVENTS$5) {
21926
+ if (signals.length > MAX_EVENTS$6) {
21280
21927
  signals.pop();
21281
21928
  }
21282
21929
  }
@@ -21527,7 +22174,7 @@ class HeatmapStorage {
21527
22174
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
21528
22175
  const markdown = await this.getReport(strategyName, columns);
21529
22176
  const timestamp = getContextTimestamp();
21530
- const filename = CREATE_FILE_NAME_FN$5(strategyName, this.exchangeName, this.frameName, timestamp);
22177
+ const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
21531
22178
  await Markdown.writeData("heat", markdown, {
21532
22179
  path,
21533
22180
  file: filename,
@@ -21573,7 +22220,7 @@ class HeatMarkdownService {
21573
22220
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21574
22221
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21575
22222
  */
21576
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
22223
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21577
22224
  /**
21578
22225
  * Subscribes to signal emitter to receive tick events.
21579
22226
  * Protected against multiple subscriptions.
@@ -21768,7 +22415,7 @@ class HeatMarkdownService {
21768
22415
  payload,
21769
22416
  });
21770
22417
  if (payload) {
21771
- const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
22418
+ const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
21772
22419
  this.getStorage.clear(key);
21773
22420
  }
21774
22421
  else {
@@ -22799,7 +23446,7 @@ class ClientPartial {
22799
23446
  * @param backtest - Whether running in backtest mode
22800
23447
  * @returns Unique string key for memoization
22801
23448
  */
22802
- const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23449
+ const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22803
23450
  /**
22804
23451
  * Creates a callback function for emitting profit events to partialProfitSubject.
22805
23452
  *
@@ -22921,7 +23568,7 @@ class PartialConnectionService {
22921
23568
  * Key format: "signalId:backtest" or "signalId:live"
22922
23569
  * Value: ClientPartial instance with logger and event emitters
22923
23570
  */
22924
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
23571
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
22925
23572
  return new ClientPartial({
22926
23573
  signalId,
22927
23574
  logger: this.loggerService,
@@ -23011,7 +23658,7 @@ class PartialConnectionService {
23011
23658
  const partial = this.getPartial(data.id, backtest);
23012
23659
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
23013
23660
  await partial.clear(symbol, data, priceClose, backtest);
23014
- const key = CREATE_KEY_FN$c(data.id, backtest);
23661
+ const key = CREATE_KEY_FN$d(data.id, backtest);
23015
23662
  this.getPartial.clear(key);
23016
23663
  };
23017
23664
  }
@@ -23027,7 +23674,7 @@ class PartialConnectionService {
23027
23674
  * @param backtest - Whether running in backtest mode
23028
23675
  * @returns Unique string key for memoization
23029
23676
  */
23030
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
23677
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
23031
23678
  const parts = [symbol, strategyName, exchangeName];
23032
23679
  if (frameName)
23033
23680
  parts.push(frameName);
@@ -23038,7 +23685,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
23038
23685
  * Creates a filename for markdown report based on memoization key components.
23039
23686
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
23040
23687
  */
23041
- const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23688
+ const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23042
23689
  const parts = [symbol, strategyName, exchangeName];
23043
23690
  if (frameName) {
23044
23691
  parts.push(frameName);
@@ -23049,12 +23696,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
23049
23696
  return `${parts.join("_")}-${timestamp}.md`;
23050
23697
  };
23051
23698
  /** Maximum number of events to store in partial reports */
23052
- const MAX_EVENTS$4 = 250;
23699
+ const MAX_EVENTS$5 = 250;
23053
23700
  /**
23054
23701
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
23055
23702
  * Maintains a chronological list of profit and loss level events.
23056
23703
  */
23057
- let ReportStorage$4 = class ReportStorage {
23704
+ let ReportStorage$5 = class ReportStorage {
23058
23705
  constructor(symbol, strategyName, exchangeName, frameName) {
23059
23706
  this.symbol = symbol;
23060
23707
  this.strategyName = strategyName;
@@ -23097,7 +23744,7 @@ let ReportStorage$4 = class ReportStorage {
23097
23744
  backtest,
23098
23745
  });
23099
23746
  // Trim queue if exceeded MAX_EVENTS
23100
- if (this._eventList.length > MAX_EVENTS$4) {
23747
+ if (this._eventList.length > MAX_EVENTS$5) {
23101
23748
  this._eventList.pop();
23102
23749
  }
23103
23750
  }
@@ -23135,7 +23782,7 @@ let ReportStorage$4 = class ReportStorage {
23135
23782
  backtest,
23136
23783
  });
23137
23784
  // Trim queue if exceeded MAX_EVENTS
23138
- if (this._eventList.length > MAX_EVENTS$4) {
23785
+ if (this._eventList.length > MAX_EVENTS$5) {
23139
23786
  this._eventList.pop();
23140
23787
  }
23141
23788
  }
@@ -23211,7 +23858,7 @@ let ReportStorage$4 = class ReportStorage {
23211
23858
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
23212
23859
  const markdown = await this.getReport(symbol, strategyName, columns);
23213
23860
  const timestamp = getContextTimestamp();
23214
- const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23861
+ const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23215
23862
  await Markdown.writeData("partial", markdown, {
23216
23863
  path,
23217
23864
  file: filename,
@@ -23252,7 +23899,7 @@ class PartialMarkdownService {
23252
23899
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23253
23900
  * Each combination gets its own isolated storage instance.
23254
23901
  */
23255
- this.getStorage = functoolsKit.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));
23902
+ this.getStorage = functoolsKit.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));
23256
23903
  /**
23257
23904
  * Subscribes to partial profit/loss signal emitters to receive events.
23258
23905
  * Protected against multiple subscriptions.
@@ -23462,7 +24109,7 @@ class PartialMarkdownService {
23462
24109
  payload,
23463
24110
  });
23464
24111
  if (payload) {
23465
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24112
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23466
24113
  this.getStorage.clear(key);
23467
24114
  }
23468
24115
  else {
@@ -23478,7 +24125,7 @@ class PartialMarkdownService {
23478
24125
  * @param context - Context with strategyName, exchangeName, frameName
23479
24126
  * @returns Unique string key for memoization
23480
24127
  */
23481
- const CREATE_KEY_FN$a = (context) => {
24128
+ const CREATE_KEY_FN$b = (context) => {
23482
24129
  const parts = [context.strategyName, context.exchangeName];
23483
24130
  if (context.frameName)
23484
24131
  parts.push(context.frameName);
@@ -23552,7 +24199,7 @@ class PartialGlobalService {
23552
24199
  * @param context - Context with strategyName, exchangeName and frameName
23553
24200
  * @param methodName - Name of the calling method for error tracking
23554
24201
  */
23555
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
24202
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
23556
24203
  this.loggerService.log("partialGlobalService validate", {
23557
24204
  context,
23558
24205
  methodName,
@@ -24007,7 +24654,7 @@ class ClientBreakeven {
24007
24654
  * @param backtest - Whether running in backtest mode
24008
24655
  * @returns Unique string key for memoization
24009
24656
  */
24010
- const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24657
+ const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24011
24658
  /**
24012
24659
  * Creates a callback function for emitting breakeven events to breakevenSubject.
24013
24660
  *
@@ -24093,7 +24740,7 @@ class BreakevenConnectionService {
24093
24740
  * Key format: "signalId:backtest" or "signalId:live"
24094
24741
  * Value: ClientBreakeven instance with logger and event emitter
24095
24742
  */
24096
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
24743
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
24097
24744
  return new ClientBreakeven({
24098
24745
  signalId,
24099
24746
  logger: this.loggerService,
@@ -24154,7 +24801,7 @@ class BreakevenConnectionService {
24154
24801
  const breakeven = this.getBreakeven(data.id, backtest);
24155
24802
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24156
24803
  await breakeven.clear(symbol, data, priceClose, backtest);
24157
- const key = CREATE_KEY_FN$9(data.id, backtest);
24804
+ const key = CREATE_KEY_FN$a(data.id, backtest);
24158
24805
  this.getBreakeven.clear(key);
24159
24806
  };
24160
24807
  }
@@ -24170,7 +24817,7 @@ class BreakevenConnectionService {
24170
24817
  * @param backtest - Whether running in backtest mode
24171
24818
  * @returns Unique string key for memoization
24172
24819
  */
24173
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24820
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24174
24821
  const parts = [symbol, strategyName, exchangeName];
24175
24822
  if (frameName)
24176
24823
  parts.push(frameName);
@@ -24181,7 +24828,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
24181
24828
  * Creates a filename for markdown report based on memoization key components.
24182
24829
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24183
24830
  */
24184
- const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24831
+ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24185
24832
  const parts = [symbol, strategyName, exchangeName];
24186
24833
  if (frameName) {
24187
24834
  parts.push(frameName);
@@ -24192,12 +24839,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
24192
24839
  return `${parts.join("_")}-${timestamp}.md`;
24193
24840
  };
24194
24841
  /** Maximum number of events to store in breakeven reports */
24195
- const MAX_EVENTS$3 = 250;
24842
+ const MAX_EVENTS$4 = 250;
24196
24843
  /**
24197
24844
  * Storage class for accumulating breakeven events per symbol-strategy pair.
24198
24845
  * Maintains a chronological list of breakeven events.
24199
24846
  */
24200
- let ReportStorage$3 = class ReportStorage {
24847
+ let ReportStorage$4 = class ReportStorage {
24201
24848
  constructor(symbol, strategyName, exchangeName, frameName) {
24202
24849
  this.symbol = symbol;
24203
24850
  this.strategyName = strategyName;
@@ -24238,7 +24885,7 @@ let ReportStorage$3 = class ReportStorage {
24238
24885
  backtest,
24239
24886
  });
24240
24887
  // Trim queue if exceeded MAX_EVENTS
24241
- if (this._eventList.length > MAX_EVENTS$3) {
24888
+ if (this._eventList.length > MAX_EVENTS$4) {
24242
24889
  this._eventList.pop();
24243
24890
  }
24244
24891
  }
@@ -24306,7 +24953,7 @@ let ReportStorage$3 = class ReportStorage {
24306
24953
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
24307
24954
  const markdown = await this.getReport(symbol, strategyName, columns);
24308
24955
  const timestamp = getContextTimestamp();
24309
- const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24956
+ const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24310
24957
  await Markdown.writeData("breakeven", markdown, {
24311
24958
  path,
24312
24959
  file: filename,
@@ -24347,7 +24994,7 @@ class BreakevenMarkdownService {
24347
24994
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24348
24995
  * Each combination gets its own isolated storage instance.
24349
24996
  */
24350
- this.getStorage = functoolsKit.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));
24997
+ this.getStorage = functoolsKit.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));
24351
24998
  /**
24352
24999
  * Subscribes to breakeven signal emitter to receive events.
24353
25000
  * Protected against multiple subscriptions.
@@ -24536,7 +25183,7 @@ class BreakevenMarkdownService {
24536
25183
  payload,
24537
25184
  });
24538
25185
  if (payload) {
24539
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25186
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24540
25187
  this.getStorage.clear(key);
24541
25188
  }
24542
25189
  else {
@@ -24552,7 +25199,7 @@ class BreakevenMarkdownService {
24552
25199
  * @param context - Context with strategyName, exchangeName, frameName
24553
25200
  * @returns Unique string key for memoization
24554
25201
  */
24555
- const CREATE_KEY_FN$7 = (context) => {
25202
+ const CREATE_KEY_FN$8 = (context) => {
24556
25203
  const parts = [context.strategyName, context.exchangeName];
24557
25204
  if (context.frameName)
24558
25205
  parts.push(context.frameName);
@@ -24626,7 +25273,7 @@ class BreakevenGlobalService {
24626
25273
  * @param context - Context with strategyName, exchangeName and frameName
24627
25274
  * @param methodName - Name of the calling method for error tracking
24628
25275
  */
24629
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
25276
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
24630
25277
  this.loggerService.log("breakevenGlobalService validate", {
24631
25278
  context,
24632
25279
  methodName,
@@ -24846,7 +25493,7 @@ class ConfigValidationService {
24846
25493
  * @param backtest - Whether running in backtest mode
24847
25494
  * @returns Unique string key for memoization
24848
25495
  */
24849
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
25496
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24850
25497
  const parts = [symbol, strategyName, exchangeName];
24851
25498
  if (frameName)
24852
25499
  parts.push(frameName);
@@ -24857,7 +25504,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
24857
25504
  * Creates a filename for markdown report based on memoization key components.
24858
25505
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24859
25506
  */
24860
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
25507
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24861
25508
  const parts = [symbol, strategyName, exchangeName];
24862
25509
  if (frameName) {
24863
25510
  parts.push(frameName);
@@ -24868,12 +25515,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
24868
25515
  return `${parts.join("_")}-${timestamp}.md`;
24869
25516
  };
24870
25517
  /** Maximum number of events to store in risk reports */
24871
- const MAX_EVENTS$2 = 250;
25518
+ const MAX_EVENTS$3 = 250;
24872
25519
  /**
24873
25520
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
24874
25521
  * Maintains a chronological list of rejected signals due to risk limits.
24875
25522
  */
24876
- let ReportStorage$2 = class ReportStorage {
25523
+ let ReportStorage$3 = class ReportStorage {
24877
25524
  constructor(symbol, strategyName, exchangeName, frameName) {
24878
25525
  this.symbol = symbol;
24879
25526
  this.strategyName = strategyName;
@@ -24890,7 +25537,7 @@ let ReportStorage$2 = class ReportStorage {
24890
25537
  addRejectionEvent(event) {
24891
25538
  this._eventList.unshift(event);
24892
25539
  // Trim queue if exceeded MAX_EVENTS
24893
- if (this._eventList.length > MAX_EVENTS$2) {
25540
+ if (this._eventList.length > MAX_EVENTS$3) {
24894
25541
  this._eventList.pop();
24895
25542
  }
24896
25543
  }
@@ -24974,7 +25621,7 @@ let ReportStorage$2 = class ReportStorage {
24974
25621
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
24975
25622
  const markdown = await this.getReport(symbol, strategyName, columns);
24976
25623
  const timestamp = getContextTimestamp();
24977
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25624
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24978
25625
  await Markdown.writeData("risk", markdown, {
24979
25626
  path,
24980
25627
  file: filename,
@@ -25015,7 +25662,7 @@ class RiskMarkdownService {
25015
25662
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25016
25663
  * Each combination gets its own isolated storage instance.
25017
25664
  */
25018
- this.getStorage = functoolsKit.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));
25665
+ this.getStorage = functoolsKit.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));
25019
25666
  /**
25020
25667
  * Subscribes to risk rejection emitter to receive rejection events.
25021
25668
  * Protected against multiple subscriptions.
@@ -25204,7 +25851,7 @@ class RiskMarkdownService {
25204
25851
  payload,
25205
25852
  });
25206
25853
  if (payload) {
25207
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25854
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25208
25855
  this.getStorage.clear(key);
25209
25856
  }
25210
25857
  else {
@@ -25502,6 +26149,7 @@ const WILDCARD_TARGET = {
25502
26149
  schedule: true,
25503
26150
  walker: true,
25504
26151
  sync: true,
26152
+ highest_profit: true,
25505
26153
  };
25506
26154
  /**
25507
26155
  * Utility class for managing report services.
@@ -25539,7 +26187,7 @@ class ReportUtils {
25539
26187
  *
25540
26188
  * @returns Cleanup function that unsubscribes from all enabled services
25541
26189
  */
25542
- 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) => {
26190
+ 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) => {
25543
26191
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
25544
26192
  backtest: bt$1,
25545
26193
  breakeven,
@@ -25587,6 +26235,9 @@ class ReportUtils {
25587
26235
  if (sync) {
25588
26236
  unList.push(bt.syncReportService.subscribe());
25589
26237
  }
26238
+ if (highest_profit) {
26239
+ unList.push(bt.highestProfitReportService.subscribe());
26240
+ }
25590
26241
  return functoolsKit.compose(...unList.map((un) => () => void un()));
25591
26242
  };
25592
26243
  /**
@@ -25625,7 +26276,7 @@ class ReportUtils {
25625
26276
  * Report.disable();
25626
26277
  * ```
25627
26278
  */
25628
- 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) => {
26279
+ 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) => {
25629
26280
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
25630
26281
  backtest: bt$1,
25631
26282
  breakeven,
@@ -25672,6 +26323,9 @@ class ReportUtils {
25672
26323
  if (sync) {
25673
26324
  bt.syncReportService.unsubscribe();
25674
26325
  }
26326
+ if (highest_profit) {
26327
+ bt.highestProfitReportService.unsubscribe();
26328
+ }
25675
26329
  };
25676
26330
  }
25677
26331
  }
@@ -27879,6 +28533,60 @@ class SyncReportService {
27879
28533
  }
27880
28534
  }
27881
28535
 
28536
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
28537
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
28538
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
28539
+ /**
28540
+ * Service for logging highest profit events to the JSONL report database.
28541
+ *
28542
+ * Listens to highestProfitSubject and writes each new price record to
28543
+ * Report.writeData() for persistence and analytics.
28544
+ */
28545
+ class HighestProfitReportService {
28546
+ constructor() {
28547
+ this.loggerService = inject(TYPES.loggerService);
28548
+ this.tick = async (data) => {
28549
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
28550
+ await Report.writeData("highest_profit", {
28551
+ timestamp: data.timestamp,
28552
+ symbol: data.symbol,
28553
+ strategyName: data.signal.strategyName,
28554
+ exchangeName: data.exchangeName,
28555
+ frameName: data.frameName,
28556
+ backtest: data.backtest,
28557
+ signalId: data.signal.id,
28558
+ position: data.signal.position,
28559
+ currentPrice: data.currentPrice,
28560
+ priceOpen: data.signal.priceOpen,
28561
+ priceTakeProfit: data.signal.priceTakeProfit,
28562
+ priceStopLoss: data.signal.priceStopLoss,
28563
+ }, {
28564
+ symbol: data.symbol,
28565
+ strategyName: data.signal.strategyName,
28566
+ exchangeName: data.exchangeName,
28567
+ frameName: data.frameName,
28568
+ signalId: data.signal.id,
28569
+ walkerName: "",
28570
+ });
28571
+ };
28572
+ this.subscribe = functoolsKit.singleshot(() => {
28573
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
28574
+ const unsub = highestProfitSubject.subscribe(this.tick);
28575
+ return () => {
28576
+ this.subscribe.clear();
28577
+ unsub();
28578
+ };
28579
+ });
28580
+ this.unsubscribe = async () => {
28581
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
28582
+ if (this.subscribe.hasValue()) {
28583
+ const lastSubscription = this.subscribe();
28584
+ lastSubscription();
28585
+ }
28586
+ };
28587
+ }
28588
+ }
28589
+
27882
28590
  /**
27883
28591
  * Creates a unique key for memoizing ReportStorage instances.
27884
28592
  *
@@ -27892,7 +28600,7 @@ class SyncReportService {
27892
28600
  * @returns Colon-separated key string for memoization
27893
28601
  * @internal
27894
28602
  */
27895
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28603
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27896
28604
  const parts = [symbol, strategyName, exchangeName];
27897
28605
  if (frameName)
27898
28606
  parts.push(frameName);
@@ -27912,7 +28620,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
27912
28620
  * @returns Underscore-separated filename with .md extension
27913
28621
  * @internal
27914
28622
  */
27915
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28623
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
27916
28624
  const parts = [symbol, strategyName, exchangeName];
27917
28625
  if (frameName) {
27918
28626
  parts.push(frameName);
@@ -27927,7 +28635,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
27927
28635
  * Older events are discarded when this limit is exceeded.
27928
28636
  * @internal
27929
28637
  */
27930
- const MAX_EVENTS$1 = 250;
28638
+ const MAX_EVENTS$2 = 250;
27931
28639
  /**
27932
28640
  * In-memory storage for accumulating strategy events per symbol-strategy pair.
27933
28641
  *
@@ -27940,7 +28648,7 @@ const MAX_EVENTS$1 = 250;
27940
28648
  *
27941
28649
  * @internal
27942
28650
  */
27943
- let ReportStorage$1 = class ReportStorage {
28651
+ let ReportStorage$2 = class ReportStorage {
27944
28652
  /**
27945
28653
  * Creates a new ReportStorage instance.
27946
28654
  *
@@ -27966,7 +28674,7 @@ let ReportStorage$1 = class ReportStorage {
27966
28674
  */
27967
28675
  addEvent(event) {
27968
28676
  this._eventList.unshift(event);
27969
- if (this._eventList.length > MAX_EVENTS$1) {
28677
+ if (this._eventList.length > MAX_EVENTS$2) {
27970
28678
  this._eventList.pop();
27971
28679
  }
27972
28680
  }
@@ -28071,7 +28779,7 @@ let ReportStorage$1 = class ReportStorage {
28071
28779
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
28072
28780
  const markdown = await this.getReport(symbol, strategyName, columns);
28073
28781
  const timestamp = getContextTimestamp();
28074
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28782
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28075
28783
  await Markdown.writeData("strategy", markdown, {
28076
28784
  path,
28077
28785
  file: filename,
@@ -28140,7 +28848,7 @@ class StrategyMarkdownService {
28140
28848
  *
28141
28849
  * @internal
28142
28850
  */
28143
- this.getStorage = functoolsKit.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));
28851
+ this.getStorage = functoolsKit.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));
28144
28852
  /**
28145
28853
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
28146
28854
  *
@@ -28708,7 +29416,7 @@ class StrategyMarkdownService {
28708
29416
  this.clear = async (payload) => {
28709
29417
  this.loggerService.log("strategyMarkdownService clear", { payload });
28710
29418
  if (payload) {
28711
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29419
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28712
29420
  this.getStorage.clear(key);
28713
29421
  }
28714
29422
  else {
@@ -28816,7 +29524,7 @@ class StrategyMarkdownService {
28816
29524
  * Creates a unique key for memoizing ReportStorage instances.
28817
29525
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28818
29526
  */
28819
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29527
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28820
29528
  const parts = [symbol, strategyName, exchangeName];
28821
29529
  if (frameName)
28822
29530
  parts.push(frameName);
@@ -28827,7 +29535,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
28827
29535
  * Creates a filename for the markdown report.
28828
29536
  * Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
28829
29537
  */
28830
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29538
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28831
29539
  const parts = [symbol, strategyName, exchangeName];
28832
29540
  if (frameName) {
28833
29541
  parts.push(frameName);
@@ -28838,12 +29546,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
28838
29546
  return `${parts.join("_")}-${timestamp}.md`;
28839
29547
  };
28840
29548
  /** Maximum number of events to store in sync reports */
28841
- const MAX_EVENTS = 250;
29549
+ const MAX_EVENTS$1 = 250;
28842
29550
  /**
28843
29551
  * Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
28844
29552
  * Maintains a chronological list of signal-open and signal-close events.
28845
29553
  */
28846
- class ReportStorage {
29554
+ let ReportStorage$1 = class ReportStorage {
28847
29555
  constructor(symbol, strategyName, exchangeName, frameName) {
28848
29556
  this.symbol = symbol;
28849
29557
  this.strategyName = strategyName;
@@ -28853,7 +29561,7 @@ class ReportStorage {
28853
29561
  }
28854
29562
  addEvent(event) {
28855
29563
  this._eventList.unshift(event);
28856
- if (this._eventList.length > MAX_EVENTS) {
29564
+ if (this._eventList.length > MAX_EVENTS$1) {
28857
29565
  this._eventList.pop();
28858
29566
  }
28859
29567
  }
@@ -28914,7 +29622,7 @@ class ReportStorage {
28914
29622
  async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
28915
29623
  const markdown = await this.getReport(symbol, strategyName, columns);
28916
29624
  const timestamp = getContextTimestamp();
28917
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29625
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28918
29626
  await Markdown.writeData("sync", markdown, {
28919
29627
  path,
28920
29628
  file: filename,
@@ -28925,7 +29633,7 @@ class ReportStorage {
28925
29633
  frameName: this.frameName,
28926
29634
  });
28927
29635
  }
28928
- }
29636
+ };
28929
29637
  /**
28930
29638
  * Service for generating and saving signal sync markdown reports.
28931
29639
  *
@@ -28948,7 +29656,7 @@ class ReportStorage {
28948
29656
  class SyncMarkdownService {
28949
29657
  constructor() {
28950
29658
  this.loggerService = inject(TYPES.loggerService);
28951
- this.getStorage = functoolsKit.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));
29659
+ this.getStorage = functoolsKit.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));
28952
29660
  this.subscribe = functoolsKit.singleshot(() => {
28953
29661
  this.loggerService.log("syncMarkdownService init");
28954
29662
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -29022,6 +29730,189 @@ class SyncMarkdownService {
29022
29730
  };
29023
29731
  this.clear = async (payload) => {
29024
29732
  this.loggerService.log("syncMarkdownService clear", { payload });
29733
+ if (payload) {
29734
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29735
+ this.getStorage.clear(key);
29736
+ }
29737
+ else {
29738
+ this.getStorage.clear();
29739
+ }
29740
+ };
29741
+ }
29742
+ }
29743
+
29744
+ /**
29745
+ * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
29746
+ */
29747
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29748
+ const parts = [symbol, strategyName, exchangeName];
29749
+ if (frameName)
29750
+ parts.push(frameName);
29751
+ parts.push(backtest ? "backtest" : "live");
29752
+ return parts.join(":");
29753
+ };
29754
+ /**
29755
+ * Creates a filename for the markdown report.
29756
+ */
29757
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29758
+ const parts = [symbol, strategyName, exchangeName];
29759
+ if (frameName) {
29760
+ parts.push(frameName);
29761
+ parts.push("backtest");
29762
+ }
29763
+ else
29764
+ parts.push("live");
29765
+ return `${parts.join("_")}-${timestamp}.md`;
29766
+ };
29767
+ /** Maximum number of events to store per combination */
29768
+ const MAX_EVENTS = 250;
29769
+ /**
29770
+ * Accumulates highest profit events per symbol-strategy-exchange-frame combination.
29771
+ */
29772
+ class ReportStorage {
29773
+ constructor(symbol, strategyName, exchangeName, frameName) {
29774
+ this.symbol = symbol;
29775
+ this.strategyName = strategyName;
29776
+ this.exchangeName = exchangeName;
29777
+ this.frameName = frameName;
29778
+ this._eventList = [];
29779
+ }
29780
+ /**
29781
+ * Adds a highest profit event to the storage.
29782
+ */
29783
+ addEvent(data, currentPrice, backtest, timestamp) {
29784
+ this._eventList.unshift({
29785
+ timestamp,
29786
+ symbol: data.symbol,
29787
+ strategyName: data.strategyName,
29788
+ signalId: data.id,
29789
+ position: data.position,
29790
+ currentPrice,
29791
+ priceOpen: data.priceOpen,
29792
+ priceTakeProfit: data.priceTakeProfit,
29793
+ priceStopLoss: data.priceStopLoss,
29794
+ backtest,
29795
+ });
29796
+ if (this._eventList.length > MAX_EVENTS) {
29797
+ this._eventList.pop();
29798
+ }
29799
+ }
29800
+ /**
29801
+ * Returns aggregated statistics from accumulated events.
29802
+ */
29803
+ async getData() {
29804
+ return {
29805
+ eventList: this._eventList,
29806
+ totalEvents: this._eventList.length,
29807
+ };
29808
+ }
29809
+ /**
29810
+ * Generates a markdown report table for this storage.
29811
+ */
29812
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
29813
+ const stats = await this.getData();
29814
+ if (stats.totalEvents === 0) {
29815
+ return [
29816
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29817
+ "",
29818
+ "No highest profit events recorded yet.",
29819
+ ].join("\n");
29820
+ }
29821
+ const visibleColumns = [];
29822
+ for (const col of columns) {
29823
+ if (await col.isVisible()) {
29824
+ visibleColumns.push(col);
29825
+ }
29826
+ }
29827
+ const header = visibleColumns.map((col) => col.label);
29828
+ const separator = visibleColumns.map(() => "---");
29829
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
29830
+ const tableData = [header, separator, ...rows];
29831
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
29832
+ return [
29833
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29834
+ "",
29835
+ table,
29836
+ "",
29837
+ `**Total events:** ${stats.totalEvents}`,
29838
+ ].join("\n");
29839
+ }
29840
+ /**
29841
+ * Saves the report to disk.
29842
+ */
29843
+ async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
29844
+ const markdown = await this.getReport(symbol, strategyName, columns);
29845
+ const timestamp = getContextTimestamp();
29846
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29847
+ await Markdown.writeData("highest_profit", markdown, {
29848
+ path,
29849
+ file: filename,
29850
+ symbol: this.symbol,
29851
+ signalId: "",
29852
+ strategyName: this.strategyName,
29853
+ exchangeName: this.exchangeName,
29854
+ frameName: this.frameName,
29855
+ });
29856
+ }
29857
+ }
29858
+ /**
29859
+ * Service for generating and saving highest profit markdown reports.
29860
+ *
29861
+ * Listens to highestProfitSubject and accumulates events per
29862
+ * symbol-strategy-exchange-frame combination. Provides getData(),
29863
+ * getReport(), and dump() methods matching the Partial pattern.
29864
+ */
29865
+ class HighestProfitMarkdownService {
29866
+ constructor() {
29867
+ this.loggerService = inject(TYPES.loggerService);
29868
+ this.getStorage = functoolsKit.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));
29869
+ this.subscribe = functoolsKit.singleshot(() => {
29870
+ this.loggerService.log("highestProfitMarkdownService init");
29871
+ const unsub = highestProfitSubject.subscribe(this.tick);
29872
+ return () => {
29873
+ this.subscribe.clear();
29874
+ this.clear();
29875
+ unsub();
29876
+ };
29877
+ });
29878
+ this.unsubscribe = async () => {
29879
+ this.loggerService.log("highestProfitMarkdownService unsubscribe");
29880
+ if (this.subscribe.hasValue()) {
29881
+ const lastSubscription = this.subscribe();
29882
+ lastSubscription();
29883
+ }
29884
+ };
29885
+ this.tick = async (data) => {
29886
+ this.loggerService.log("highestProfitMarkdownService tick", { data });
29887
+ const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
29888
+ storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
29889
+ };
29890
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
29891
+ this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
29892
+ if (!this.subscribe.hasValue()) {
29893
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
29894
+ }
29895
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29896
+ return storage.getData();
29897
+ };
29898
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
29899
+ this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
29900
+ if (!this.subscribe.hasValue()) {
29901
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
29902
+ }
29903
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29904
+ return storage.getReport(symbol, strategyName, columns);
29905
+ };
29906
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
29907
+ this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
29908
+ if (!this.subscribe.hasValue()) {
29909
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
29910
+ }
29911
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29912
+ await storage.dump(symbol, strategyName, path, columns);
29913
+ };
29914
+ this.clear = async (payload) => {
29915
+ this.loggerService.log("highestProfitMarkdownService clear", { payload });
29025
29916
  if (payload) {
29026
29917
  const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29027
29918
  this.getStorage.clear(key);
@@ -29371,6 +30262,7 @@ class TimeMetaService {
29371
30262
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
29372
30263
  provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
29373
30264
  provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
30265
+ provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
29374
30266
  }
29375
30267
  {
29376
30268
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -29384,6 +30276,7 @@ class TimeMetaService {
29384
30276
  provide(TYPES.riskReportService, () => new RiskReportService());
29385
30277
  provide(TYPES.strategyReportService, () => new StrategyReportService());
29386
30278
  provide(TYPES.syncReportService, () => new SyncReportService());
30279
+ provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
29387
30280
  }
29388
30281
  {
29389
30282
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -29466,6 +30359,7 @@ const markdownServices = {
29466
30359
  riskMarkdownService: inject(TYPES.riskMarkdownService),
29467
30360
  strategyMarkdownService: inject(TYPES.strategyMarkdownService),
29468
30361
  syncMarkdownService: inject(TYPES.syncMarkdownService),
30362
+ highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
29469
30363
  };
29470
30364
  const reportServices = {
29471
30365
  backtestReportService: inject(TYPES.backtestReportService),
@@ -29479,6 +30373,7 @@ const reportServices = {
29479
30373
  riskReportService: inject(TYPES.riskReportService),
29480
30374
  strategyReportService: inject(TYPES.strategyReportService),
29481
30375
  syncReportService: inject(TYPES.syncReportService),
30376
+ highestProfitReportService: inject(TYPES.highestProfitReportService),
29482
30377
  };
29483
30378
  const validationServices = {
29484
30379
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -31250,7 +32145,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
31250
32145
  *
31251
32146
  * @param newStopLossPrice - Desired absolute stop-loss price
31252
32147
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31253
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32148
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31254
32149
  * @returns percentShift to pass to `commitTrailingStop`
31255
32150
  *
31256
32151
  * @example
@@ -31272,7 +32167,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
31272
32167
  *
31273
32168
  * @param newTakeProfitPrice - Desired absolute take-profit price
31274
32169
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31275
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32170
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31276
32171
  * @returns percentShift to pass to `commitTrailingTake`
31277
32172
  *
31278
32173
  * @example
@@ -31297,7 +32192,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
31297
32192
  *
31298
32193
  * @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
31299
32194
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31300
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32195
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31301
32196
  * @param position - Position direction: "long" or "short"
31302
32197
  * @returns Absolute stop-loss price corresponding to the given percentShift
31303
32198
  *
@@ -31324,7 +32219,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
31324
32219
  *
31325
32220
  * @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
31326
32221
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31327
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32222
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31328
32223
  * @param position - Position direction: "long" or "short"
31329
32224
  * @returns Absolute take-profit price corresponding to the given percentShift
31330
32225
  *
@@ -31363,7 +32258,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
31363
32258
  * Breakeven moves the SL to the effective entry price (effectivePriceOpen).
31364
32259
  * The value is the same regardless of position direction.
31365
32260
  *
31366
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32261
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31367
32262
  * @returns New stop-loss price equal to the effective entry price
31368
32263
  *
31369
32264
  * @example
@@ -32285,13 +33180,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
32285
33180
  const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
32286
33181
  const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
32287
33182
  const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
32288
- const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
33183
+ const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
32289
33184
  const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
32290
33185
  const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
32291
33186
  const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
32292
33187
  const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
32293
33188
  const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
32294
33189
  const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
33190
+ const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
33191
+ const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
33192
+ const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
33193
+ const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
33194
+ const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
33195
+ const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
33196
+ const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
33197
+ const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
33198
+ const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
32295
33199
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
32296
33200
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
32297
33201
  /**
@@ -32558,7 +33462,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
32558
33462
  if (!signal) {
32559
33463
  return false;
32560
33464
  }
32561
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33465
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32562
33466
  if (effectivePriceOpen === null) {
32563
33467
  return false;
32564
33468
  }
@@ -32638,7 +33542,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
32638
33542
  if (!signal) {
32639
33543
  return false;
32640
33544
  }
32641
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33545
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32642
33546
  if (effectivePriceOpen === null) {
32643
33547
  return false;
32644
33548
  }
@@ -32688,7 +33592,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
32688
33592
  if (!signal) {
32689
33593
  return false;
32690
33594
  }
32691
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33595
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32692
33596
  if (effectivePriceOpen === null) {
32693
33597
  return false;
32694
33598
  }
@@ -32739,7 +33643,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
32739
33643
  if (!signal) {
32740
33644
  return false;
32741
33645
  }
32742
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33646
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32743
33647
  if (effectivePriceOpen === null) {
32744
33648
  return false;
32745
33649
  }
@@ -32801,7 +33705,7 @@ async function commitBreakeven(symbol) {
32801
33705
  if (!signal) {
32802
33706
  return false;
32803
33707
  }
32804
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33708
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32805
33709
  if (effectivePriceOpen === null) {
32806
33710
  return false;
32807
33711
  }
@@ -33097,26 +34001,26 @@ async function getBreakeven(symbol, currentPrice) {
33097
34001
  *
33098
34002
  * @example
33099
34003
  * ```typescript
33100
- * import { getPositionAveragePrice } from "backtest-kit";
34004
+ * import { getPositionEffectivePrice } from "backtest-kit";
33101
34005
  *
33102
- * const avgPrice = await getPositionAveragePrice("BTCUSDT");
34006
+ * const avgPrice = await getPositionEffectivePrice("BTCUSDT");
33103
34007
  * // No DCA: avgPrice === priceOpen
33104
34008
  * // After DCA at lower price: avgPrice < priceOpen
33105
34009
  * ```
33106
34010
  */
33107
- async function getPositionAveragePrice(symbol) {
34011
+ async function getPositionEffectivePrice(symbol) {
33108
34012
  bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
33109
34013
  symbol,
33110
34014
  });
33111
34015
  if (!ExecutionContextService.hasContext()) {
33112
- throw new Error("getPositionAveragePrice requires an execution context");
34016
+ throw new Error("getPositionEffectivePrice requires an execution context");
33113
34017
  }
33114
34018
  if (!MethodContextService.hasContext()) {
33115
- throw new Error("getPositionAveragePrice requires a method context");
34019
+ throw new Error("getPositionEffectivePrice requires a method context");
33116
34020
  }
33117
34021
  const { backtest: isBacktest } = bt.executionContextService.context;
33118
34022
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33119
- return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34023
+ return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33120
34024
  }
33121
34025
  /**
33122
34026
  * Returns the number of DCA entries made for the current pending signal.
@@ -33471,6 +34375,284 @@ async function getPositionPartials(symbol) {
33471
34375
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33472
34376
  return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
33473
34377
  }
34378
+ /**
34379
+ * Returns the list of DCA entry prices and costs for the current pending signal.
34380
+ *
34381
+ * Each element represents a single position entry — the initial open or a subsequent
34382
+ * DCA entry added via commitAverageBuy.
34383
+ *
34384
+ * Returns null if no pending signal exists.
34385
+ * Returns a single-element array if no DCA entries were made.
34386
+ *
34387
+ * Each entry contains:
34388
+ * - `price` — execution price of this entry
34389
+ * - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
34390
+ *
34391
+ * @param symbol - Trading pair symbol
34392
+ * @returns Promise resolving to array of entry records or null
34393
+ *
34394
+ * @example
34395
+ * ```typescript
34396
+ * import { getPositionEntries } from "backtest-kit";
34397
+ *
34398
+ * const entries = await getPositionEntries("BTCUSDT");
34399
+ * // No DCA: [{ price: 43000, cost: 100 }]
34400
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
34401
+ * ```
34402
+ */
34403
+ async function getPositionEntries(symbol) {
34404
+ bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
34405
+ if (!ExecutionContextService.hasContext()) {
34406
+ throw new Error("getPositionEntries requires an execution context");
34407
+ }
34408
+ if (!MethodContextService.hasContext()) {
34409
+ throw new Error("getPositionEntries requires a method context");
34410
+ }
34411
+ const { backtest: isBacktest } = bt.executionContextService.context;
34412
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34413
+ return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
34414
+ }
34415
+ /**
34416
+ * Returns the original estimated duration for the current pending signal.
34417
+ *
34418
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
34419
+ * number of minutes the position is expected to be active before `time_expired`.
34420
+ *
34421
+ * Returns null if no pending signal exists.
34422
+ *
34423
+ * @param symbol - Trading pair symbol
34424
+ * @returns Promise resolving to estimated duration in minutes or null
34425
+ *
34426
+ * @example
34427
+ * ```typescript
34428
+ * import { getPositionEstimateMinutes } from "backtest-kit";
34429
+ *
34430
+ * const estimate = await getPositionEstimateMinutes("BTCUSDT");
34431
+ * // e.g. 120 (2 hours)
34432
+ * ```
34433
+ */
34434
+ async function getPositionEstimateMinutes(symbol) {
34435
+ bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
34436
+ if (!ExecutionContextService.hasContext()) {
34437
+ throw new Error("getPositionEstimateMinutes requires an execution context");
34438
+ }
34439
+ if (!MethodContextService.hasContext()) {
34440
+ throw new Error("getPositionEstimateMinutes requires a method context");
34441
+ }
34442
+ const { backtest: isBacktest } = bt.executionContextService.context;
34443
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34444
+ return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34445
+ }
34446
+ /**
34447
+ * Returns the remaining time before the position expires, clamped to zero.
34448
+ *
34449
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
34450
+ * Returns 0 once the estimate is exceeded (never negative).
34451
+ *
34452
+ * Returns null if no pending signal exists.
34453
+ *
34454
+ * @param symbol - Trading pair symbol
34455
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
34456
+ *
34457
+ * @example
34458
+ * ```typescript
34459
+ * import { getPositionCountdownMinutes } from "backtest-kit";
34460
+ *
34461
+ * const remaining = await getPositionCountdownMinutes("BTCUSDT");
34462
+ * // e.g. 45 (45 minutes left)
34463
+ * // 0 when expired
34464
+ * ```
34465
+ */
34466
+ async function getPositionCountdownMinutes(symbol) {
34467
+ bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
34468
+ if (!ExecutionContextService.hasContext()) {
34469
+ throw new Error("getPositionCountdownMinutes requires an execution context");
34470
+ }
34471
+ if (!MethodContextService.hasContext()) {
34472
+ throw new Error("getPositionCountdownMinutes requires a method context");
34473
+ }
34474
+ const { backtest: isBacktest } = bt.executionContextService.context;
34475
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34476
+ return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34477
+ }
34478
+ /**
34479
+ * Returns the best price reached in the profit direction during this position's life.
34480
+ *
34481
+ * Initialized at position open with the entry price and timestamp.
34482
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
34483
+ * - LONG: tracks the highest price seen above effective entry
34484
+ * - SHORT: tracks the lowest price seen below effective entry
34485
+ *
34486
+ * Returns null if no pending signal exists.
34487
+ * Never returns null when a signal is active — always contains at least the entry price.
34488
+ *
34489
+ * @param symbol - Trading pair symbol
34490
+ * @returns Promise resolving to `{ price, timestamp }` record or null
34491
+ *
34492
+ * @example
34493
+ * ```typescript
34494
+ * import { getPositionHighestProfitPrice } from "backtest-kit";
34495
+ *
34496
+ * const peak = await getPositionHighestProfitPrice("BTCUSDT");
34497
+ * // e.g. { price: 44500, timestamp: 1700000000000 }
34498
+ * ```
34499
+ */
34500
+ async function getPositionHighestProfitPrice(symbol) {
34501
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
34502
+ if (!ExecutionContextService.hasContext()) {
34503
+ throw new Error("getPositionHighestProfitPrice requires an execution context");
34504
+ }
34505
+ if (!MethodContextService.hasContext()) {
34506
+ throw new Error("getPositionHighestProfitPrice requires a method context");
34507
+ }
34508
+ const { backtest: isBacktest } = bt.executionContextService.context;
34509
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34510
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34511
+ }
34512
+ /**
34513
+ * Returns the timestamp when the best profit price was recorded during this position's life.
34514
+ *
34515
+ * Returns null if no pending signal exists.
34516
+ *
34517
+ * @param symbol - Trading pair symbol
34518
+ * @returns Promise resolving to timestamp in milliseconds or null
34519
+ *
34520
+ * @example
34521
+ * ```typescript
34522
+ * import { getPositionHighestProfitTimestamp } from "backtest-kit";
34523
+ *
34524
+ * const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
34525
+ * // e.g. 1700000000000
34526
+ * ```
34527
+ */
34528
+ async function getPositionHighestProfitTimestamp(symbol) {
34529
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
34530
+ if (!ExecutionContextService.hasContext()) {
34531
+ throw new Error("getPositionHighestProfitTimestamp requires an execution context");
34532
+ }
34533
+ if (!MethodContextService.hasContext()) {
34534
+ throw new Error("getPositionHighestProfitTimestamp requires a method context");
34535
+ }
34536
+ const { backtest: isBacktest } = bt.executionContextService.context;
34537
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34538
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
34539
+ }
34540
+ /**
34541
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
34542
+ *
34543
+ * Returns null if no pending signal exists.
34544
+ *
34545
+ * @param symbol - Trading pair symbol
34546
+ * @returns Promise resolving to PnL percentage or null
34547
+ *
34548
+ * @example
34549
+ * ```typescript
34550
+ * import { getPositionHighestPnlPercentage } from "backtest-kit";
34551
+ *
34552
+ * const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
34553
+ * // e.g. 3.5
34554
+ * ```
34555
+ */
34556
+ async function getPositionHighestPnlPercentage(symbol) {
34557
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
34558
+ if (!ExecutionContextService.hasContext()) {
34559
+ throw new Error("getPositionHighestPnlPercentage requires an execution context");
34560
+ }
34561
+ if (!MethodContextService.hasContext()) {
34562
+ throw new Error("getPositionHighestPnlPercentage requires a method context");
34563
+ }
34564
+ const { backtest: isBacktest } = bt.executionContextService.context;
34565
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34566
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
34567
+ }
34568
+ /**
34569
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
34570
+ *
34571
+ * Returns null if no pending signal exists.
34572
+ *
34573
+ * @param symbol - Trading pair symbol
34574
+ * @returns Promise resolving to PnL cost or null
34575
+ *
34576
+ * @example
34577
+ * ```typescript
34578
+ * import { getPositionHighestPnlCost } from "backtest-kit";
34579
+ *
34580
+ * const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
34581
+ * // e.g. 35.5
34582
+ * ```
34583
+ */
34584
+ async function getPositionHighestPnlCost(symbol) {
34585
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
34586
+ if (!ExecutionContextService.hasContext()) {
34587
+ throw new Error("getPositionHighestPnlCost requires an execution context");
34588
+ }
34589
+ if (!MethodContextService.hasContext()) {
34590
+ throw new Error("getPositionHighestPnlCost requires a method context");
34591
+ }
34592
+ const { backtest: isBacktest } = bt.executionContextService.context;
34593
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34594
+ return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
34595
+ }
34596
+ /**
34597
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
34598
+ *
34599
+ * Returns null if no pending signal exists.
34600
+ *
34601
+ * @param symbol - Trading pair symbol
34602
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
34603
+ *
34604
+ * @example
34605
+ * ```typescript
34606
+ * import { getPositionHighestProfitBreakeven } from "backtest-kit";
34607
+ *
34608
+ * const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
34609
+ * // e.g. true (price reached the breakeven threshold at its peak)
34610
+ * ```
34611
+ */
34612
+ async function getPositionHighestProfitBreakeven(symbol) {
34613
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
34614
+ if (!ExecutionContextService.hasContext()) {
34615
+ throw new Error("getPositionHighestProfitBreakeven requires an execution context");
34616
+ }
34617
+ if (!MethodContextService.hasContext()) {
34618
+ throw new Error("getPositionHighestProfitBreakeven requires a method context");
34619
+ }
34620
+ const { backtest: isBacktest } = bt.executionContextService.context;
34621
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34622
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
34623
+ }
34624
+ /**
34625
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
34626
+ *
34627
+ * Measures how long the position has been pulling back from its peak profit level.
34628
+ * Zero when called at the exact moment the peak was set.
34629
+ * Grows continuously as price moves away from the peak without setting a new record.
34630
+ *
34631
+ * Returns null if no pending signal exists.
34632
+ *
34633
+ * @param symbol - Trading pair symbol
34634
+ * @returns Promise resolving to drawdown duration in minutes or null
34635
+ *
34636
+ * @example
34637
+ * ```typescript
34638
+ * import { getPositionDrawdownMinutes } from "backtest-kit";
34639
+ *
34640
+ * const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
34641
+ * // e.g. 30 (30 minutes since the highest profit price)
34642
+ * ```
34643
+ */
34644
+ async function getPositionDrawdownMinutes(symbol) {
34645
+ bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
34646
+ if (!ExecutionContextService.hasContext()) {
34647
+ throw new Error("getPositionDrawdownMinutes requires an execution context");
34648
+ }
34649
+ if (!MethodContextService.hasContext()) {
34650
+ throw new Error("getPositionDrawdownMinutes requires a method context");
34651
+ }
34652
+ const { backtest: isBacktest } = bt.executionContextService.context;
34653
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34654
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34655
+ }
33474
34656
  /**
33475
34657
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
33476
34658
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -33648,6 +34830,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
33648
34830
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
33649
34831
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
33650
34832
  const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
34833
+ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
34834
+ const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
33651
34835
  /**
33652
34836
  * Subscribes to all signal events with queued async processing.
33653
34837
  *
@@ -34774,6 +35958,33 @@ function listenSyncOnce(filterFn, fn) {
34774
35958
  bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
34775
35959
  return syncSubject.filter(filterFn).once(fn);
34776
35960
  }
35961
+ /**
35962
+ * Subscribes to highest profit events with queued async processing.
35963
+ * Emits when a signal reaches a new highest profit level during its lifecycle.
35964
+ * Events are processed sequentially in order received, even if callback is async.
35965
+ * Uses queued wrapper to prevent concurrent execution of the callback.
35966
+ * Useful for tracking profit milestones and implementing dynamic management logic.
35967
+ *
35968
+ * @param fn - Callback function to handle highest profit events
35969
+ * @return Unsubscribe function to stop listening to events
35970
+ */
35971
+ function listenHighestProfit(fn) {
35972
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
35973
+ return highestProfitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
35974
+ }
35975
+ /**
35976
+ * Subscribes to filtered highest profit events with one-time execution.
35977
+ * Listens for events matching the filter predicate, then executes callback once
35978
+ * and automatically unsubscribes. Useful for waiting for specific profit conditions.
35979
+ *
35980
+ * @param filterFn - Predicate to filter which events trigger the callback
35981
+ * @param fn - Callback function to handle the filtered event (called only once)
35982
+ * @returns Unsubscribe function to cancel the listener before it fires
35983
+ */
35984
+ function listenHighestProfitOnce(filterFn, fn) {
35985
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
35986
+ return highestProfitSubject.filter(filterFn).once(fn);
35987
+ }
34777
35988
 
34778
35989
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
34779
35990
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -34787,7 +35998,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
34787
35998
  const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
34788
35999
  const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
34789
36000
  const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
34790
- const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
36001
+ const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
34791
36002
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
34792
36003
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
34793
36004
  const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
@@ -34795,6 +36006,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
34795
36006
  const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
34796
36007
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
34797
36008
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
36009
+ const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
36010
+ const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
36011
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
36012
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
36013
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
36014
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
36015
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
36016
+ const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
34798
36017
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
34799
36018
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
34800
36019
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -35367,7 +36586,7 @@ class BacktestUtils {
35367
36586
  * @param context - Execution context with strategyName, exchangeName, and frameName
35368
36587
  * @returns Effective entry price, or null if no active position
35369
36588
  */
35370
- this.getPositionAveragePrice = async (symbol, context) => {
36589
+ this.getPositionEffectivePrice = async (symbol, context) => {
35371
36590
  bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
35372
36591
  symbol,
35373
36592
  context,
@@ -35383,7 +36602,7 @@ class BacktestUtils {
35383
36602
  actions &&
35384
36603
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
35385
36604
  }
35386
- return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
36605
+ return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
35387
36606
  };
35388
36607
  /**
35389
36608
  * Returns the total number of base-asset units currently held in the position.
@@ -35601,6 +36820,230 @@ class BacktestUtils {
35601
36820
  }
35602
36821
  return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
35603
36822
  };
36823
+ /**
36824
+ * Returns the original estimated duration for the current pending signal.
36825
+ *
36826
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
36827
+ * number of minutes the position is expected to be active before `time_expired`.
36828
+ *
36829
+ * Returns null if no pending signal exists.
36830
+ *
36831
+ * @param symbol - Trading pair symbol
36832
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36833
+ * @returns Estimated duration in minutes, or null if no active position
36834
+ */
36835
+ this.getPositionEstimateMinutes = async (symbol, context) => {
36836
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
36837
+ symbol,
36838
+ context,
36839
+ });
36840
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36841
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36842
+ {
36843
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36844
+ riskName &&
36845
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36846
+ riskList &&
36847
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36848
+ actions &&
36849
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36850
+ }
36851
+ return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
36852
+ };
36853
+ /**
36854
+ * Returns the remaining time before the position expires, clamped to zero.
36855
+ *
36856
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
36857
+ * Returns 0 once the estimate is exceeded (never negative).
36858
+ *
36859
+ * Returns null if no pending signal exists.
36860
+ *
36861
+ * @param symbol - Trading pair symbol
36862
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36863
+ * @returns Remaining minutes (≥ 0), or null if no active position
36864
+ */
36865
+ this.getPositionCountdownMinutes = async (symbol, context) => {
36866
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
36867
+ symbol,
36868
+ context,
36869
+ });
36870
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36871
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36872
+ {
36873
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36874
+ riskName &&
36875
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36876
+ riskList &&
36877
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36878
+ actions &&
36879
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36880
+ }
36881
+ return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
36882
+ };
36883
+ /**
36884
+ * Returns the best price reached in the profit direction during this position's life.
36885
+ *
36886
+ * Returns null if no pending signal exists.
36887
+ *
36888
+ * @param symbol - Trading pair symbol
36889
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36890
+ * @returns price or null if no active position
36891
+ */
36892
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
36893
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
36894
+ symbol,
36895
+ context,
36896
+ });
36897
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36898
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36899
+ {
36900
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36901
+ riskName &&
36902
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36903
+ riskList &&
36904
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36905
+ actions &&
36906
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36907
+ }
36908
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
36909
+ };
36910
+ /**
36911
+ * Returns the timestamp when the best profit price was recorded during this position's life.
36912
+ *
36913
+ * Returns null if no pending signal exists.
36914
+ *
36915
+ * @param symbol - Trading pair symbol
36916
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36917
+ * @returns timestamp in milliseconds or null if no active position
36918
+ */
36919
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
36920
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
36921
+ symbol,
36922
+ context,
36923
+ });
36924
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36925
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36926
+ {
36927
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36928
+ riskName &&
36929
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36930
+ riskList &&
36931
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36932
+ actions &&
36933
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36934
+ }
36935
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
36936
+ };
36937
+ /**
36938
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
36939
+ *
36940
+ * Returns null if no pending signal exists.
36941
+ *
36942
+ * @param symbol - Trading pair symbol
36943
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36944
+ * @returns PnL percentage or null if no active position
36945
+ */
36946
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
36947
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
36948
+ symbol,
36949
+ context,
36950
+ });
36951
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36952
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36953
+ {
36954
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36955
+ riskName &&
36956
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36957
+ riskList &&
36958
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36959
+ actions &&
36960
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36961
+ }
36962
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
36963
+ };
36964
+ /**
36965
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
36966
+ *
36967
+ * Returns null if no pending signal exists.
36968
+ *
36969
+ * @param symbol - Trading pair symbol
36970
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36971
+ * @returns PnL cost or null if no active position
36972
+ */
36973
+ this.getPositionHighestPnlCost = async (symbol, context) => {
36974
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
36975
+ symbol,
36976
+ context,
36977
+ });
36978
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36979
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36980
+ {
36981
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36982
+ riskName &&
36983
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36984
+ riskList &&
36985
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36986
+ actions &&
36987
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
36988
+ }
36989
+ return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
36990
+ };
36991
+ /**
36992
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
36993
+ *
36994
+ * @param symbol - Trading pair symbol
36995
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36996
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
36997
+ */
36998
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
36999
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
37000
+ symbol,
37001
+ context,
37002
+ });
37003
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37004
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37005
+ {
37006
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37007
+ riskName &&
37008
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37009
+ riskList &&
37010
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37011
+ actions &&
37012
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37013
+ }
37014
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
37015
+ };
37016
+ /**
37017
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
37018
+ *
37019
+ * Measures how long the position has been pulling back from its peak profit level.
37020
+ * Zero when called at the exact moment the peak was set.
37021
+ * Grows continuously as price moves away from the peak without setting a new record.
37022
+ *
37023
+ * Returns null if no pending signal exists.
37024
+ *
37025
+ * @param symbol - Trading pair symbol
37026
+ * @param context - Execution context with strategyName, exchangeName, and frameName
37027
+ * @returns Drawdown duration in minutes, or null if no active position
37028
+ */
37029
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
37030
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
37031
+ symbol,
37032
+ context,
37033
+ });
37034
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37035
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37036
+ {
37037
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37038
+ riskName &&
37039
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37040
+ riskList &&
37041
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37042
+ actions &&
37043
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37044
+ }
37045
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
37046
+ };
35604
37047
  /**
35605
37048
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
35606
37049
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -36162,7 +37605,7 @@ class BacktestUtils {
36162
37605
  if (!signal) {
36163
37606
  return false;
36164
37607
  }
36165
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37608
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36166
37609
  if (effectivePriceOpen === null) {
36167
37610
  return false;
36168
37611
  }
@@ -36247,7 +37690,7 @@ class BacktestUtils {
36247
37690
  if (!signal) {
36248
37691
  return false;
36249
37692
  }
36250
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37693
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36251
37694
  if (effectivePriceOpen === null) {
36252
37695
  return false;
36253
37696
  }
@@ -36300,7 +37743,7 @@ class BacktestUtils {
36300
37743
  if (!signal) {
36301
37744
  return false;
36302
37745
  }
36303
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37746
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36304
37747
  if (effectivePriceOpen === null) {
36305
37748
  return false;
36306
37749
  }
@@ -36354,7 +37797,7 @@ class BacktestUtils {
36354
37797
  if (!signal) {
36355
37798
  return false;
36356
37799
  }
36357
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37800
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36358
37801
  if (effectivePriceOpen === null) {
36359
37802
  return false;
36360
37803
  }
@@ -36416,7 +37859,7 @@ class BacktestUtils {
36416
37859
  if (!signal) {
36417
37860
  return false;
36418
37861
  }
36419
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37862
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36420
37863
  if (effectivePriceOpen === null) {
36421
37864
  return false;
36422
37865
  }
@@ -36704,7 +38147,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
36704
38147
  const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
36705
38148
  const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
36706
38149
  const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
36707
- const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
38150
+ const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
36708
38151
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
36709
38152
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
36710
38153
  const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
@@ -36712,6 +38155,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
36712
38155
  const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
36713
38156
  const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
36714
38157
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
38158
+ const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
38159
+ const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
38160
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
38161
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
38162
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
38163
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
38164
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
38165
+ const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
36715
38166
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
36716
38167
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
36717
38168
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -37315,7 +38766,7 @@ class LiveUtils {
37315
38766
  * @param context - Execution context with strategyName and exchangeName
37316
38767
  * @returns Effective entry price, or null if no active position
37317
38768
  */
37318
- this.getPositionAveragePrice = async (symbol, context) => {
38769
+ this.getPositionEffectivePrice = async (symbol, context) => {
37319
38770
  bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
37320
38771
  symbol,
37321
38772
  context,
@@ -37331,7 +38782,7 @@ class LiveUtils {
37331
38782
  actions &&
37332
38783
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
37333
38784
  }
37334
- return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
38785
+ return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
37335
38786
  strategyName: context.strategyName,
37336
38787
  exchangeName: context.exchangeName,
37337
38788
  frameName: "",
@@ -37581,6 +39032,262 @@ class LiveUtils {
37581
39032
  frameName: "",
37582
39033
  });
37583
39034
  };
39035
+ /**
39036
+ * Returns the original estimated duration for the current pending signal.
39037
+ *
39038
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
39039
+ * number of minutes the position is expected to be active before `time_expired`.
39040
+ *
39041
+ * Returns null if no pending signal exists.
39042
+ *
39043
+ * @param symbol - Trading pair symbol
39044
+ * @param context - Execution context with strategyName and exchangeName
39045
+ * @returns Estimated duration in minutes, or null if no active position
39046
+ */
39047
+ this.getPositionEstimateMinutes = async (symbol, context) => {
39048
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
39049
+ symbol,
39050
+ context,
39051
+ });
39052
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39053
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39054
+ {
39055
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39056
+ riskName &&
39057
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39058
+ riskList &&
39059
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39060
+ actions &&
39061
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39062
+ }
39063
+ return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
39064
+ strategyName: context.strategyName,
39065
+ exchangeName: context.exchangeName,
39066
+ frameName: "",
39067
+ });
39068
+ };
39069
+ /**
39070
+ * Returns the remaining time before the position expires, clamped to zero.
39071
+ *
39072
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
39073
+ * Returns 0 once the estimate is exceeded (never negative).
39074
+ *
39075
+ * Returns null if no pending signal exists.
39076
+ *
39077
+ * @param symbol - Trading pair symbol
39078
+ * @param context - Execution context with strategyName and exchangeName
39079
+ * @returns Remaining minutes (≥ 0), or null if no active position
39080
+ */
39081
+ this.getPositionCountdownMinutes = async (symbol, context) => {
39082
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
39083
+ symbol,
39084
+ context,
39085
+ });
39086
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39087
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39088
+ {
39089
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39090
+ riskName &&
39091
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39092
+ riskList &&
39093
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39094
+ actions &&
39095
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39096
+ }
39097
+ return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
39098
+ strategyName: context.strategyName,
39099
+ exchangeName: context.exchangeName,
39100
+ frameName: "",
39101
+ });
39102
+ };
39103
+ /**
39104
+ * Returns the best price reached in the profit direction during this position's life.
39105
+ *
39106
+ * Returns null if no pending signal exists.
39107
+ *
39108
+ * @param symbol - Trading pair symbol
39109
+ * @param context - Execution context with strategyName and exchangeName
39110
+ * @returns price or null if no active position
39111
+ */
39112
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
39113
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
39114
+ symbol,
39115
+ context,
39116
+ });
39117
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39118
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39119
+ {
39120
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39121
+ riskName &&
39122
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39123
+ riskList &&
39124
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39125
+ actions &&
39126
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39127
+ }
39128
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
39129
+ strategyName: context.strategyName,
39130
+ exchangeName: context.exchangeName,
39131
+ frameName: "",
39132
+ });
39133
+ };
39134
+ /**
39135
+ * Returns the timestamp when the best profit price was recorded during this position's life.
39136
+ *
39137
+ * Returns null if no pending signal exists.
39138
+ *
39139
+ * @param symbol - Trading pair symbol
39140
+ * @param context - Execution context with strategyName and exchangeName
39141
+ * @returns timestamp in milliseconds or null if no active position
39142
+ */
39143
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
39144
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
39145
+ symbol,
39146
+ context,
39147
+ });
39148
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39149
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39150
+ {
39151
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39152
+ riskName &&
39153
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39154
+ riskList &&
39155
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39156
+ actions &&
39157
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39158
+ }
39159
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
39160
+ strategyName: context.strategyName,
39161
+ exchangeName: context.exchangeName,
39162
+ frameName: "",
39163
+ });
39164
+ };
39165
+ /**
39166
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
39167
+ *
39168
+ * Returns null if no pending signal exists.
39169
+ *
39170
+ * @param symbol - Trading pair symbol
39171
+ * @param context - Execution context with strategyName and exchangeName
39172
+ * @returns PnL percentage or null if no active position
39173
+ */
39174
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
39175
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
39176
+ symbol,
39177
+ context,
39178
+ });
39179
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39180
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39181
+ {
39182
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39183
+ riskName &&
39184
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39185
+ riskList &&
39186
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39187
+ actions &&
39188
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39189
+ }
39190
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
39191
+ strategyName: context.strategyName,
39192
+ exchangeName: context.exchangeName,
39193
+ frameName: "",
39194
+ });
39195
+ };
39196
+ /**
39197
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
39198
+ *
39199
+ * Returns null if no pending signal exists.
39200
+ *
39201
+ * @param symbol - Trading pair symbol
39202
+ * @param context - Execution context with strategyName and exchangeName
39203
+ * @returns PnL cost or null if no active position
39204
+ */
39205
+ this.getPositionHighestPnlCost = async (symbol, context) => {
39206
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
39207
+ symbol,
39208
+ context,
39209
+ });
39210
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39211
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39212
+ {
39213
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39214
+ riskName &&
39215
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39216
+ riskList &&
39217
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39218
+ actions &&
39219
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39220
+ }
39221
+ return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
39222
+ strategyName: context.strategyName,
39223
+ exchangeName: context.exchangeName,
39224
+ frameName: "",
39225
+ });
39226
+ };
39227
+ /**
39228
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
39229
+ *
39230
+ * @param symbol - Trading pair symbol
39231
+ * @param context - Execution context with strategyName and exchangeName
39232
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
39233
+ */
39234
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
39235
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
39236
+ symbol,
39237
+ context,
39238
+ });
39239
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39240
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39241
+ {
39242
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39243
+ riskName &&
39244
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39245
+ riskList &&
39246
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39247
+ actions &&
39248
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39249
+ }
39250
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
39251
+ strategyName: context.strategyName,
39252
+ exchangeName: context.exchangeName,
39253
+ frameName: "",
39254
+ });
39255
+ };
39256
+ /**
39257
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
39258
+ *
39259
+ * Measures how long the position has been pulling back from its peak profit level.
39260
+ * Zero when called at the exact moment the peak was set.
39261
+ * Grows continuously as price moves away from the peak without setting a new record.
39262
+ *
39263
+ * Returns null if no pending signal exists.
39264
+ *
39265
+ * @param symbol - Trading pair symbol
39266
+ * @param context - Execution context with strategyName and exchangeName
39267
+ * @returns Drawdown duration in minutes, or null if no active position
39268
+ */
39269
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
39270
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
39271
+ symbol,
39272
+ context,
39273
+ });
39274
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39275
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39276
+ {
39277
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39278
+ riskName &&
39279
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39280
+ riskList &&
39281
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39282
+ actions &&
39283
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39284
+ }
39285
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
39286
+ strategyName: context.strategyName,
39287
+ exchangeName: context.exchangeName,
39288
+ frameName: "",
39289
+ });
39290
+ };
37584
39291
  /**
37585
39292
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
37586
39293
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38235,7 +39942,7 @@ class LiveUtils {
38235
39942
  if (!signal) {
38236
39943
  return false;
38237
39944
  }
38238
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
39945
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38239
39946
  strategyName: context.strategyName,
38240
39947
  exchangeName: context.exchangeName,
38241
39948
  frameName: "",
@@ -38335,7 +40042,7 @@ class LiveUtils {
38335
40042
  if (!signal) {
38336
40043
  return false;
38337
40044
  }
38338
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40045
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38339
40046
  strategyName: context.strategyName,
38340
40047
  exchangeName: context.exchangeName,
38341
40048
  frameName: "",
@@ -38404,7 +40111,7 @@ class LiveUtils {
38404
40111
  if (!signal) {
38405
40112
  return false;
38406
40113
  }
38407
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40114
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38408
40115
  strategyName: context.strategyName,
38409
40116
  exchangeName: context.exchangeName,
38410
40117
  frameName: "",
@@ -38474,7 +40181,7 @@ class LiveUtils {
38474
40181
  if (!signal) {
38475
40182
  return false;
38476
40183
  }
38477
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40184
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38478
40185
  strategyName: context.strategyName,
38479
40186
  exchangeName: context.exchangeName,
38480
40187
  frameName: "",
@@ -38552,7 +40259,7 @@ class LiveUtils {
38552
40259
  if (!signal) {
38553
40260
  return false;
38554
40261
  }
38555
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40262
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38556
40263
  strategyName: context.strategyName,
38557
40264
  exchangeName: context.exchangeName,
38558
40265
  frameName: "",
@@ -41976,6 +43683,98 @@ class PartialUtils {
41976
43683
  */
41977
43684
  const Partial = new PartialUtils();
41978
43685
 
43686
+ const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
43687
+ const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
43688
+ const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
43689
+ /**
43690
+ * Utility class for accessing highest profit reports and statistics.
43691
+ *
43692
+ * Provides static-like methods (via singleton instance) to retrieve data
43693
+ * accumulated by HighestProfitMarkdownService from highestProfitSubject events.
43694
+ *
43695
+ * @example
43696
+ * ```typescript
43697
+ * import { HighestProfit } from "backtest-kit";
43698
+ *
43699
+ * const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
43700
+ * const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
43701
+ * await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
43702
+ * ```
43703
+ */
43704
+ class HighestProfitUtils {
43705
+ constructor() {
43706
+ /**
43707
+ * Retrieves statistical data from accumulated highest profit events.
43708
+ *
43709
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43710
+ * @param context - Execution context
43711
+ * @param backtest - Whether to query backtest data
43712
+ * @returns Promise resolving to HighestProfitStatisticsModel
43713
+ */
43714
+ this.getData = async (symbol, context, backtest = false) => {
43715
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
43716
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43717
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43718
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43719
+ {
43720
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43721
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43722
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43723
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43724
+ }
43725
+ return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
43726
+ };
43727
+ /**
43728
+ * Generates a markdown report with all highest profit events for a symbol-strategy pair.
43729
+ *
43730
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43731
+ * @param context - Execution context
43732
+ * @param backtest - Whether to query backtest data
43733
+ * @param columns - Optional column configuration
43734
+ * @returns Promise resolving to markdown formatted report string
43735
+ */
43736
+ this.getReport = async (symbol, context, backtest = false, columns) => {
43737
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
43738
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43739
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43740
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43741
+ {
43742
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43743
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43744
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43745
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43746
+ }
43747
+ return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
43748
+ };
43749
+ /**
43750
+ * Generates and saves a markdown report to file.
43751
+ *
43752
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43753
+ * @param context - Execution context
43754
+ * @param backtest - Whether to query backtest data
43755
+ * @param path - Output directory path (default: "./dump/highest_profit")
43756
+ * @param columns - Optional column configuration
43757
+ */
43758
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
43759
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
43760
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43761
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43762
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43763
+ {
43764
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43765
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43766
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43767
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43768
+ }
43769
+ await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
43770
+ };
43771
+ }
43772
+ }
43773
+ /**
43774
+ * Global singleton instance of HighestProfitUtils.
43775
+ */
43776
+ const HighestProfit = new HighestProfitUtils();
43777
+
41979
43778
  /**
41980
43779
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
41981
43780
  *
@@ -46453,6 +48252,7 @@ exports.Constant = Constant;
46453
48252
  exports.Exchange = Exchange;
46454
48253
  exports.ExecutionContextService = ExecutionContextService;
46455
48254
  exports.Heat = Heat;
48255
+ exports.HighestProfit = HighestProfit;
46456
48256
  exports.Live = Live;
46457
48257
  exports.Log = Log;
46458
48258
  exports.Markdown = Markdown;
@@ -46532,8 +48332,17 @@ exports.getMode = getMode;
46532
48332
  exports.getNextCandles = getNextCandles;
46533
48333
  exports.getOrderBook = getOrderBook;
46534
48334
  exports.getPendingSignal = getPendingSignal;
46535
- exports.getPositionAveragePrice = getPositionAveragePrice;
48335
+ exports.getPositionCountdownMinutes = getPositionCountdownMinutes;
48336
+ exports.getPositionDrawdownMinutes = getPositionDrawdownMinutes;
48337
+ exports.getPositionEffectivePrice = getPositionEffectivePrice;
48338
+ exports.getPositionEntries = getPositionEntries;
46536
48339
  exports.getPositionEntryOverlap = getPositionEntryOverlap;
48340
+ exports.getPositionEstimateMinutes = getPositionEstimateMinutes;
48341
+ exports.getPositionHighestPnlCost = getPositionHighestPnlCost;
48342
+ exports.getPositionHighestPnlPercentage = getPositionHighestPnlPercentage;
48343
+ exports.getPositionHighestProfitBreakeven = getPositionHighestProfitBreakeven;
48344
+ exports.getPositionHighestProfitPrice = getPositionHighestProfitPrice;
48345
+ exports.getPositionHighestProfitTimestamp = getPositionHighestProfitTimestamp;
46537
48346
  exports.getPositionInvestedCost = getPositionInvestedCost;
46538
48347
  exports.getPositionInvestedCount = getPositionInvestedCount;
46539
48348
  exports.getPositionLevels = getPositionLevels;
@@ -46574,6 +48383,8 @@ exports.listenDoneWalker = listenDoneWalker;
46574
48383
  exports.listenDoneWalkerOnce = listenDoneWalkerOnce;
46575
48384
  exports.listenError = listenError;
46576
48385
  exports.listenExit = listenExit;
48386
+ exports.listenHighestProfit = listenHighestProfit;
48387
+ exports.listenHighestProfitOnce = listenHighestProfitOnce;
46577
48388
  exports.listenPartialLossAvailable = listenPartialLossAvailable;
46578
48389
  exports.listenPartialLossAvailableOnce = listenPartialLossAvailableOnce;
46579
48390
  exports.listenPartialProfitAvailable = listenPartialProfitAvailable;