backtest-kit 5.5.3 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/build/index.cjs +1999 -173
  2. package/build/index.mjs +1987 -173
  3. package/package.json +1 -1
  4. package/types.d.ts +1141 -179
package/build/index.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,93 @@ 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: "pnl",
18507
+ label: "PNL (net)",
18508
+ format: (data) => {
18509
+ const pnlPercentage = data.pnl.pnlPercentage;
18510
+ return `${pnlPercentage > 0 ? "+" : ""}${pnlPercentage.toFixed(2)}%`;
18511
+ },
18512
+ isVisible: () => true,
18513
+ },
18514
+ {
18515
+ key: "pnlCost",
18516
+ label: "PNL (USD)",
18517
+ format: (data) => `${data.pnl.pnlCost > 0 ? "+" : ""}${data.pnl.pnlCost.toFixed(2)} USD`,
18518
+ isVisible: () => true,
18519
+ },
18520
+ {
18521
+ key: "currentPrice",
18522
+ label: "Record Price",
18523
+ format: (data) => `${data.currentPrice.toFixed(8)} USD`,
18524
+ isVisible: () => true,
18525
+ },
18526
+ {
18527
+ key: "priceOpen",
18528
+ label: "Entry Price",
18529
+ format: (data) => `${data.priceOpen.toFixed(8)} USD`,
18530
+ isVisible: () => true,
18531
+ },
18532
+ {
18533
+ key: "priceTakeProfit",
18534
+ label: "Take Profit",
18535
+ format: (data) => `${data.priceTakeProfit.toFixed(8)} USD`,
18536
+ isVisible: () => true,
18537
+ },
18538
+ {
18539
+ key: "priceStopLoss",
18540
+ label: "Stop Loss",
18541
+ format: (data) => `${data.priceStopLoss.toFixed(8)} USD`,
18542
+ isVisible: () => true,
18543
+ },
18544
+ {
18545
+ key: "timestamp",
18546
+ label: "Timestamp",
18547
+ format: (data) => new Date(data.timestamp).toISOString(),
18548
+ isVisible: () => true,
18549
+ },
18550
+ {
18551
+ key: "mode",
18552
+ label: "Mode",
18553
+ format: (data) => (data.backtest ? "Backtest" : "Live"),
18554
+ isVisible: () => true,
18555
+ },
18556
+ ];
18557
+
17907
18558
  /**
17908
18559
  * Column configuration for walker strategy comparison table in markdown reports.
17909
18560
  *
@@ -18121,6 +18772,8 @@ const COLUMN_CONFIG = {
18121
18772
  strategy_columns,
18122
18773
  /** Columns for signal sync lifecycle events (signal-open, signal-close) */
18123
18774
  sync_columns,
18775
+ /** Columns for highest profit milestone tracking events */
18776
+ highest_profit_columns,
18124
18777
  /** Walker: PnL summary columns */
18125
18778
  walker_pnl_columns,
18126
18779
  /** Walker: strategy-level summary columns */
@@ -18161,6 +18814,7 @@ const WILDCARD_TARGET$1 = {
18161
18814
  schedule: true,
18162
18815
  walker: true,
18163
18816
  sync: true,
18817
+ highest_profit: true,
18164
18818
  };
18165
18819
  /**
18166
18820
  * JSONL-based markdown adapter with append-only writes.
@@ -18388,7 +19042,7 @@ class MarkdownUtils {
18388
19042
  *
18389
19043
  * @returns Cleanup function that unsubscribes from all enabled services
18390
19044
  */
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) => {
19045
+ 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
19046
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_ENABLE, {
18393
19047
  backtest: bt$1,
18394
19048
  breakeven,
@@ -18401,6 +19055,7 @@ class MarkdownUtils {
18401
19055
  schedule,
18402
19056
  walker,
18403
19057
  sync,
19058
+ highest_profit,
18404
19059
  });
18405
19060
  const unList = [];
18406
19061
  if (bt$1) {
@@ -18436,6 +19091,9 @@ class MarkdownUtils {
18436
19091
  if (sync) {
18437
19092
  unList.push(bt.syncMarkdownService.subscribe());
18438
19093
  }
19094
+ if (highest_profit) {
19095
+ unList.push(bt.highestProfitMarkdownService.subscribe());
19096
+ }
18439
19097
  return functoolsKit.compose(...unList.map((un) => () => void un()));
18440
19098
  };
18441
19099
  /**
@@ -18475,7 +19133,7 @@ class MarkdownUtils {
18475
19133
  * Markdown.disable();
18476
19134
  * ```
18477
19135
  */
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) => {
19136
+ 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
19137
  bt.loggerService.debug(MARKDOWN_METHOD_NAME_DISABLE, {
18480
19138
  backtest: bt$1,
18481
19139
  breakeven,
@@ -18488,6 +19146,7 @@ class MarkdownUtils {
18488
19146
  schedule,
18489
19147
  walker,
18490
19148
  sync,
19149
+ highest_profit,
18491
19150
  });
18492
19151
  if (bt$1) {
18493
19152
  bt.backtestMarkdownService.unsubscribe();
@@ -18522,6 +19181,9 @@ class MarkdownUtils {
18522
19181
  if (sync) {
18523
19182
  bt.syncMarkdownService.unsubscribe();
18524
19183
  }
19184
+ if (highest_profit) {
19185
+ bt.highestProfitMarkdownService.unsubscribe();
19186
+ }
18525
19187
  };
18526
19188
  }
18527
19189
  }
@@ -18628,7 +19290,7 @@ const Markdown = new MarkdownAdapter();
18628
19290
  * @param backtest - Whether running in backtest mode
18629
19291
  * @returns Unique string key for memoization
18630
19292
  */
18631
- const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19293
+ const CREATE_KEY_FN$i = (symbol, strategyName, exchangeName, frameName, backtest) => {
18632
19294
  const parts = [symbol, strategyName, exchangeName];
18633
19295
  if (frameName)
18634
19296
  parts.push(frameName);
@@ -18645,7 +19307,7 @@ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest
18645
19307
  * @param timestamp - Unix timestamp in milliseconds
18646
19308
  * @returns Filename string
18647
19309
  */
18648
- const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19310
+ const CREATE_FILE_NAME_FN$b = (symbol, strategyName, exchangeName, frameName, timestamp) => {
18649
19311
  const parts = [symbol, strategyName, exchangeName];
18650
19312
  if (frameName) {
18651
19313
  parts.push(frameName);
@@ -18674,12 +19336,12 @@ function isUnsafe$3(value) {
18674
19336
  return false;
18675
19337
  }
18676
19338
  /** Maximum number of signals to store in backtest reports */
18677
- const MAX_EVENTS$9 = 250;
19339
+ const MAX_EVENTS$a = 250;
18678
19340
  /**
18679
19341
  * Storage class for accumulating closed signals per strategy.
18680
19342
  * Maintains a list of all closed signals and provides methods to generate reports.
18681
19343
  */
18682
- let ReportStorage$8 = class ReportStorage {
19344
+ let ReportStorage$9 = class ReportStorage {
18683
19345
  constructor(symbol, strategyName, exchangeName, frameName) {
18684
19346
  this.symbol = symbol;
18685
19347
  this.strategyName = strategyName;
@@ -18696,7 +19358,7 @@ let ReportStorage$8 = class ReportStorage {
18696
19358
  addSignal(data) {
18697
19359
  this._signalList.unshift(data);
18698
19360
  // Trim queue if exceeded MAX_EVENTS
18699
- if (this._signalList.length > MAX_EVENTS$9) {
19361
+ if (this._signalList.length > MAX_EVENTS$a) {
18700
19362
  this._signalList.pop();
18701
19363
  }
18702
19364
  }
@@ -18820,7 +19482,7 @@ let ReportStorage$8 = class ReportStorage {
18820
19482
  async dump(strategyName, path = "./dump/backtest", columns = COLUMN_CONFIG.backtest_columns) {
18821
19483
  const markdown = await this.getReport(strategyName, columns);
18822
19484
  const timestamp = getContextTimestamp();
18823
- const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19485
+ const filename = CREATE_FILE_NAME_FN$b(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
18824
19486
  await Markdown.writeData("backtest", markdown, {
18825
19487
  path,
18826
19488
  file: filename,
@@ -18867,7 +19529,7 @@ class BacktestMarkdownService {
18867
19529
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
18868
19530
  * Each combination gets its own isolated storage instance.
18869
19531
  */
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));
19532
+ 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
19533
  /**
18872
19534
  * Processes tick events and accumulates closed signals.
18873
19535
  * Should be called from IStrategyCallbacks.onTick.
@@ -19024,7 +19686,7 @@ class BacktestMarkdownService {
19024
19686
  payload,
19025
19687
  });
19026
19688
  if (payload) {
19027
- const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19689
+ const key = CREATE_KEY_FN$i(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19028
19690
  this.getStorage.clear(key);
19029
19691
  }
19030
19692
  else {
@@ -19086,7 +19748,7 @@ class BacktestMarkdownService {
19086
19748
  * @param backtest - Whether running in backtest mode
19087
19749
  * @returns Unique string key for memoization
19088
19750
  */
19089
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19751
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
19090
19752
  const parts = [symbol, strategyName, exchangeName];
19091
19753
  if (frameName)
19092
19754
  parts.push(frameName);
@@ -19103,7 +19765,7 @@ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest
19103
19765
  * @param timestamp - Unix timestamp in milliseconds
19104
19766
  * @returns Filename string
19105
19767
  */
19106
- const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19768
+ const CREATE_FILE_NAME_FN$a = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19107
19769
  const parts = [symbol, strategyName, exchangeName];
19108
19770
  if (frameName) {
19109
19771
  parts.push(frameName);
@@ -19132,12 +19794,12 @@ function isUnsafe$2(value) {
19132
19794
  return false;
19133
19795
  }
19134
19796
  /** Maximum number of events to store in live trading reports */
19135
- const MAX_EVENTS$8 = 250;
19797
+ const MAX_EVENTS$9 = 250;
19136
19798
  /**
19137
19799
  * Storage class for accumulating all tick events per strategy.
19138
19800
  * Maintains a chronological list of all events (idle, opened, active, closed).
19139
19801
  */
19140
- let ReportStorage$7 = class ReportStorage {
19802
+ let ReportStorage$8 = class ReportStorage {
19141
19803
  constructor(symbol, strategyName, exchangeName, frameName) {
19142
19804
  this.symbol = symbol;
19143
19805
  this.strategyName = strategyName;
@@ -19169,7 +19831,7 @@ let ReportStorage$7 = class ReportStorage {
19169
19831
  }
19170
19832
  {
19171
19833
  this._eventList.unshift(newEvent);
19172
- if (this._eventList.length > MAX_EVENTS$8) {
19834
+ if (this._eventList.length > MAX_EVENTS$9) {
19173
19835
  this._eventList.pop();
19174
19836
  }
19175
19837
  }
@@ -19199,7 +19861,7 @@ let ReportStorage$7 = class ReportStorage {
19199
19861
  scheduledAt: data.signal.scheduledAt,
19200
19862
  });
19201
19863
  // Trim queue if exceeded MAX_EVENTS
19202
- if (this._eventList.length > MAX_EVENTS$8) {
19864
+ if (this._eventList.length > MAX_EVENTS$9) {
19203
19865
  this._eventList.pop();
19204
19866
  }
19205
19867
  }
@@ -19243,7 +19905,7 @@ let ReportStorage$7 = class ReportStorage {
19243
19905
  // If no previous active event found, add new event
19244
19906
  this._eventList.unshift(newEvent);
19245
19907
  // Trim queue if exceeded MAX_EVENTS
19246
- if (this._eventList.length > MAX_EVENTS$8) {
19908
+ if (this._eventList.length > MAX_EVENTS$9) {
19247
19909
  this._eventList.pop();
19248
19910
  }
19249
19911
  }
@@ -19280,7 +19942,7 @@ let ReportStorage$7 = class ReportStorage {
19280
19942
  };
19281
19943
  this._eventList.unshift(newEvent);
19282
19944
  // Trim queue if exceeded MAX_EVENTS
19283
- if (this._eventList.length > MAX_EVENTS$8) {
19945
+ if (this._eventList.length > MAX_EVENTS$9) {
19284
19946
  this._eventList.pop();
19285
19947
  }
19286
19948
  }
@@ -19308,7 +19970,7 @@ let ReportStorage$7 = class ReportStorage {
19308
19970
  scheduledAt: data.signal.scheduledAt,
19309
19971
  });
19310
19972
  // Trim queue if exceeded MAX_EVENTS
19311
- if (this._eventList.length > MAX_EVENTS$8) {
19973
+ if (this._eventList.length > MAX_EVENTS$9) {
19312
19974
  this._eventList.pop();
19313
19975
  }
19314
19976
  }
@@ -19351,7 +20013,7 @@ let ReportStorage$7 = class ReportStorage {
19351
20013
  // If no previous waiting event found, add new event
19352
20014
  this._eventList.unshift(newEvent);
19353
20015
  // Trim queue if exceeded MAX_EVENTS
19354
- if (this._eventList.length > MAX_EVENTS$8) {
20016
+ if (this._eventList.length > MAX_EVENTS$9) {
19355
20017
  this._eventList.pop();
19356
20018
  }
19357
20019
  }
@@ -19380,7 +20042,7 @@ let ReportStorage$7 = class ReportStorage {
19380
20042
  scheduledAt: data.signal.scheduledAt,
19381
20043
  });
19382
20044
  // Trim queue if exceeded MAX_EVENTS
19383
- if (this._eventList.length > MAX_EVENTS$8) {
20045
+ if (this._eventList.length > MAX_EVENTS$9) {
19384
20046
  this._eventList.pop();
19385
20047
  }
19386
20048
  }
@@ -19519,7 +20181,7 @@ let ReportStorage$7 = class ReportStorage {
19519
20181
  async dump(strategyName, path = "./dump/live", columns = COLUMN_CONFIG.live_columns) {
19520
20182
  const markdown = await this.getReport(strategyName, columns);
19521
20183
  const timestamp = getContextTimestamp();
19522
- const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20184
+ const filename = CREATE_FILE_NAME_FN$a(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
19523
20185
  await Markdown.writeData("live", markdown, {
19524
20186
  path,
19525
20187
  signalId: "",
@@ -19569,7 +20231,7 @@ class LiveMarkdownService {
19569
20231
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
19570
20232
  * Each combination gets its own isolated storage instance.
19571
20233
  */
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));
20234
+ 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
20235
  /**
19574
20236
  * Subscribes to live signal emitter to receive tick events.
19575
20237
  * Protected against multiple subscriptions.
@@ -19787,7 +20449,7 @@ class LiveMarkdownService {
19787
20449
  payload,
19788
20450
  });
19789
20451
  if (payload) {
19790
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20452
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
19791
20453
  this.getStorage.clear(key);
19792
20454
  }
19793
20455
  else {
@@ -19807,7 +20469,7 @@ class LiveMarkdownService {
19807
20469
  * @param backtest - Whether running in backtest mode
19808
20470
  * @returns Unique string key for memoization
19809
20471
  */
19810
- const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20472
+ const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
19811
20473
  const parts = [symbol, strategyName, exchangeName];
19812
20474
  if (frameName)
19813
20475
  parts.push(frameName);
@@ -19824,7 +20486,7 @@ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest
19824
20486
  * @param timestamp - Unix timestamp in milliseconds
19825
20487
  * @returns Filename string
19826
20488
  */
19827
- const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20489
+ const CREATE_FILE_NAME_FN$9 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
19828
20490
  const parts = [symbol, strategyName, exchangeName];
19829
20491
  if (frameName) {
19830
20492
  parts.push(frameName);
@@ -19835,12 +20497,12 @@ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, ti
19835
20497
  return `${parts.join("_")}-${timestamp}.md`;
19836
20498
  };
19837
20499
  /** Maximum number of events to store in schedule reports */
19838
- const MAX_EVENTS$7 = 250;
20500
+ const MAX_EVENTS$8 = 250;
19839
20501
  /**
19840
20502
  * Storage class for accumulating scheduled signal events per strategy.
19841
20503
  * Maintains a chronological list of scheduled and cancelled events.
19842
20504
  */
19843
- let ReportStorage$6 = class ReportStorage {
20505
+ let ReportStorage$7 = class ReportStorage {
19844
20506
  constructor(symbol, strategyName, exchangeName, frameName) {
19845
20507
  this.symbol = symbol;
19846
20508
  this.strategyName = strategyName;
@@ -19876,7 +20538,7 @@ let ReportStorage$6 = class ReportStorage {
19876
20538
  scheduledAt: data.signal.scheduledAt,
19877
20539
  });
19878
20540
  // Trim queue if exceeded MAX_EVENTS
19879
- if (this._eventList.length > MAX_EVENTS$7) {
20541
+ if (this._eventList.length > MAX_EVENTS$8) {
19880
20542
  this._eventList.pop();
19881
20543
  }
19882
20544
  }
@@ -19912,7 +20574,7 @@ let ReportStorage$6 = class ReportStorage {
19912
20574
  };
19913
20575
  this._eventList.unshift(newEvent);
19914
20576
  // Trim queue if exceeded MAX_EVENTS
19915
- if (this._eventList.length > MAX_EVENTS$7) {
20577
+ if (this._eventList.length > MAX_EVENTS$8) {
19916
20578
  this._eventList.pop();
19917
20579
  }
19918
20580
  }
@@ -19950,7 +20612,7 @@ let ReportStorage$6 = class ReportStorage {
19950
20612
  };
19951
20613
  this._eventList.unshift(newEvent);
19952
20614
  // Trim queue if exceeded MAX_EVENTS
19953
- if (this._eventList.length > MAX_EVENTS$7) {
20615
+ if (this._eventList.length > MAX_EVENTS$8) {
19954
20616
  this._eventList.pop();
19955
20617
  }
19956
20618
  }
@@ -20057,7 +20719,7 @@ let ReportStorage$6 = class ReportStorage {
20057
20719
  async dump(strategyName, path = "./dump/schedule", columns = COLUMN_CONFIG.schedule_columns) {
20058
20720
  const markdown = await this.getReport(strategyName, columns);
20059
20721
  const timestamp = getContextTimestamp();
20060
- const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20722
+ const filename = CREATE_FILE_NAME_FN$9(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20061
20723
  await Markdown.writeData("schedule", markdown, {
20062
20724
  path,
20063
20725
  file: filename,
@@ -20098,7 +20760,7 @@ class ScheduleMarkdownService {
20098
20760
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20099
20761
  * Each combination gets its own isolated storage instance.
20100
20762
  */
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));
20763
+ 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
20764
  /**
20103
20765
  * Subscribes to signal emitter to receive scheduled signal events.
20104
20766
  * Protected against multiple subscriptions.
@@ -20301,7 +20963,7 @@ class ScheduleMarkdownService {
20301
20963
  payload,
20302
20964
  });
20303
20965
  if (payload) {
20304
- const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20966
+ const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20305
20967
  this.getStorage.clear(key);
20306
20968
  }
20307
20969
  else {
@@ -20321,7 +20983,7 @@ class ScheduleMarkdownService {
20321
20983
  * @param backtest - Whether running in backtest mode
20322
20984
  * @returns Unique string key for memoization
20323
20985
  */
20324
- const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
20986
+ const CREATE_KEY_FN$f = (symbol, strategyName, exchangeName, frameName, backtest) => {
20325
20987
  const parts = [symbol, strategyName, exchangeName];
20326
20988
  if (frameName)
20327
20989
  parts.push(frameName);
@@ -20338,7 +21000,7 @@ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest
20338
21000
  * @param timestamp - Unix timestamp in milliseconds
20339
21001
  * @returns Filename string
20340
21002
  */
20341
- const CREATE_FILE_NAME_FN$7 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
21003
+ const CREATE_FILE_NAME_FN$8 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
20342
21004
  const parts = [symbol, strategyName, exchangeName];
20343
21005
  if (frameName) {
20344
21006
  parts.push(frameName);
@@ -20358,7 +21020,7 @@ function percentile(sortedArray, p) {
20358
21020
  return sortedArray[Math.max(0, index)];
20359
21021
  }
20360
21022
  /** Maximum number of performance events to store per strategy */
20361
- const MAX_EVENTS$6 = 10000;
21023
+ const MAX_EVENTS$7 = 10000;
20362
21024
  /**
20363
21025
  * Storage class for accumulating performance metrics per strategy.
20364
21026
  * Maintains a list of all performance events and provides aggregated statistics.
@@ -20380,7 +21042,7 @@ class PerformanceStorage {
20380
21042
  addEvent(event) {
20381
21043
  this._events.unshift(event);
20382
21044
  // Trim queue if exceeded MAX_EVENTS (keep most recent)
20383
- if (this._events.length > MAX_EVENTS$6) {
21045
+ if (this._events.length > MAX_EVENTS$7) {
20384
21046
  this._events.pop();
20385
21047
  }
20386
21048
  }
@@ -20497,6 +21159,10 @@ class PerformanceStorage {
20497
21159
  return [
20498
21160
  `# Performance Report: ${strategyName}`,
20499
21161
  "",
21162
+ summaryTable,
21163
+ "",
21164
+ "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type.",
21165
+ "",
20500
21166
  `**Total events:** ${stats.totalEvents}`,
20501
21167
  `**Total execution time:** ${stats.totalDuration.toFixed(2)}ms`,
20502
21168
  `**Number of metric types:** ${Object.keys(stats.metricStats).length}`,
@@ -20505,11 +21171,6 @@ class PerformanceStorage {
20505
21171
  "",
20506
21172
  percentages.join("\n"),
20507
21173
  "",
20508
- "## Detailed Metrics",
20509
- "",
20510
- summaryTable,
20511
- "",
20512
- "**Note:** All durations are in milliseconds. P95/P99 represent 95th and 99th percentile response times. Wait times show the interval between consecutive events of the same type."
20513
21174
  ].join("\n");
20514
21175
  }
20515
21176
  /**
@@ -20522,7 +21183,7 @@ class PerformanceStorage {
20522
21183
  async dump(strategyName, path = "./dump/performance", columns = COLUMN_CONFIG.performance_columns) {
20523
21184
  const markdown = await this.getReport(strategyName, columns);
20524
21185
  const timestamp = getContextTimestamp();
20525
- const filename = CREATE_FILE_NAME_FN$7(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
21186
+ const filename = CREATE_FILE_NAME_FN$8(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
20526
21187
  await Markdown.writeData("performance", markdown, {
20527
21188
  path,
20528
21189
  file: filename,
@@ -20569,7 +21230,7 @@ class PerformanceMarkdownService {
20569
21230
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
20570
21231
  * Each combination gets its own isolated storage instance.
20571
21232
  */
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));
21233
+ 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
21234
  /**
20574
21235
  * Subscribes to performance emitter to receive performance events.
20575
21236
  * Protected against multiple subscriptions.
@@ -20736,7 +21397,7 @@ class PerformanceMarkdownService {
20736
21397
  payload,
20737
21398
  });
20738
21399
  if (payload) {
20739
- const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21400
+ const key = CREATE_KEY_FN$f(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
20740
21401
  this.getStorage.clear(key);
20741
21402
  }
20742
21403
  else {
@@ -20750,7 +21411,7 @@ class PerformanceMarkdownService {
20750
21411
  * Creates a filename for markdown report based on walker name.
20751
21412
  * Filename format: "walkerName-timestamp.md"
20752
21413
  */
20753
- const CREATE_FILE_NAME_FN$6 = (walkerName, timestamp) => {
21414
+ const CREATE_FILE_NAME_FN$7 = (walkerName, timestamp) => {
20754
21415
  return `${walkerName}-${timestamp}.md`;
20755
21416
  };
20756
21417
  /**
@@ -20785,7 +21446,7 @@ function formatMetric(value) {
20785
21446
  * Storage class for accumulating walker results.
20786
21447
  * Maintains a list of all strategy results and provides methods to generate reports.
20787
21448
  */
20788
- let ReportStorage$5 = class ReportStorage {
21449
+ let ReportStorage$6 = class ReportStorage {
20789
21450
  constructor(walkerName) {
20790
21451
  this.walkerName = walkerName;
20791
21452
  /** Walker metadata (set from first addResult call) */
@@ -20973,7 +21634,7 @@ let ReportStorage$5 = class ReportStorage {
20973
21634
  async dump(symbol, metric, context, path = "./dump/walker", strategyColumns = COLUMN_CONFIG.walker_strategy_columns, pnlColumns = COLUMN_CONFIG.walker_pnl_columns) {
20974
21635
  const markdown = await this.getReport(symbol, metric, context, strategyColumns, pnlColumns);
20975
21636
  const timestamp = getContextTimestamp();
20976
- const filename = CREATE_FILE_NAME_FN$6(this.walkerName, timestamp);
21637
+ const filename = CREATE_FILE_NAME_FN$7(this.walkerName, timestamp);
20977
21638
  await Markdown.writeData("walker", markdown, {
20978
21639
  path,
20979
21640
  file: filename,
@@ -21009,7 +21670,7 @@ class WalkerMarkdownService {
21009
21670
  * Memoized function to get or create ReportStorage for a walker.
21010
21671
  * Each walker gets its own isolated storage instance.
21011
21672
  */
21012
- this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$5(walkerName));
21673
+ this.getStorage = functoolsKit.memoize(([walkerName]) => `${walkerName}`, (walkerName) => new ReportStorage$6(walkerName));
21013
21674
  /**
21014
21675
  * Subscribes to walker emitter to receive walker progress events.
21015
21676
  * Protected against multiple subscriptions.
@@ -21206,7 +21867,7 @@ class WalkerMarkdownService {
21206
21867
  * @param backtest - Whether running in backtest mode
21207
21868
  * @returns Unique string key for memoization
21208
21869
  */
21209
- const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21870
+ const CREATE_KEY_FN$e = (exchangeName, frameName, backtest) => {
21210
21871
  const parts = [exchangeName];
21211
21872
  if (frameName)
21212
21873
  parts.push(frameName);
@@ -21217,7 +21878,7 @@ const CREATE_KEY_FN$d = (exchangeName, frameName, backtest) => {
21217
21878
  * Creates a filename for markdown report based on memoization key components.
21218
21879
  * Filename format: "strategyName_exchangeName_frameName-timestamp.md"
21219
21880
  */
21220
- const CREATE_FILE_NAME_FN$5 = (strategyName, exchangeName, frameName, timestamp) => {
21881
+ const CREATE_FILE_NAME_FN$6 = (strategyName, exchangeName, frameName, timestamp) => {
21221
21882
  const parts = [strategyName, exchangeName];
21222
21883
  if (frameName) {
21223
21884
  parts.push(frameName);
@@ -21250,7 +21911,7 @@ function isUnsafe(value) {
21250
21911
  return false;
21251
21912
  }
21252
21913
  /** Maximum number of signals to store per symbol in heatmap reports */
21253
- const MAX_EVENTS$5 = 250;
21914
+ const MAX_EVENTS$6 = 250;
21254
21915
  /**
21255
21916
  * Storage class for accumulating closed signals per strategy and generating heatmap.
21256
21917
  * Maintains symbol-level statistics and provides portfolio-wide metrics.
@@ -21276,7 +21937,7 @@ class HeatmapStorage {
21276
21937
  const signals = this.symbolData.get(symbol);
21277
21938
  signals.unshift(data);
21278
21939
  // Trim queue if exceeded MAX_EVENTS per symbol
21279
- if (signals.length > MAX_EVENTS$5) {
21940
+ if (signals.length > MAX_EVENTS$6) {
21280
21941
  signals.pop();
21281
21942
  }
21282
21943
  }
@@ -21527,7 +22188,7 @@ class HeatmapStorage {
21527
22188
  async dump(strategyName, path = "./dump/heatmap", columns = COLUMN_CONFIG.heat_columns) {
21528
22189
  const markdown = await this.getReport(strategyName, columns);
21529
22190
  const timestamp = getContextTimestamp();
21530
- const filename = CREATE_FILE_NAME_FN$5(strategyName, this.exchangeName, this.frameName, timestamp);
22191
+ const filename = CREATE_FILE_NAME_FN$6(strategyName, this.exchangeName, this.frameName, timestamp);
21531
22192
  await Markdown.writeData("heat", markdown, {
21532
22193
  path,
21533
22194
  file: filename,
@@ -21573,7 +22234,7 @@ class HeatMarkdownService {
21573
22234
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
21574
22235
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
21575
22236
  */
21576
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
22237
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
21577
22238
  /**
21578
22239
  * Subscribes to signal emitter to receive tick events.
21579
22240
  * Protected against multiple subscriptions.
@@ -21768,7 +22429,7 @@ class HeatMarkdownService {
21768
22429
  payload,
21769
22430
  });
21770
22431
  if (payload) {
21771
- const key = CREATE_KEY_FN$d(payload.exchangeName, payload.frameName, payload.backtest);
22432
+ const key = CREATE_KEY_FN$e(payload.exchangeName, payload.frameName, payload.backtest);
21772
22433
  this.getStorage.clear(key);
21773
22434
  }
21774
22435
  else {
@@ -22799,7 +23460,7 @@ class ClientPartial {
22799
23460
  * @param backtest - Whether running in backtest mode
22800
23461
  * @returns Unique string key for memoization
22801
23462
  */
22802
- const CREATE_KEY_FN$c = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
23463
+ const CREATE_KEY_FN$d = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
22803
23464
  /**
22804
23465
  * Creates a callback function for emitting profit events to partialProfitSubject.
22805
23466
  *
@@ -22921,7 +23582,7 @@ class PartialConnectionService {
22921
23582
  * Key format: "signalId:backtest" or "signalId:live"
22922
23583
  * Value: ClientPartial instance with logger and event emitters
22923
23584
  */
22924
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$c(signalId, backtest), (signalId, backtest) => {
23585
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$d(signalId, backtest), (signalId, backtest) => {
22925
23586
  return new ClientPartial({
22926
23587
  signalId,
22927
23588
  logger: this.loggerService,
@@ -23011,7 +23672,7 @@ class PartialConnectionService {
23011
23672
  const partial = this.getPartial(data.id, backtest);
23012
23673
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
23013
23674
  await partial.clear(symbol, data, priceClose, backtest);
23014
- const key = CREATE_KEY_FN$c(data.id, backtest);
23675
+ const key = CREATE_KEY_FN$d(data.id, backtest);
23015
23676
  this.getPartial.clear(key);
23016
23677
  };
23017
23678
  }
@@ -23027,7 +23688,7 @@ class PartialConnectionService {
23027
23688
  * @param backtest - Whether running in backtest mode
23028
23689
  * @returns Unique string key for memoization
23029
23690
  */
23030
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
23691
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
23031
23692
  const parts = [symbol, strategyName, exchangeName];
23032
23693
  if (frameName)
23033
23694
  parts.push(frameName);
@@ -23038,7 +23699,7 @@ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest
23038
23699
  * Creates a filename for markdown report based on memoization key components.
23039
23700
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
23040
23701
  */
23041
- const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23702
+ const CREATE_FILE_NAME_FN$5 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
23042
23703
  const parts = [symbol, strategyName, exchangeName];
23043
23704
  if (frameName) {
23044
23705
  parts.push(frameName);
@@ -23049,12 +23710,12 @@ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, ti
23049
23710
  return `${parts.join("_")}-${timestamp}.md`;
23050
23711
  };
23051
23712
  /** Maximum number of events to store in partial reports */
23052
- const MAX_EVENTS$4 = 250;
23713
+ const MAX_EVENTS$5 = 250;
23053
23714
  /**
23054
23715
  * Storage class for accumulating partial profit/loss events per symbol-strategy pair.
23055
23716
  * Maintains a chronological list of profit and loss level events.
23056
23717
  */
23057
- let ReportStorage$4 = class ReportStorage {
23718
+ let ReportStorage$5 = class ReportStorage {
23058
23719
  constructor(symbol, strategyName, exchangeName, frameName) {
23059
23720
  this.symbol = symbol;
23060
23721
  this.strategyName = strategyName;
@@ -23097,7 +23758,7 @@ let ReportStorage$4 = class ReportStorage {
23097
23758
  backtest,
23098
23759
  });
23099
23760
  // Trim queue if exceeded MAX_EVENTS
23100
- if (this._eventList.length > MAX_EVENTS$4) {
23761
+ if (this._eventList.length > MAX_EVENTS$5) {
23101
23762
  this._eventList.pop();
23102
23763
  }
23103
23764
  }
@@ -23135,7 +23796,7 @@ let ReportStorage$4 = class ReportStorage {
23135
23796
  backtest,
23136
23797
  });
23137
23798
  // Trim queue if exceeded MAX_EVENTS
23138
- if (this._eventList.length > MAX_EVENTS$4) {
23799
+ if (this._eventList.length > MAX_EVENTS$5) {
23139
23800
  this._eventList.pop();
23140
23801
  }
23141
23802
  }
@@ -23211,7 +23872,7 @@ let ReportStorage$4 = class ReportStorage {
23211
23872
  async dump(symbol, strategyName, path = "./dump/partial", columns = COLUMN_CONFIG.partial_columns) {
23212
23873
  const markdown = await this.getReport(symbol, strategyName, columns);
23213
23874
  const timestamp = getContextTimestamp();
23214
- const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23875
+ const filename = CREATE_FILE_NAME_FN$5(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
23215
23876
  await Markdown.writeData("partial", markdown, {
23216
23877
  path,
23217
23878
  file: filename,
@@ -23252,7 +23913,7 @@ class PartialMarkdownService {
23252
23913
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
23253
23914
  * Each combination gets its own isolated storage instance.
23254
23915
  */
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));
23916
+ 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
23917
  /**
23257
23918
  * Subscribes to partial profit/loss signal emitters to receive events.
23258
23919
  * Protected against multiple subscriptions.
@@ -23462,7 +24123,7 @@ class PartialMarkdownService {
23462
24123
  payload,
23463
24124
  });
23464
24125
  if (payload) {
23465
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24126
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23466
24127
  this.getStorage.clear(key);
23467
24128
  }
23468
24129
  else {
@@ -23478,7 +24139,7 @@ class PartialMarkdownService {
23478
24139
  * @param context - Context with strategyName, exchangeName, frameName
23479
24140
  * @returns Unique string key for memoization
23480
24141
  */
23481
- const CREATE_KEY_FN$a = (context) => {
24142
+ const CREATE_KEY_FN$b = (context) => {
23482
24143
  const parts = [context.strategyName, context.exchangeName];
23483
24144
  if (context.frameName)
23484
24145
  parts.push(context.frameName);
@@ -23552,7 +24213,7 @@ class PartialGlobalService {
23552
24213
  * @param context - Context with strategyName, exchangeName and frameName
23553
24214
  * @param methodName - Name of the calling method for error tracking
23554
24215
  */
23555
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$a(context), (context, methodName) => {
24216
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$b(context), (context, methodName) => {
23556
24217
  this.loggerService.log("partialGlobalService validate", {
23557
24218
  context,
23558
24219
  methodName,
@@ -24007,7 +24668,7 @@ class ClientBreakeven {
24007
24668
  * @param backtest - Whether running in backtest mode
24008
24669
  * @returns Unique string key for memoization
24009
24670
  */
24010
- const CREATE_KEY_FN$9 = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24671
+ const CREATE_KEY_FN$a = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
24011
24672
  /**
24012
24673
  * Creates a callback function for emitting breakeven events to breakevenSubject.
24013
24674
  *
@@ -24093,7 +24754,7 @@ class BreakevenConnectionService {
24093
24754
  * Key format: "signalId:backtest" or "signalId:live"
24094
24755
  * Value: ClientBreakeven instance with logger and event emitter
24095
24756
  */
24096
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$9(signalId, backtest), (signalId, backtest) => {
24757
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$a(signalId, backtest), (signalId, backtest) => {
24097
24758
  return new ClientBreakeven({
24098
24759
  signalId,
24099
24760
  logger: this.loggerService,
@@ -24154,7 +24815,7 @@ class BreakevenConnectionService {
24154
24815
  const breakeven = this.getBreakeven(data.id, backtest);
24155
24816
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
24156
24817
  await breakeven.clear(symbol, data, priceClose, backtest);
24157
- const key = CREATE_KEY_FN$9(data.id, backtest);
24818
+ const key = CREATE_KEY_FN$a(data.id, backtest);
24158
24819
  this.getBreakeven.clear(key);
24159
24820
  };
24160
24821
  }
@@ -24170,7 +24831,7 @@ class BreakevenConnectionService {
24170
24831
  * @param backtest - Whether running in backtest mode
24171
24832
  * @returns Unique string key for memoization
24172
24833
  */
24173
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24834
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24174
24835
  const parts = [symbol, strategyName, exchangeName];
24175
24836
  if (frameName)
24176
24837
  parts.push(frameName);
@@ -24181,7 +24842,7 @@ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest
24181
24842
  * Creates a filename for markdown report based on memoization key components.
24182
24843
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24183
24844
  */
24184
- const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24845
+ const CREATE_FILE_NAME_FN$4 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24185
24846
  const parts = [symbol, strategyName, exchangeName];
24186
24847
  if (frameName) {
24187
24848
  parts.push(frameName);
@@ -24192,12 +24853,12 @@ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, ti
24192
24853
  return `${parts.join("_")}-${timestamp}.md`;
24193
24854
  };
24194
24855
  /** Maximum number of events to store in breakeven reports */
24195
- const MAX_EVENTS$3 = 250;
24856
+ const MAX_EVENTS$4 = 250;
24196
24857
  /**
24197
24858
  * Storage class for accumulating breakeven events per symbol-strategy pair.
24198
24859
  * Maintains a chronological list of breakeven events.
24199
24860
  */
24200
- let ReportStorage$3 = class ReportStorage {
24861
+ let ReportStorage$4 = class ReportStorage {
24201
24862
  constructor(symbol, strategyName, exchangeName, frameName) {
24202
24863
  this.symbol = symbol;
24203
24864
  this.strategyName = strategyName;
@@ -24238,7 +24899,7 @@ let ReportStorage$3 = class ReportStorage {
24238
24899
  backtest,
24239
24900
  });
24240
24901
  // Trim queue if exceeded MAX_EVENTS
24241
- if (this._eventList.length > MAX_EVENTS$3) {
24902
+ if (this._eventList.length > MAX_EVENTS$4) {
24242
24903
  this._eventList.pop();
24243
24904
  }
24244
24905
  }
@@ -24306,7 +24967,7 @@ let ReportStorage$3 = class ReportStorage {
24306
24967
  async dump(symbol, strategyName, path = "./dump/breakeven", columns = COLUMN_CONFIG.breakeven_columns) {
24307
24968
  const markdown = await this.getReport(symbol, strategyName, columns);
24308
24969
  const timestamp = getContextTimestamp();
24309
- const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24970
+ const filename = CREATE_FILE_NAME_FN$4(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24310
24971
  await Markdown.writeData("breakeven", markdown, {
24311
24972
  path,
24312
24973
  file: filename,
@@ -24347,7 +25008,7 @@ class BreakevenMarkdownService {
24347
25008
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
24348
25009
  * Each combination gets its own isolated storage instance.
24349
25010
  */
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));
25011
+ 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
25012
  /**
24352
25013
  * Subscribes to breakeven signal emitter to receive events.
24353
25014
  * Protected against multiple subscriptions.
@@ -24536,7 +25197,7 @@ class BreakevenMarkdownService {
24536
25197
  payload,
24537
25198
  });
24538
25199
  if (payload) {
24539
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25200
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
24540
25201
  this.getStorage.clear(key);
24541
25202
  }
24542
25203
  else {
@@ -24552,7 +25213,7 @@ class BreakevenMarkdownService {
24552
25213
  * @param context - Context with strategyName, exchangeName, frameName
24553
25214
  * @returns Unique string key for memoization
24554
25215
  */
24555
- const CREATE_KEY_FN$7 = (context) => {
25216
+ const CREATE_KEY_FN$8 = (context) => {
24556
25217
  const parts = [context.strategyName, context.exchangeName];
24557
25218
  if (context.frameName)
24558
25219
  parts.push(context.frameName);
@@ -24626,7 +25287,7 @@ class BreakevenGlobalService {
24626
25287
  * @param context - Context with strategyName, exchangeName and frameName
24627
25288
  * @param methodName - Name of the calling method for error tracking
24628
25289
  */
24629
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$7(context), (context, methodName) => {
25290
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$8(context), (context, methodName) => {
24630
25291
  this.loggerService.log("breakevenGlobalService validate", {
24631
25292
  context,
24632
25293
  methodName,
@@ -24846,7 +25507,7 @@ class ConfigValidationService {
24846
25507
  * @param backtest - Whether running in backtest mode
24847
25508
  * @returns Unique string key for memoization
24848
25509
  */
24849
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
25510
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
24850
25511
  const parts = [symbol, strategyName, exchangeName];
24851
25512
  if (frameName)
24852
25513
  parts.push(frameName);
@@ -24857,7 +25518,7 @@ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest
24857
25518
  * Creates a filename for markdown report based on memoization key components.
24858
25519
  * Filename format: "symbol_strategyName_exchangeName_frameName-timestamp.md"
24859
25520
  */
24860
- const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
25521
+ const CREATE_FILE_NAME_FN$3 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
24861
25522
  const parts = [symbol, strategyName, exchangeName];
24862
25523
  if (frameName) {
24863
25524
  parts.push(frameName);
@@ -24868,12 +25529,12 @@ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, ti
24868
25529
  return `${parts.join("_")}-${timestamp}.md`;
24869
25530
  };
24870
25531
  /** Maximum number of events to store in risk reports */
24871
- const MAX_EVENTS$2 = 250;
25532
+ const MAX_EVENTS$3 = 250;
24872
25533
  /**
24873
25534
  * Storage class for accumulating risk rejection events per symbol-strategy pair.
24874
25535
  * Maintains a chronological list of rejected signals due to risk limits.
24875
25536
  */
24876
- let ReportStorage$2 = class ReportStorage {
25537
+ let ReportStorage$3 = class ReportStorage {
24877
25538
  constructor(symbol, strategyName, exchangeName, frameName) {
24878
25539
  this.symbol = symbol;
24879
25540
  this.strategyName = strategyName;
@@ -24890,7 +25551,7 @@ let ReportStorage$2 = class ReportStorage {
24890
25551
  addRejectionEvent(event) {
24891
25552
  this._eventList.unshift(event);
24892
25553
  // Trim queue if exceeded MAX_EVENTS
24893
- if (this._eventList.length > MAX_EVENTS$2) {
25554
+ if (this._eventList.length > MAX_EVENTS$3) {
24894
25555
  this._eventList.pop();
24895
25556
  }
24896
25557
  }
@@ -24974,7 +25635,7 @@ let ReportStorage$2 = class ReportStorage {
24974
25635
  async dump(symbol, strategyName, path = "./dump/risk", columns = COLUMN_CONFIG.risk_columns) {
24975
25636
  const markdown = await this.getReport(symbol, strategyName, columns);
24976
25637
  const timestamp = getContextTimestamp();
24977
- const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
25638
+ const filename = CREATE_FILE_NAME_FN$3(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
24978
25639
  await Markdown.writeData("risk", markdown, {
24979
25640
  path,
24980
25641
  file: filename,
@@ -25015,7 +25676,7 @@ class RiskMarkdownService {
25015
25676
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25016
25677
  * Each combination gets its own isolated storage instance.
25017
25678
  */
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));
25679
+ 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
25680
  /**
25020
25681
  * Subscribes to risk rejection emitter to receive rejection events.
25021
25682
  * Protected against multiple subscriptions.
@@ -25204,7 +25865,7 @@ class RiskMarkdownService {
25204
25865
  payload,
25205
25866
  });
25206
25867
  if (payload) {
25207
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25868
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25208
25869
  this.getStorage.clear(key);
25209
25870
  }
25210
25871
  else {
@@ -25502,6 +26163,7 @@ const WILDCARD_TARGET = {
25502
26163
  schedule: true,
25503
26164
  walker: true,
25504
26165
  sync: true,
26166
+ highest_profit: true,
25505
26167
  };
25506
26168
  /**
25507
26169
  * Utility class for managing report services.
@@ -25539,7 +26201,7 @@ class ReportUtils {
25539
26201
  *
25540
26202
  * @returns Cleanup function that unsubscribes from all enabled services
25541
26203
  */
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) => {
26204
+ 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
26205
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_ENABLE, {
25544
26206
  backtest: bt$1,
25545
26207
  breakeven,
@@ -25587,6 +26249,9 @@ class ReportUtils {
25587
26249
  if (sync) {
25588
26250
  unList.push(bt.syncReportService.subscribe());
25589
26251
  }
26252
+ if (highest_profit) {
26253
+ unList.push(bt.highestProfitReportService.subscribe());
26254
+ }
25590
26255
  return functoolsKit.compose(...unList.map((un) => () => void un()));
25591
26256
  };
25592
26257
  /**
@@ -25625,7 +26290,7 @@ class ReportUtils {
25625
26290
  * Report.disable();
25626
26291
  * ```
25627
26292
  */
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) => {
26293
+ 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
26294
  bt.loggerService.debug(REPORT_UTILS_METHOD_NAME_DISABLE, {
25630
26295
  backtest: bt$1,
25631
26296
  breakeven,
@@ -25672,6 +26337,9 @@ class ReportUtils {
25672
26337
  if (sync) {
25673
26338
  bt.syncReportService.unsubscribe();
25674
26339
  }
26340
+ if (highest_profit) {
26341
+ bt.highestProfitReportService.unsubscribe();
26342
+ }
25675
26343
  };
25676
26344
  }
25677
26345
  }
@@ -27879,6 +28547,60 @@ class SyncReportService {
27879
28547
  }
27880
28548
  }
27881
28549
 
28550
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE = "HighestProfitReportService.subscribe";
28551
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE = "HighestProfitReportService.unsubscribe";
28552
+ const HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK = "HighestProfitReportService.tick";
28553
+ /**
28554
+ * Service for logging highest profit events to the JSONL report database.
28555
+ *
28556
+ * Listens to highestProfitSubject and writes each new price record to
28557
+ * Report.writeData() for persistence and analytics.
28558
+ */
28559
+ class HighestProfitReportService {
28560
+ constructor() {
28561
+ this.loggerService = inject(TYPES.loggerService);
28562
+ this.tick = async (data) => {
28563
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_TICK, { data });
28564
+ await Report.writeData("highest_profit", {
28565
+ timestamp: data.timestamp,
28566
+ symbol: data.symbol,
28567
+ strategyName: data.signal.strategyName,
28568
+ exchangeName: data.exchangeName,
28569
+ frameName: data.frameName,
28570
+ backtest: data.backtest,
28571
+ signalId: data.signal.id,
28572
+ position: data.signal.position,
28573
+ currentPrice: data.currentPrice,
28574
+ priceOpen: data.signal.priceOpen,
28575
+ priceTakeProfit: data.signal.priceTakeProfit,
28576
+ priceStopLoss: data.signal.priceStopLoss,
28577
+ }, {
28578
+ symbol: data.symbol,
28579
+ strategyName: data.signal.strategyName,
28580
+ exchangeName: data.exchangeName,
28581
+ frameName: data.frameName,
28582
+ signalId: data.signal.id,
28583
+ walkerName: "",
28584
+ });
28585
+ };
28586
+ this.subscribe = functoolsKit.singleshot(() => {
28587
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_SUBSCRIBE);
28588
+ const unsub = highestProfitSubject.subscribe(this.tick);
28589
+ return () => {
28590
+ this.subscribe.clear();
28591
+ unsub();
28592
+ };
28593
+ });
28594
+ this.unsubscribe = async () => {
28595
+ this.loggerService.log(HIGHEST_PROFIT_REPORT_METHOD_NAME_UNSUBSCRIBE);
28596
+ if (this.subscribe.hasValue()) {
28597
+ const lastSubscription = this.subscribe();
28598
+ lastSubscription();
28599
+ }
28600
+ };
28601
+ }
28602
+ }
28603
+
27882
28604
  /**
27883
28605
  * Creates a unique key for memoizing ReportStorage instances.
27884
28606
  *
@@ -27892,7 +28614,7 @@ class SyncReportService {
27892
28614
  * @returns Colon-separated key string for memoization
27893
28615
  * @internal
27894
28616
  */
27895
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28617
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
27896
28618
  const parts = [symbol, strategyName, exchangeName];
27897
28619
  if (frameName)
27898
28620
  parts.push(frameName);
@@ -27912,7 +28634,7 @@ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest
27912
28634
  * @returns Underscore-separated filename with .md extension
27913
28635
  * @internal
27914
28636
  */
27915
- const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28637
+ const CREATE_FILE_NAME_FN$2 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
27916
28638
  const parts = [symbol, strategyName, exchangeName];
27917
28639
  if (frameName) {
27918
28640
  parts.push(frameName);
@@ -27927,7 +28649,7 @@ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, ti
27927
28649
  * Older events are discarded when this limit is exceeded.
27928
28650
  * @internal
27929
28651
  */
27930
- const MAX_EVENTS$1 = 250;
28652
+ const MAX_EVENTS$2 = 250;
27931
28653
  /**
27932
28654
  * In-memory storage for accumulating strategy events per symbol-strategy pair.
27933
28655
  *
@@ -27940,7 +28662,7 @@ const MAX_EVENTS$1 = 250;
27940
28662
  *
27941
28663
  * @internal
27942
28664
  */
27943
- let ReportStorage$1 = class ReportStorage {
28665
+ let ReportStorage$2 = class ReportStorage {
27944
28666
  /**
27945
28667
  * Creates a new ReportStorage instance.
27946
28668
  *
@@ -27966,7 +28688,7 @@ let ReportStorage$1 = class ReportStorage {
27966
28688
  */
27967
28689
  addEvent(event) {
27968
28690
  this._eventList.unshift(event);
27969
- if (this._eventList.length > MAX_EVENTS$1) {
28691
+ if (this._eventList.length > MAX_EVENTS$2) {
27970
28692
  this._eventList.pop();
27971
28693
  }
27972
28694
  }
@@ -28071,7 +28793,7 @@ let ReportStorage$1 = class ReportStorage {
28071
28793
  async dump(symbol, strategyName, path = "./dump/strategy", columns = COLUMN_CONFIG.strategy_columns) {
28072
28794
  const markdown = await this.getReport(symbol, strategyName, columns);
28073
28795
  const timestamp = getContextTimestamp();
28074
- const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28796
+ const filename = CREATE_FILE_NAME_FN$2(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28075
28797
  await Markdown.writeData("strategy", markdown, {
28076
28798
  path,
28077
28799
  file: filename,
@@ -28140,7 +28862,7 @@ class StrategyMarkdownService {
28140
28862
  *
28141
28863
  * @internal
28142
28864
  */
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));
28865
+ 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
28866
  /**
28145
28867
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
28146
28868
  *
@@ -28708,7 +29430,7 @@ class StrategyMarkdownService {
28708
29430
  this.clear = async (payload) => {
28709
29431
  this.loggerService.log("strategyMarkdownService clear", { payload });
28710
29432
  if (payload) {
28711
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29433
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
28712
29434
  this.getStorage.clear(key);
28713
29435
  }
28714
29436
  else {
@@ -28816,7 +29538,7 @@ class StrategyMarkdownService {
28816
29538
  * Creates a unique key for memoizing ReportStorage instances.
28817
29539
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
28818
29540
  */
28819
- const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29541
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
28820
29542
  const parts = [symbol, strategyName, exchangeName];
28821
29543
  if (frameName)
28822
29544
  parts.push(frameName);
@@ -28827,7 +29549,7 @@ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest
28827
29549
  * Creates a filename for the markdown report.
28828
29550
  * Filename format: "symbol_strategyName_exchangeName[_frameName_backtest|_live]-timestamp.md"
28829
29551
  */
28830
- const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29552
+ const CREATE_FILE_NAME_FN$1 = (symbol, strategyName, exchangeName, frameName, timestamp) => {
28831
29553
  const parts = [symbol, strategyName, exchangeName];
28832
29554
  if (frameName) {
28833
29555
  parts.push(frameName);
@@ -28838,12 +29560,12 @@ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, time
28838
29560
  return `${parts.join("_")}-${timestamp}.md`;
28839
29561
  };
28840
29562
  /** Maximum number of events to store in sync reports */
28841
- const MAX_EVENTS = 250;
29563
+ const MAX_EVENTS$1 = 250;
28842
29564
  /**
28843
29565
  * Storage class for accumulating signal sync events per symbol-strategy-exchange-frame-backtest combination.
28844
29566
  * Maintains a chronological list of signal-open and signal-close events.
28845
29567
  */
28846
- class ReportStorage {
29568
+ let ReportStorage$1 = class ReportStorage {
28847
29569
  constructor(symbol, strategyName, exchangeName, frameName) {
28848
29570
  this.symbol = symbol;
28849
29571
  this.strategyName = strategyName;
@@ -28853,7 +29575,7 @@ class ReportStorage {
28853
29575
  }
28854
29576
  addEvent(event) {
28855
29577
  this._eventList.unshift(event);
28856
- if (this._eventList.length > MAX_EVENTS) {
29578
+ if (this._eventList.length > MAX_EVENTS$1) {
28857
29579
  this._eventList.pop();
28858
29580
  }
28859
29581
  }
@@ -28914,7 +29636,7 @@ class ReportStorage {
28914
29636
  async dump(symbol, strategyName, path = "./dump/sync", columns = COLUMN_CONFIG.sync_columns) {
28915
29637
  const markdown = await this.getReport(symbol, strategyName, columns);
28916
29638
  const timestamp = getContextTimestamp();
28917
- const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29639
+ const filename = CREATE_FILE_NAME_FN$1(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
28918
29640
  await Markdown.writeData("sync", markdown, {
28919
29641
  path,
28920
29642
  file: filename,
@@ -28925,7 +29647,7 @@ class ReportStorage {
28925
29647
  frameName: this.frameName,
28926
29648
  });
28927
29649
  }
28928
- }
29650
+ };
28929
29651
  /**
28930
29652
  * Service for generating and saving signal sync markdown reports.
28931
29653
  *
@@ -28948,7 +29670,7 @@ class ReportStorage {
28948
29670
  class SyncMarkdownService {
28949
29671
  constructor() {
28950
29672
  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));
29673
+ 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
29674
  this.subscribe = functoolsKit.singleshot(() => {
28953
29675
  this.loggerService.log("syncMarkdownService init");
28954
29676
  const unsubscribe = syncSubject.subscribe(this.tick);
@@ -29022,6 +29744,190 @@ class SyncMarkdownService {
29022
29744
  };
29023
29745
  this.clear = async (payload) => {
29024
29746
  this.loggerService.log("syncMarkdownService clear", { payload });
29747
+ if (payload) {
29748
+ const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29749
+ this.getStorage.clear(key);
29750
+ }
29751
+ else {
29752
+ this.getStorage.clear();
29753
+ }
29754
+ };
29755
+ }
29756
+ }
29757
+
29758
+ /**
29759
+ * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
29760
+ */
29761
+ const CREATE_KEY_FN$4 = (symbol, strategyName, exchangeName, frameName, backtest) => {
29762
+ const parts = [symbol, strategyName, exchangeName];
29763
+ if (frameName)
29764
+ parts.push(frameName);
29765
+ parts.push(backtest ? "backtest" : "live");
29766
+ return parts.join(":");
29767
+ };
29768
+ /**
29769
+ * Creates a filename for the markdown report.
29770
+ */
29771
+ const CREATE_FILE_NAME_FN = (symbol, strategyName, exchangeName, frameName, timestamp) => {
29772
+ const parts = [symbol, strategyName, exchangeName];
29773
+ if (frameName) {
29774
+ parts.push(frameName);
29775
+ parts.push("backtest");
29776
+ }
29777
+ else
29778
+ parts.push("live");
29779
+ return `${parts.join("_")}-${timestamp}.md`;
29780
+ };
29781
+ /** Maximum number of events to store per combination */
29782
+ const MAX_EVENTS = 250;
29783
+ /**
29784
+ * Accumulates highest profit events per symbol-strategy-exchange-frame combination.
29785
+ */
29786
+ class ReportStorage {
29787
+ constructor(symbol, strategyName, exchangeName, frameName) {
29788
+ this.symbol = symbol;
29789
+ this.strategyName = strategyName;
29790
+ this.exchangeName = exchangeName;
29791
+ this.frameName = frameName;
29792
+ this._eventList = [];
29793
+ }
29794
+ /**
29795
+ * Adds a highest profit event to the storage.
29796
+ */
29797
+ addEvent(data, currentPrice, backtest, timestamp) {
29798
+ this._eventList.unshift({
29799
+ timestamp,
29800
+ symbol: data.symbol,
29801
+ strategyName: data.strategyName,
29802
+ signalId: data.id,
29803
+ position: data.position,
29804
+ pnl: data.pnl,
29805
+ currentPrice,
29806
+ priceOpen: data.priceOpen,
29807
+ priceTakeProfit: data.priceTakeProfit,
29808
+ priceStopLoss: data.priceStopLoss,
29809
+ backtest,
29810
+ });
29811
+ if (this._eventList.length > MAX_EVENTS) {
29812
+ this._eventList.pop();
29813
+ }
29814
+ }
29815
+ /**
29816
+ * Returns aggregated statistics from accumulated events.
29817
+ */
29818
+ async getData() {
29819
+ return {
29820
+ eventList: this._eventList,
29821
+ totalEvents: this._eventList.length,
29822
+ };
29823
+ }
29824
+ /**
29825
+ * Generates a markdown report table for this storage.
29826
+ */
29827
+ async getReport(symbol, strategyName, columns = COLUMN_CONFIG.highest_profit_columns) {
29828
+ const stats = await this.getData();
29829
+ if (stats.totalEvents === 0) {
29830
+ return [
29831
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29832
+ "",
29833
+ "No highest profit events recorded yet.",
29834
+ ].join("\n");
29835
+ }
29836
+ const visibleColumns = [];
29837
+ for (const col of columns) {
29838
+ if (await col.isVisible()) {
29839
+ visibleColumns.push(col);
29840
+ }
29841
+ }
29842
+ const header = visibleColumns.map((col) => col.label);
29843
+ const separator = visibleColumns.map(() => "---");
29844
+ const rows = await Promise.all(this._eventList.map(async (event, index) => Promise.all(visibleColumns.map((col) => col.format(event, index)))));
29845
+ const tableData = [header, separator, ...rows];
29846
+ const table = tableData.map((row) => `| ${row.join(" | ")} |`).join("\n");
29847
+ return [
29848
+ `# Highest Profit Report: ${symbol}:${strategyName}`,
29849
+ "",
29850
+ table,
29851
+ "",
29852
+ `**Total events:** ${stats.totalEvents}`,
29853
+ ].join("\n");
29854
+ }
29855
+ /**
29856
+ * Saves the report to disk.
29857
+ */
29858
+ async dump(symbol, strategyName, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) {
29859
+ const markdown = await this.getReport(symbol, strategyName, columns);
29860
+ const timestamp = getContextTimestamp();
29861
+ const filename = CREATE_FILE_NAME_FN(this.symbol, strategyName, this.exchangeName, this.frameName, timestamp);
29862
+ await Markdown.writeData("highest_profit", markdown, {
29863
+ path,
29864
+ file: filename,
29865
+ symbol: this.symbol,
29866
+ signalId: "",
29867
+ strategyName: this.strategyName,
29868
+ exchangeName: this.exchangeName,
29869
+ frameName: this.frameName,
29870
+ });
29871
+ }
29872
+ }
29873
+ /**
29874
+ * Service for generating and saving highest profit markdown reports.
29875
+ *
29876
+ * Listens to highestProfitSubject and accumulates events per
29877
+ * symbol-strategy-exchange-frame combination. Provides getData(),
29878
+ * getReport(), and dump() methods matching the Partial pattern.
29879
+ */
29880
+ class HighestProfitMarkdownService {
29881
+ constructor() {
29882
+ this.loggerService = inject(TYPES.loggerService);
29883
+ 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));
29884
+ this.subscribe = functoolsKit.singleshot(() => {
29885
+ this.loggerService.log("highestProfitMarkdownService init");
29886
+ const unsub = highestProfitSubject.subscribe(this.tick);
29887
+ return () => {
29888
+ this.subscribe.clear();
29889
+ this.clear();
29890
+ unsub();
29891
+ };
29892
+ });
29893
+ this.unsubscribe = async () => {
29894
+ this.loggerService.log("highestProfitMarkdownService unsubscribe");
29895
+ if (this.subscribe.hasValue()) {
29896
+ const lastSubscription = this.subscribe();
29897
+ lastSubscription();
29898
+ }
29899
+ };
29900
+ this.tick = async (data) => {
29901
+ this.loggerService.log("highestProfitMarkdownService tick", { data });
29902
+ const storage = this.getStorage(data.symbol, data.signal.strategyName, data.exchangeName, data.frameName, data.backtest);
29903
+ storage.addEvent(data.signal, data.currentPrice, data.backtest, data.timestamp);
29904
+ };
29905
+ this.getData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
29906
+ this.loggerService.log("highestProfitMarkdownService getData", { symbol, strategyName, exchangeName, frameName, backtest });
29907
+ if (!this.subscribe.hasValue()) {
29908
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before getting data.");
29909
+ }
29910
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29911
+ return storage.getData();
29912
+ };
29913
+ this.getReport = async (symbol, strategyName, exchangeName, frameName, backtest, columns = COLUMN_CONFIG.highest_profit_columns) => {
29914
+ this.loggerService.log("highestProfitMarkdownService getReport", { symbol, strategyName, exchangeName, frameName, backtest });
29915
+ if (!this.subscribe.hasValue()) {
29916
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before generating reports.");
29917
+ }
29918
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29919
+ return storage.getReport(symbol, strategyName, columns);
29920
+ };
29921
+ this.dump = async (symbol, strategyName, exchangeName, frameName, backtest, path = "./dump/highest_profit", columns = COLUMN_CONFIG.highest_profit_columns) => {
29922
+ this.loggerService.log("highestProfitMarkdownService dump", { symbol, strategyName, exchangeName, frameName, backtest, path });
29923
+ if (!this.subscribe.hasValue()) {
29924
+ throw new Error("HighestProfitMarkdownService not initialized. Call subscribe() before dumping reports.");
29925
+ }
29926
+ const storage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
29927
+ await storage.dump(symbol, strategyName, path, columns);
29928
+ };
29929
+ this.clear = async (payload) => {
29930
+ this.loggerService.log("highestProfitMarkdownService clear", { payload });
29025
29931
  if (payload) {
29026
29932
  const key = CREATE_KEY_FN$4(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
29027
29933
  this.getStorage.clear(key);
@@ -29371,6 +30277,7 @@ class TimeMetaService {
29371
30277
  provide(TYPES.riskMarkdownService, () => new RiskMarkdownService());
29372
30278
  provide(TYPES.strategyMarkdownService, () => new StrategyMarkdownService());
29373
30279
  provide(TYPES.syncMarkdownService, () => new SyncMarkdownService());
30280
+ provide(TYPES.highestProfitMarkdownService, () => new HighestProfitMarkdownService());
29374
30281
  }
29375
30282
  {
29376
30283
  provide(TYPES.backtestReportService, () => new BacktestReportService());
@@ -29384,6 +30291,7 @@ class TimeMetaService {
29384
30291
  provide(TYPES.riskReportService, () => new RiskReportService());
29385
30292
  provide(TYPES.strategyReportService, () => new StrategyReportService());
29386
30293
  provide(TYPES.syncReportService, () => new SyncReportService());
30294
+ provide(TYPES.highestProfitReportService, () => new HighestProfitReportService());
29387
30295
  }
29388
30296
  {
29389
30297
  provide(TYPES.exchangeValidationService, () => new ExchangeValidationService());
@@ -29466,6 +30374,7 @@ const markdownServices = {
29466
30374
  riskMarkdownService: inject(TYPES.riskMarkdownService),
29467
30375
  strategyMarkdownService: inject(TYPES.strategyMarkdownService),
29468
30376
  syncMarkdownService: inject(TYPES.syncMarkdownService),
30377
+ highestProfitMarkdownService: inject(TYPES.highestProfitMarkdownService),
29469
30378
  };
29470
30379
  const reportServices = {
29471
30380
  backtestReportService: inject(TYPES.backtestReportService),
@@ -29479,6 +30388,7 @@ const reportServices = {
29479
30388
  riskReportService: inject(TYPES.riskReportService),
29480
30389
  strategyReportService: inject(TYPES.strategyReportService),
29481
30390
  syncReportService: inject(TYPES.syncReportService),
30391
+ highestProfitReportService: inject(TYPES.highestProfitReportService),
29482
30392
  };
29483
30393
  const validationServices = {
29484
30394
  exchangeValidationService: inject(TYPES.exchangeValidationService),
@@ -31250,7 +32160,7 @@ const investedCostToPercent = (dollarAmount, investedCost) => {
31250
32160
  *
31251
32161
  * @param newStopLossPrice - Desired absolute stop-loss price
31252
32162
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31253
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32163
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31254
32164
  * @returns percentShift to pass to `commitTrailingStop`
31255
32165
  *
31256
32166
  * @example
@@ -31272,7 +32182,7 @@ const slPriceToPercentShift = (newStopLossPrice, originalStopLossPrice, effectiv
31272
32182
  *
31273
32183
  * @param newTakeProfitPrice - Desired absolute take-profit price
31274
32184
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31275
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32185
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31276
32186
  * @returns percentShift to pass to `commitTrailingTake`
31277
32187
  *
31278
32188
  * @example
@@ -31297,7 +32207,7 @@ const tpPriceToPercentShift = (newTakeProfitPrice, originalTakeProfitPrice, effe
31297
32207
  *
31298
32208
  * @param percentShift - Value returned by `slPriceToPercentShift` (or passed to `commitTrailingStop`)
31299
32209
  * @param originalStopLossPrice - Original stop-loss price from the pending signal
31300
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32210
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31301
32211
  * @param position - Position direction: "long" or "short"
31302
32212
  * @returns Absolute stop-loss price corresponding to the given percentShift
31303
32213
  *
@@ -31324,7 +32234,7 @@ const slPercentShiftToPrice = (percentShift, originalStopLossPrice, effectivePri
31324
32234
  *
31325
32235
  * @param percentShift - Value returned by `tpPriceToPercentShift` (or passed to `commitTrailingTake`)
31326
32236
  * @param originalTakeProfitPrice - Original take-profit price from the pending signal
31327
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32237
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31328
32238
  * @param position - Position direction: "long" or "short"
31329
32239
  * @returns Absolute take-profit price corresponding to the given percentShift
31330
32240
  *
@@ -31363,7 +32273,7 @@ const percentToCloseCost = (percentToClose, investedCost) => {
31363
32273
  * Breakeven moves the SL to the effective entry price (effectivePriceOpen).
31364
32274
  * The value is the same regardless of position direction.
31365
32275
  *
31366
- * @param effectivePriceOpen - Effective entry price (from `getPositionAveragePrice`)
32276
+ * @param effectivePriceOpen - Effective entry price (from `getPositionEffectivePrice`)
31367
32277
  * @returns New stop-loss price equal to the effective entry price
31368
32278
  *
31369
32279
  * @example
@@ -32285,13 +33195,22 @@ const GET_TOTAL_COST_CLOSED_METHOD_NAME = "strategy.getTotalCostClosed";
32285
33195
  const GET_PENDING_SIGNAL_METHOD_NAME = "strategy.getPendingSignal";
32286
33196
  const GET_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.getScheduledSignal";
32287
33197
  const GET_BREAKEVEN_METHOD_NAME = "strategy.getBreakeven";
32288
- const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionAveragePrice";
33198
+ const GET_POSITION_AVERAGE_PRICE_METHOD_NAME = "strategy.getPositionEffectivePrice";
32289
33199
  const GET_POSITION_INVESTED_COUNT_METHOD_NAME = "strategy.getPositionInvestedCount";
32290
33200
  const GET_POSITION_INVESTED_COST_METHOD_NAME = "strategy.getPositionInvestedCost";
32291
33201
  const GET_POSITION_PNL_PERCENT_METHOD_NAME = "strategy.getPositionPnlPercent";
32292
33202
  const GET_POSITION_PNL_COST_METHOD_NAME = "strategy.getPositionPnlCost";
32293
33203
  const GET_POSITION_LEVELS_METHOD_NAME = "strategy.getPositionLevels";
32294
33204
  const GET_POSITION_PARTIALS_METHOD_NAME = "strategy.getPositionPartials";
33205
+ const GET_POSITION_ENTRIES_METHOD_NAME = "strategy.getPositionEntries";
33206
+ const GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME = "strategy.getPositionEstimateMinutes";
33207
+ const GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME = "strategy.getPositionCountdownMinutes";
33208
+ const GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME = "strategy.getPositionHighestProfitPrice";
33209
+ const GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME = "strategy.getPositionHighestProfitTimestamp";
33210
+ const GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestPnlPercentage";
33211
+ const GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME = "strategy.getPositionHighestPnlCost";
33212
+ const GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME = "strategy.getPositionHighestProfitBreakeven";
33213
+ const GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME = "strategy.getPositionDrawdownMinutes";
32295
33214
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
32296
33215
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
32297
33216
  /**
@@ -32558,7 +33477,7 @@ async function commitTrailingStop(symbol, percentShift, currentPrice) {
32558
33477
  if (!signal) {
32559
33478
  return false;
32560
33479
  }
32561
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33480
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32562
33481
  if (effectivePriceOpen === null) {
32563
33482
  return false;
32564
33483
  }
@@ -32638,7 +33557,7 @@ async function commitTrailingTake(symbol, percentShift, currentPrice) {
32638
33557
  if (!signal) {
32639
33558
  return false;
32640
33559
  }
32641
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33560
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32642
33561
  if (effectivePriceOpen === null) {
32643
33562
  return false;
32644
33563
  }
@@ -32688,7 +33607,7 @@ async function commitTrailingStopCost(symbol, newStopLossPrice) {
32688
33607
  if (!signal) {
32689
33608
  return false;
32690
33609
  }
32691
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33610
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32692
33611
  if (effectivePriceOpen === null) {
32693
33612
  return false;
32694
33613
  }
@@ -32739,7 +33658,7 @@ async function commitTrailingTakeCost(symbol, newTakeProfitPrice) {
32739
33658
  if (!signal) {
32740
33659
  return false;
32741
33660
  }
32742
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33661
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32743
33662
  if (effectivePriceOpen === null) {
32744
33663
  return false;
32745
33664
  }
@@ -32801,7 +33720,7 @@ async function commitBreakeven(symbol) {
32801
33720
  if (!signal) {
32802
33721
  return false;
32803
33722
  }
32804
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33723
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
32805
33724
  if (effectivePriceOpen === null) {
32806
33725
  return false;
32807
33726
  }
@@ -33097,26 +34016,26 @@ async function getBreakeven(symbol, currentPrice) {
33097
34016
  *
33098
34017
  * @example
33099
34018
  * ```typescript
33100
- * import { getPositionAveragePrice } from "backtest-kit";
34019
+ * import { getPositionEffectivePrice } from "backtest-kit";
33101
34020
  *
33102
- * const avgPrice = await getPositionAveragePrice("BTCUSDT");
34021
+ * const avgPrice = await getPositionEffectivePrice("BTCUSDT");
33103
34022
  * // No DCA: avgPrice === priceOpen
33104
34023
  * // After DCA at lower price: avgPrice < priceOpen
33105
34024
  * ```
33106
34025
  */
33107
- async function getPositionAveragePrice(symbol) {
34026
+ async function getPositionEffectivePrice(symbol) {
33108
34027
  bt.loggerService.info(GET_POSITION_AVERAGE_PRICE_METHOD_NAME, {
33109
34028
  symbol,
33110
34029
  });
33111
34030
  if (!ExecutionContextService.hasContext()) {
33112
- throw new Error("getPositionAveragePrice requires an execution context");
34031
+ throw new Error("getPositionEffectivePrice requires an execution context");
33113
34032
  }
33114
34033
  if (!MethodContextService.hasContext()) {
33115
- throw new Error("getPositionAveragePrice requires a method context");
34034
+ throw new Error("getPositionEffectivePrice requires a method context");
33116
34035
  }
33117
34036
  const { backtest: isBacktest } = bt.executionContextService.context;
33118
34037
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33119
- return await bt.strategyCoreService.getPositionAveragePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34038
+ return await bt.strategyCoreService.getPositionEffectivePrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
33120
34039
  }
33121
34040
  /**
33122
34041
  * Returns the number of DCA entries made for the current pending signal.
@@ -33471,6 +34390,284 @@ async function getPositionPartials(symbol) {
33471
34390
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
33472
34391
  return await bt.strategyCoreService.getPositionPartials(isBacktest, symbol, { exchangeName, frameName, strategyName });
33473
34392
  }
34393
+ /**
34394
+ * Returns the list of DCA entry prices and costs for the current pending signal.
34395
+ *
34396
+ * Each element represents a single position entry — the initial open or a subsequent
34397
+ * DCA entry added via commitAverageBuy.
34398
+ *
34399
+ * Returns null if no pending signal exists.
34400
+ * Returns a single-element array if no DCA entries were made.
34401
+ *
34402
+ * Each entry contains:
34403
+ * - `price` — execution price of this entry
34404
+ * - `cost` — dollar cost allocated to this entry (e.g. 100 for $100)
34405
+ *
34406
+ * @param symbol - Trading pair symbol
34407
+ * @returns Promise resolving to array of entry records or null
34408
+ *
34409
+ * @example
34410
+ * ```typescript
34411
+ * import { getPositionEntries } from "backtest-kit";
34412
+ *
34413
+ * const entries = await getPositionEntries("BTCUSDT");
34414
+ * // No DCA: [{ price: 43000, cost: 100 }]
34415
+ * // One DCA: [{ price: 43000, cost: 100 }, { price: 42000, cost: 100 }]
34416
+ * ```
34417
+ */
34418
+ async function getPositionEntries(symbol) {
34419
+ bt.loggerService.info(GET_POSITION_ENTRIES_METHOD_NAME, { symbol });
34420
+ if (!ExecutionContextService.hasContext()) {
34421
+ throw new Error("getPositionEntries requires an execution context");
34422
+ }
34423
+ if (!MethodContextService.hasContext()) {
34424
+ throw new Error("getPositionEntries requires a method context");
34425
+ }
34426
+ const { backtest: isBacktest } = bt.executionContextService.context;
34427
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34428
+ return await bt.strategyCoreService.getPositionEntries(isBacktest, symbol, { exchangeName, frameName, strategyName });
34429
+ }
34430
+ /**
34431
+ * Returns the original estimated duration for the current pending signal.
34432
+ *
34433
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
34434
+ * number of minutes the position is expected to be active before `time_expired`.
34435
+ *
34436
+ * Returns null if no pending signal exists.
34437
+ *
34438
+ * @param symbol - Trading pair symbol
34439
+ * @returns Promise resolving to estimated duration in minutes or null
34440
+ *
34441
+ * @example
34442
+ * ```typescript
34443
+ * import { getPositionEstimateMinutes } from "backtest-kit";
34444
+ *
34445
+ * const estimate = await getPositionEstimateMinutes("BTCUSDT");
34446
+ * // e.g. 120 (2 hours)
34447
+ * ```
34448
+ */
34449
+ async function getPositionEstimateMinutes(symbol) {
34450
+ bt.loggerService.info(GET_POSITION_ESTIMATE_MINUTES_METHOD_NAME, { symbol });
34451
+ if (!ExecutionContextService.hasContext()) {
34452
+ throw new Error("getPositionEstimateMinutes requires an execution context");
34453
+ }
34454
+ if (!MethodContextService.hasContext()) {
34455
+ throw new Error("getPositionEstimateMinutes requires a method context");
34456
+ }
34457
+ const { backtest: isBacktest } = bt.executionContextService.context;
34458
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34459
+ return await bt.strategyCoreService.getPositionEstimateMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34460
+ }
34461
+ /**
34462
+ * Returns the remaining time before the position expires, clamped to zero.
34463
+ *
34464
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
34465
+ * Returns 0 once the estimate is exceeded (never negative).
34466
+ *
34467
+ * Returns null if no pending signal exists.
34468
+ *
34469
+ * @param symbol - Trading pair symbol
34470
+ * @returns Promise resolving to remaining minutes (≥ 0) or null
34471
+ *
34472
+ * @example
34473
+ * ```typescript
34474
+ * import { getPositionCountdownMinutes } from "backtest-kit";
34475
+ *
34476
+ * const remaining = await getPositionCountdownMinutes("BTCUSDT");
34477
+ * // e.g. 45 (45 minutes left)
34478
+ * // 0 when expired
34479
+ * ```
34480
+ */
34481
+ async function getPositionCountdownMinutes(symbol) {
34482
+ bt.loggerService.info(GET_POSITION_COUNTDOWN_MINUTES_METHOD_NAME, { symbol });
34483
+ if (!ExecutionContextService.hasContext()) {
34484
+ throw new Error("getPositionCountdownMinutes requires an execution context");
34485
+ }
34486
+ if (!MethodContextService.hasContext()) {
34487
+ throw new Error("getPositionCountdownMinutes requires a method context");
34488
+ }
34489
+ const { backtest: isBacktest } = bt.executionContextService.context;
34490
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34491
+ return await bt.strategyCoreService.getPositionCountdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34492
+ }
34493
+ /**
34494
+ * Returns the best price reached in the profit direction during this position's life.
34495
+ *
34496
+ * Initialized at position open with the entry price and timestamp.
34497
+ * Updated on every tick/candle when VWAP moves beyond the previous record toward TP:
34498
+ * - LONG: tracks the highest price seen above effective entry
34499
+ * - SHORT: tracks the lowest price seen below effective entry
34500
+ *
34501
+ * Returns null if no pending signal exists.
34502
+ * Never returns null when a signal is active — always contains at least the entry price.
34503
+ *
34504
+ * @param symbol - Trading pair symbol
34505
+ * @returns Promise resolving to `{ price, timestamp }` record or null
34506
+ *
34507
+ * @example
34508
+ * ```typescript
34509
+ * import { getPositionHighestProfitPrice } from "backtest-kit";
34510
+ *
34511
+ * const peak = await getPositionHighestProfitPrice("BTCUSDT");
34512
+ * // e.g. { price: 44500, timestamp: 1700000000000 }
34513
+ * ```
34514
+ */
34515
+ async function getPositionHighestProfitPrice(symbol) {
34516
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_PRICE_METHOD_NAME, { symbol });
34517
+ if (!ExecutionContextService.hasContext()) {
34518
+ throw new Error("getPositionHighestProfitPrice requires an execution context");
34519
+ }
34520
+ if (!MethodContextService.hasContext()) {
34521
+ throw new Error("getPositionHighestProfitPrice requires a method context");
34522
+ }
34523
+ const { backtest: isBacktest } = bt.executionContextService.context;
34524
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34525
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(isBacktest, symbol, { exchangeName, frameName, strategyName });
34526
+ }
34527
+ /**
34528
+ * Returns the timestamp when the best profit price was recorded during this position's life.
34529
+ *
34530
+ * Returns null if no pending signal exists.
34531
+ *
34532
+ * @param symbol - Trading pair symbol
34533
+ * @returns Promise resolving to timestamp in milliseconds or null
34534
+ *
34535
+ * @example
34536
+ * ```typescript
34537
+ * import { getPositionHighestProfitTimestamp } from "backtest-kit";
34538
+ *
34539
+ * const ts = await getPositionHighestProfitTimestamp("BTCUSDT");
34540
+ * // e.g. 1700000000000
34541
+ * ```
34542
+ */
34543
+ async function getPositionHighestProfitTimestamp(symbol) {
34544
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_TIMESTAMP_METHOD_NAME, { symbol });
34545
+ if (!ExecutionContextService.hasContext()) {
34546
+ throw new Error("getPositionHighestProfitTimestamp requires an execution context");
34547
+ }
34548
+ if (!MethodContextService.hasContext()) {
34549
+ throw new Error("getPositionHighestProfitTimestamp requires a method context");
34550
+ }
34551
+ const { backtest: isBacktest } = bt.executionContextService.context;
34552
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34553
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(isBacktest, symbol, { exchangeName, frameName, strategyName });
34554
+ }
34555
+ /**
34556
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
34557
+ *
34558
+ * Returns null if no pending signal exists.
34559
+ *
34560
+ * @param symbol - Trading pair symbol
34561
+ * @returns Promise resolving to PnL percentage or null
34562
+ *
34563
+ * @example
34564
+ * ```typescript
34565
+ * import { getPositionHighestPnlPercentage } from "backtest-kit";
34566
+ *
34567
+ * const pnl = await getPositionHighestPnlPercentage("BTCUSDT");
34568
+ * // e.g. 3.5
34569
+ * ```
34570
+ */
34571
+ async function getPositionHighestPnlPercentage(symbol) {
34572
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_PERCENTAGE_METHOD_NAME, { symbol });
34573
+ if (!ExecutionContextService.hasContext()) {
34574
+ throw new Error("getPositionHighestPnlPercentage requires an execution context");
34575
+ }
34576
+ if (!MethodContextService.hasContext()) {
34577
+ throw new Error("getPositionHighestPnlPercentage requires a method context");
34578
+ }
34579
+ const { backtest: isBacktest } = bt.executionContextService.context;
34580
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34581
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
34582
+ }
34583
+ /**
34584
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
34585
+ *
34586
+ * Returns null if no pending signal exists.
34587
+ *
34588
+ * @param symbol - Trading pair symbol
34589
+ * @returns Promise resolving to PnL cost or null
34590
+ *
34591
+ * @example
34592
+ * ```typescript
34593
+ * import { getPositionHighestPnlCost } from "backtest-kit";
34594
+ *
34595
+ * const pnlCost = await getPositionHighestPnlCost("BTCUSDT");
34596
+ * // e.g. 35.5
34597
+ * ```
34598
+ */
34599
+ async function getPositionHighestPnlCost(symbol) {
34600
+ bt.loggerService.info(GET_POSITION_HIGHEST_PNL_COST_METHOD_NAME, { symbol });
34601
+ if (!ExecutionContextService.hasContext()) {
34602
+ throw new Error("getPositionHighestPnlCost requires an execution context");
34603
+ }
34604
+ if (!MethodContextService.hasContext()) {
34605
+ throw new Error("getPositionHighestPnlCost requires a method context");
34606
+ }
34607
+ const { backtest: isBacktest } = bt.executionContextService.context;
34608
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34609
+ return await bt.strategyCoreService.getPositionHighestPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
34610
+ }
34611
+ /**
34612
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
34613
+ *
34614
+ * Returns null if no pending signal exists.
34615
+ *
34616
+ * @param symbol - Trading pair symbol
34617
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
34618
+ *
34619
+ * @example
34620
+ * ```typescript
34621
+ * import { getPositionHighestProfitBreakeven } from "backtest-kit";
34622
+ *
34623
+ * const couldBreakeven = await getPositionHighestProfitBreakeven("BTCUSDT");
34624
+ * // e.g. true (price reached the breakeven threshold at its peak)
34625
+ * ```
34626
+ */
34627
+ async function getPositionHighestProfitBreakeven(symbol) {
34628
+ bt.loggerService.info(GET_POSITION_HIGHEST_PROFIT_BREAKEVEN_METHOD_NAME, { symbol });
34629
+ if (!ExecutionContextService.hasContext()) {
34630
+ throw new Error("getPositionHighestProfitBreakeven requires an execution context");
34631
+ }
34632
+ if (!MethodContextService.hasContext()) {
34633
+ throw new Error("getPositionHighestProfitBreakeven requires a method context");
34634
+ }
34635
+ const { backtest: isBacktest } = bt.executionContextService.context;
34636
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34637
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(isBacktest, symbol, { exchangeName, frameName, strategyName });
34638
+ }
34639
+ /**
34640
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
34641
+ *
34642
+ * Measures how long the position has been pulling back from its peak profit level.
34643
+ * Zero when called at the exact moment the peak was set.
34644
+ * Grows continuously as price moves away from the peak without setting a new record.
34645
+ *
34646
+ * Returns null if no pending signal exists.
34647
+ *
34648
+ * @param symbol - Trading pair symbol
34649
+ * @returns Promise resolving to drawdown duration in minutes or null
34650
+ *
34651
+ * @example
34652
+ * ```typescript
34653
+ * import { getPositionDrawdownMinutes } from "backtest-kit";
34654
+ *
34655
+ * const drawdown = await getPositionDrawdownMinutes("BTCUSDT");
34656
+ * // e.g. 30 (30 minutes since the highest profit price)
34657
+ * ```
34658
+ */
34659
+ async function getPositionDrawdownMinutes(symbol) {
34660
+ bt.loggerService.info(GET_POSITION_DRAWDOWN_MINUTES_METHOD_NAME, { symbol });
34661
+ if (!ExecutionContextService.hasContext()) {
34662
+ throw new Error("getPositionDrawdownMinutes requires an execution context");
34663
+ }
34664
+ if (!MethodContextService.hasContext()) {
34665
+ throw new Error("getPositionDrawdownMinutes requires a method context");
34666
+ }
34667
+ const { backtest: isBacktest } = bt.executionContextService.context;
34668
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
34669
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(isBacktest, symbol, { exchangeName, frameName, strategyName });
34670
+ }
33474
34671
  /**
33475
34672
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
33476
34673
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -33648,6 +34845,8 @@ const LISTEN_STRATEGY_COMMIT_METHOD_NAME = "event.listenStrategyCommit";
33648
34845
  const LISTEN_STRATEGY_COMMIT_ONCE_METHOD_NAME = "event.listenStrategyCommitOnce";
33649
34846
  const LISTEN_SYNC_METHOD_NAME = "event.listenSync";
33650
34847
  const LISTEN_SYNC_ONCE_METHOD_NAME = "event.listenSyncOnce";
34848
+ const LISTEN_HIGHEST_PROFIT_METHOD_NAME = "event.listenHighestProfit";
34849
+ const LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME = "event.listenHighestProfitOnce";
33651
34850
  /**
33652
34851
  * Subscribes to all signal events with queued async processing.
33653
34852
  *
@@ -34774,6 +35973,33 @@ function listenSyncOnce(filterFn, fn) {
34774
35973
  bt.loggerService.log(LISTEN_SYNC_ONCE_METHOD_NAME);
34775
35974
  return syncSubject.filter(filterFn).once(fn);
34776
35975
  }
35976
+ /**
35977
+ * Subscribes to highest profit events with queued async processing.
35978
+ * Emits when a signal reaches a new highest profit level during its lifecycle.
35979
+ * Events are processed sequentially in order received, even if callback is async.
35980
+ * Uses queued wrapper to prevent concurrent execution of the callback.
35981
+ * Useful for tracking profit milestones and implementing dynamic management logic.
35982
+ *
35983
+ * @param fn - Callback function to handle highest profit events
35984
+ * @return Unsubscribe function to stop listening to events
35985
+ */
35986
+ function listenHighestProfit(fn) {
35987
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_METHOD_NAME);
35988
+ return highestProfitSubject.subscribe(functoolsKit.queued(async (event) => fn(event)));
35989
+ }
35990
+ /**
35991
+ * Subscribes to filtered highest profit events with one-time execution.
35992
+ * Listens for events matching the filter predicate, then executes callback once
35993
+ * and automatically unsubscribes. Useful for waiting for specific profit conditions.
35994
+ *
35995
+ * @param filterFn - Predicate to filter which events trigger the callback
35996
+ * @param fn - Callback function to handle the filtered event (called only once)
35997
+ * @returns Unsubscribe function to cancel the listener before it fires
35998
+ */
35999
+ function listenHighestProfitOnce(filterFn, fn) {
36000
+ bt.loggerService.log(LISTEN_HIGHEST_PROFIT_ONCE_METHOD_NAME);
36001
+ return highestProfitSubject.filter(filterFn).once(fn);
36002
+ }
34777
36003
 
34778
36004
  const BACKTEST_METHOD_NAME_RUN = "BacktestUtils.run";
34779
36005
  const BACKTEST_METHOD_NAME_BACKGROUND = "BacktestUtils.background";
@@ -34787,7 +36013,7 @@ const BACKTEST_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "BacktestUtils.getTotalPer
34787
36013
  const BACKTEST_METHOD_NAME_GET_TOTAL_COST_CLOSED = "BacktestUtils.getTotalCostClosed";
34788
36014
  const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
34789
36015
  const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
34790
- const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionAveragePrice";
36016
+ const BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "BacktestUtils.getPositionEffectivePrice";
34791
36017
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "BacktestUtils.getPositionInvestedCount";
34792
36018
  const BACKTEST_METHOD_NAME_GET_POSITION_INVESTED_COST = "BacktestUtils.getPositionInvestedCost";
34793
36019
  const BACKTEST_METHOD_NAME_GET_POSITION_PNL_PERCENT = "BacktestUtils.getPositionPnlPercent";
@@ -34795,6 +36021,14 @@ const BACKTEST_METHOD_NAME_GET_POSITION_PNL_COST = "BacktestUtils.getPositionPnl
34795
36021
  const BACKTEST_METHOD_NAME_GET_POSITION_LEVELS = "BacktestUtils.getPositionLevels";
34796
36022
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIALS = "BacktestUtils.getPositionPartials";
34797
36023
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRIES = "BacktestUtils.getPositionEntries";
36024
+ const BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "BacktestUtils.getPositionEstimateMinutes";
36025
+ const BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "BacktestUtils.getPositionCountdownMinutes";
36026
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "BacktestUtils.getPositionHighestProfitPrice";
36027
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "BacktestUtils.getPositionHighestProfitTimestamp";
36028
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestPnlPercentage";
36029
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "BacktestUtils.getPositionHighestPnlCost";
36030
+ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "BacktestUtils.getPositionHighestProfitBreakeven";
36031
+ const BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "BacktestUtils.getPositionDrawdownMinutes";
34798
36032
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
34799
36033
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
34800
36034
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -35367,7 +36601,7 @@ class BacktestUtils {
35367
36601
  * @param context - Execution context with strategyName, exchangeName, and frameName
35368
36602
  * @returns Effective entry price, or null if no active position
35369
36603
  */
35370
- this.getPositionAveragePrice = async (symbol, context) => {
36604
+ this.getPositionEffectivePrice = async (symbol, context) => {
35371
36605
  bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
35372
36606
  symbol,
35373
36607
  context,
@@ -35383,7 +36617,7 @@ class BacktestUtils {
35383
36617
  actions &&
35384
36618
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
35385
36619
  }
35386
- return await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
36620
+ return await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
35387
36621
  };
35388
36622
  /**
35389
36623
  * Returns the total number of base-asset units currently held in the position.
@@ -35601,6 +36835,230 @@ class BacktestUtils {
35601
36835
  }
35602
36836
  return await bt.strategyCoreService.getPositionEntries(true, symbol, context);
35603
36837
  };
36838
+ /**
36839
+ * Returns the original estimated duration for the current pending signal.
36840
+ *
36841
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
36842
+ * number of minutes the position is expected to be active before `time_expired`.
36843
+ *
36844
+ * Returns null if no pending signal exists.
36845
+ *
36846
+ * @param symbol - Trading pair symbol
36847
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36848
+ * @returns Estimated duration in minutes, or null if no active position
36849
+ */
36850
+ this.getPositionEstimateMinutes = async (symbol, context) => {
36851
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
36852
+ symbol,
36853
+ context,
36854
+ });
36855
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36856
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36857
+ {
36858
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36859
+ riskName &&
36860
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
36861
+ riskList &&
36862
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36863
+ actions &&
36864
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
36865
+ }
36866
+ return await bt.strategyCoreService.getPositionEstimateMinutes(true, symbol, context);
36867
+ };
36868
+ /**
36869
+ * Returns the remaining time before the position expires, clamped to zero.
36870
+ *
36871
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
36872
+ * Returns 0 once the estimate is exceeded (never negative).
36873
+ *
36874
+ * Returns null if no pending signal exists.
36875
+ *
36876
+ * @param symbol - Trading pair symbol
36877
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36878
+ * @returns Remaining minutes (≥ 0), or null if no active position
36879
+ */
36880
+ this.getPositionCountdownMinutes = async (symbol, context) => {
36881
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
36882
+ symbol,
36883
+ context,
36884
+ });
36885
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36886
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36887
+ {
36888
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36889
+ riskName &&
36890
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
36891
+ riskList &&
36892
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36893
+ actions &&
36894
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
36895
+ }
36896
+ return await bt.strategyCoreService.getPositionCountdownMinutes(true, symbol, context);
36897
+ };
36898
+ /**
36899
+ * Returns the best price reached in the profit direction during this position's life.
36900
+ *
36901
+ * Returns null if no pending signal exists.
36902
+ *
36903
+ * @param symbol - Trading pair symbol
36904
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36905
+ * @returns price or null if no active position
36906
+ */
36907
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
36908
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
36909
+ symbol,
36910
+ context,
36911
+ });
36912
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36913
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36914
+ {
36915
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36916
+ riskName &&
36917
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
36918
+ riskList &&
36919
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36920
+ actions &&
36921
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
36922
+ }
36923
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(true, symbol, context);
36924
+ };
36925
+ /**
36926
+ * Returns the timestamp when the best profit price was recorded during this position's life.
36927
+ *
36928
+ * Returns null if no pending signal exists.
36929
+ *
36930
+ * @param symbol - Trading pair symbol
36931
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36932
+ * @returns timestamp in milliseconds or null if no active position
36933
+ */
36934
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
36935
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
36936
+ symbol,
36937
+ context,
36938
+ });
36939
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36940
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36941
+ {
36942
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36943
+ riskName &&
36944
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
36945
+ riskList &&
36946
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36947
+ actions &&
36948
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
36949
+ }
36950
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(true, symbol, context);
36951
+ };
36952
+ /**
36953
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
36954
+ *
36955
+ * Returns null if no pending signal exists.
36956
+ *
36957
+ * @param symbol - Trading pair symbol
36958
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36959
+ * @returns PnL percentage or null if no active position
36960
+ */
36961
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
36962
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
36963
+ symbol,
36964
+ context,
36965
+ });
36966
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36967
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36968
+ {
36969
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36970
+ riskName &&
36971
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
36972
+ riskList &&
36973
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36974
+ actions &&
36975
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
36976
+ }
36977
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(true, symbol, context);
36978
+ };
36979
+ /**
36980
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
36981
+ *
36982
+ * Returns null if no pending signal exists.
36983
+ *
36984
+ * @param symbol - Trading pair symbol
36985
+ * @param context - Execution context with strategyName, exchangeName, and frameName
36986
+ * @returns PnL cost or null if no active position
36987
+ */
36988
+ this.getPositionHighestPnlCost = async (symbol, context) => {
36989
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
36990
+ symbol,
36991
+ context,
36992
+ });
36993
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36994
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36995
+ {
36996
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
36997
+ riskName &&
36998
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
36999
+ riskList &&
37000
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
37001
+ actions &&
37002
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
37003
+ }
37004
+ return await bt.strategyCoreService.getPositionHighestPnlCost(true, symbol, context);
37005
+ };
37006
+ /**
37007
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
37008
+ *
37009
+ * @param symbol - Trading pair symbol
37010
+ * @param context - Execution context with strategyName, exchangeName, and frameName
37011
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
37012
+ */
37013
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
37014
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
37015
+ symbol,
37016
+ context,
37017
+ });
37018
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37019
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37020
+ {
37021
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37022
+ riskName &&
37023
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
37024
+ riskList &&
37025
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37026
+ actions &&
37027
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
37028
+ }
37029
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(true, symbol, context);
37030
+ };
37031
+ /**
37032
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
37033
+ *
37034
+ * Measures how long the position has been pulling back from its peak profit level.
37035
+ * Zero when called at the exact moment the peak was set.
37036
+ * Grows continuously as price moves away from the peak without setting a new record.
37037
+ *
37038
+ * Returns null if no pending signal exists.
37039
+ *
37040
+ * @param symbol - Trading pair symbol
37041
+ * @param context - Execution context with strategyName, exchangeName, and frameName
37042
+ * @returns Drawdown duration in minutes, or null if no active position
37043
+ */
37044
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
37045
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
37046
+ symbol,
37047
+ context,
37048
+ });
37049
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37050
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37051
+ {
37052
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
37053
+ riskName &&
37054
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
37055
+ riskList &&
37056
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37057
+ actions &&
37058
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
37059
+ }
37060
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(true, symbol, context);
37061
+ };
35604
37062
  /**
35605
37063
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
35606
37064
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -36162,7 +37620,7 @@ class BacktestUtils {
36162
37620
  if (!signal) {
36163
37621
  return false;
36164
37622
  }
36165
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37623
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36166
37624
  if (effectivePriceOpen === null) {
36167
37625
  return false;
36168
37626
  }
@@ -36247,7 +37705,7 @@ class BacktestUtils {
36247
37705
  if (!signal) {
36248
37706
  return false;
36249
37707
  }
36250
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37708
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36251
37709
  if (effectivePriceOpen === null) {
36252
37710
  return false;
36253
37711
  }
@@ -36300,7 +37758,7 @@ class BacktestUtils {
36300
37758
  if (!signal) {
36301
37759
  return false;
36302
37760
  }
36303
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37761
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36304
37762
  if (effectivePriceOpen === null) {
36305
37763
  return false;
36306
37764
  }
@@ -36354,7 +37812,7 @@ class BacktestUtils {
36354
37812
  if (!signal) {
36355
37813
  return false;
36356
37814
  }
36357
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37815
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36358
37816
  if (effectivePriceOpen === null) {
36359
37817
  return false;
36360
37818
  }
@@ -36416,7 +37874,7 @@ class BacktestUtils {
36416
37874
  if (!signal) {
36417
37875
  return false;
36418
37876
  }
36419
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(true, symbol, context);
37877
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(true, symbol, context);
36420
37878
  if (effectivePriceOpen === null) {
36421
37879
  return false;
36422
37880
  }
@@ -36704,7 +38162,7 @@ const LIVE_METHOD_NAME_GET_TOTAL_PERCENT_CLOSED = "LiveUtils.getTotalPercentClos
36704
38162
  const LIVE_METHOD_NAME_GET_TOTAL_COST_CLOSED = "LiveUtils.getTotalCostClosed";
36705
38163
  const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
36706
38164
  const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
36707
- const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionAveragePrice";
38165
+ const LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE = "LiveUtils.getPositionEffectivePrice";
36708
38166
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COUNT = "LiveUtils.getPositionInvestedCount";
36709
38167
  const LIVE_METHOD_NAME_GET_POSITION_INVESTED_COST = "LiveUtils.getPositionInvestedCost";
36710
38168
  const LIVE_METHOD_NAME_GET_POSITION_PNL_PERCENT = "LiveUtils.getPositionPnlPercent";
@@ -36712,6 +38170,14 @@ const LIVE_METHOD_NAME_GET_POSITION_PNL_COST = "LiveUtils.getPositionPnlCost";
36712
38170
  const LIVE_METHOD_NAME_GET_POSITION_LEVELS = "LiveUtils.getPositionLevels";
36713
38171
  const LIVE_METHOD_NAME_GET_POSITION_PARTIALS = "LiveUtils.getPositionPartials";
36714
38172
  const LIVE_METHOD_NAME_GET_POSITION_ENTRIES = "LiveUtils.getPositionEntries";
38173
+ const LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES = "LiveUtils.getPositionEstimateMinutes";
38174
+ const LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES = "LiveUtils.getPositionCountdownMinutes";
38175
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "LiveUtils.getPositionHighestProfitPrice";
38176
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "LiveUtils.getPositionHighestProfitTimestamp";
38177
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "LiveUtils.getPositionHighestPnlPercentage";
38178
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "LiveUtils.getPositionHighestPnlCost";
38179
+ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "LiveUtils.getPositionHighestProfitBreakeven";
38180
+ const LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "LiveUtils.getPositionDrawdownMinutes";
36715
38181
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
36716
38182
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
36717
38183
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -37315,7 +38781,7 @@ class LiveUtils {
37315
38781
  * @param context - Execution context with strategyName and exchangeName
37316
38782
  * @returns Effective entry price, or null if no active position
37317
38783
  */
37318
- this.getPositionAveragePrice = async (symbol, context) => {
38784
+ this.getPositionEffectivePrice = async (symbol, context) => {
37319
38785
  bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE, {
37320
38786
  symbol,
37321
38787
  context,
@@ -37331,7 +38797,7 @@ class LiveUtils {
37331
38797
  actions &&
37332
38798
  actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_AVERAGE_PRICE));
37333
38799
  }
37334
- return await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
38800
+ return await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
37335
38801
  strategyName: context.strategyName,
37336
38802
  exchangeName: context.exchangeName,
37337
38803
  frameName: "",
@@ -37581,6 +39047,262 @@ class LiveUtils {
37581
39047
  frameName: "",
37582
39048
  });
37583
39049
  };
39050
+ /**
39051
+ * Returns the original estimated duration for the current pending signal.
39052
+ *
39053
+ * Reflects `minuteEstimatedTime` as set in the signal DTO — the maximum
39054
+ * number of minutes the position is expected to be active before `time_expired`.
39055
+ *
39056
+ * Returns null if no pending signal exists.
39057
+ *
39058
+ * @param symbol - Trading pair symbol
39059
+ * @param context - Execution context with strategyName and exchangeName
39060
+ * @returns Estimated duration in minutes, or null if no active position
39061
+ */
39062
+ this.getPositionEstimateMinutes = async (symbol, context) => {
39063
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES, {
39064
+ symbol,
39065
+ context,
39066
+ });
39067
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39068
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39069
+ {
39070
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39071
+ riskName &&
39072
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES);
39073
+ riskList &&
39074
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39075
+ actions &&
39076
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_ESTIMATE_MINUTES));
39077
+ }
39078
+ return await bt.strategyCoreService.getPositionEstimateMinutes(false, symbol, {
39079
+ strategyName: context.strategyName,
39080
+ exchangeName: context.exchangeName,
39081
+ frameName: "",
39082
+ });
39083
+ };
39084
+ /**
39085
+ * Returns the remaining time before the position expires, clamped to zero.
39086
+ *
39087
+ * Computes elapsed minutes since `pendingAt` and subtracts from `minuteEstimatedTime`.
39088
+ * Returns 0 once the estimate is exceeded (never negative).
39089
+ *
39090
+ * Returns null if no pending signal exists.
39091
+ *
39092
+ * @param symbol - Trading pair symbol
39093
+ * @param context - Execution context with strategyName and exchangeName
39094
+ * @returns Remaining minutes (≥ 0), or null if no active position
39095
+ */
39096
+ this.getPositionCountdownMinutes = async (symbol, context) => {
39097
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES, {
39098
+ symbol,
39099
+ context,
39100
+ });
39101
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39102
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39103
+ {
39104
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39105
+ riskName &&
39106
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES);
39107
+ riskList &&
39108
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39109
+ actions &&
39110
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_COUNTDOWN_MINUTES));
39111
+ }
39112
+ return await bt.strategyCoreService.getPositionCountdownMinutes(false, symbol, {
39113
+ strategyName: context.strategyName,
39114
+ exchangeName: context.exchangeName,
39115
+ frameName: "",
39116
+ });
39117
+ };
39118
+ /**
39119
+ * Returns the best price reached in the profit direction during this position's life.
39120
+ *
39121
+ * Returns null if no pending signal exists.
39122
+ *
39123
+ * @param symbol - Trading pair symbol
39124
+ * @param context - Execution context with strategyName and exchangeName
39125
+ * @returns price or null if no active position
39126
+ */
39127
+ this.getPositionHighestProfitPrice = async (symbol, context) => {
39128
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, {
39129
+ symbol,
39130
+ context,
39131
+ });
39132
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39133
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39134
+ {
39135
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39136
+ riskName &&
39137
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
39138
+ riskList &&
39139
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39140
+ actions &&
39141
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
39142
+ }
39143
+ return await bt.strategyCoreService.getPositionHighestProfitPrice(false, symbol, {
39144
+ strategyName: context.strategyName,
39145
+ exchangeName: context.exchangeName,
39146
+ frameName: "",
39147
+ });
39148
+ };
39149
+ /**
39150
+ * Returns the timestamp when the best profit price was recorded during this position's life.
39151
+ *
39152
+ * Returns null if no pending signal exists.
39153
+ *
39154
+ * @param symbol - Trading pair symbol
39155
+ * @param context - Execution context with strategyName and exchangeName
39156
+ * @returns timestamp in milliseconds or null if no active position
39157
+ */
39158
+ this.getPositionHighestProfitTimestamp = async (symbol, context) => {
39159
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, {
39160
+ symbol,
39161
+ context,
39162
+ });
39163
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39164
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39165
+ {
39166
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39167
+ riskName &&
39168
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
39169
+ riskList &&
39170
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39171
+ actions &&
39172
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
39173
+ }
39174
+ return await bt.strategyCoreService.getPositionHighestProfitTimestamp(false, symbol, {
39175
+ strategyName: context.strategyName,
39176
+ exchangeName: context.exchangeName,
39177
+ frameName: "",
39178
+ });
39179
+ };
39180
+ /**
39181
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
39182
+ *
39183
+ * Returns null if no pending signal exists.
39184
+ *
39185
+ * @param symbol - Trading pair symbol
39186
+ * @param context - Execution context with strategyName and exchangeName
39187
+ * @returns PnL percentage or null if no active position
39188
+ */
39189
+ this.getPositionHighestPnlPercentage = async (symbol, context) => {
39190
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, {
39191
+ symbol,
39192
+ context,
39193
+ });
39194
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39195
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39196
+ {
39197
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39198
+ riskName &&
39199
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
39200
+ riskList &&
39201
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39202
+ actions &&
39203
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
39204
+ }
39205
+ return await bt.strategyCoreService.getPositionHighestPnlPercentage(false, symbol, {
39206
+ strategyName: context.strategyName,
39207
+ exchangeName: context.exchangeName,
39208
+ frameName: "",
39209
+ });
39210
+ };
39211
+ /**
39212
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
39213
+ *
39214
+ * Returns null if no pending signal exists.
39215
+ *
39216
+ * @param symbol - Trading pair symbol
39217
+ * @param context - Execution context with strategyName and exchangeName
39218
+ * @returns PnL cost or null if no active position
39219
+ */
39220
+ this.getPositionHighestPnlCost = async (symbol, context) => {
39221
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, {
39222
+ symbol,
39223
+ context,
39224
+ });
39225
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39226
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39227
+ {
39228
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39229
+ riskName &&
39230
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
39231
+ riskList &&
39232
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39233
+ actions &&
39234
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
39235
+ }
39236
+ return await bt.strategyCoreService.getPositionHighestPnlCost(false, symbol, {
39237
+ strategyName: context.strategyName,
39238
+ exchangeName: context.exchangeName,
39239
+ frameName: "",
39240
+ });
39241
+ };
39242
+ /**
39243
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
39244
+ *
39245
+ * @param symbol - Trading pair symbol
39246
+ * @param context - Execution context with strategyName and exchangeName
39247
+ * @returns true if breakeven was reachable at peak, false otherwise, or null if no active position
39248
+ */
39249
+ this.getPositionHighestProfitBreakeven = async (symbol, context) => {
39250
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, {
39251
+ symbol,
39252
+ context,
39253
+ });
39254
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39255
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39256
+ {
39257
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39258
+ riskName &&
39259
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
39260
+ riskList &&
39261
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39262
+ actions &&
39263
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
39264
+ }
39265
+ return await bt.strategyCoreService.getPositionHighestProfitBreakeven(false, symbol, {
39266
+ strategyName: context.strategyName,
39267
+ exchangeName: context.exchangeName,
39268
+ frameName: "",
39269
+ });
39270
+ };
39271
+ /**
39272
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
39273
+ *
39274
+ * Measures how long the position has been pulling back from its peak profit level.
39275
+ * Zero when called at the exact moment the peak was set.
39276
+ * Grows continuously as price moves away from the peak without setting a new record.
39277
+ *
39278
+ * Returns null if no pending signal exists.
39279
+ *
39280
+ * @param symbol - Trading pair symbol
39281
+ * @param context - Execution context with strategyName and exchangeName
39282
+ * @returns Drawdown duration in minutes, or null if no active position
39283
+ */
39284
+ this.getPositionDrawdownMinutes = async (symbol, context) => {
39285
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, {
39286
+ symbol,
39287
+ context,
39288
+ });
39289
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39290
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39291
+ {
39292
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
39293
+ riskName &&
39294
+ bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
39295
+ riskList &&
39296
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39297
+ actions &&
39298
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
39299
+ }
39300
+ return await bt.strategyCoreService.getPositionDrawdownMinutes(false, symbol, {
39301
+ strategyName: context.strategyName,
39302
+ exchangeName: context.exchangeName,
39303
+ frameName: "",
39304
+ });
39305
+ };
37584
39306
  /**
37585
39307
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
37586
39308
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38235,7 +39957,7 @@ class LiveUtils {
38235
39957
  if (!signal) {
38236
39958
  return false;
38237
39959
  }
38238
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
39960
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38239
39961
  strategyName: context.strategyName,
38240
39962
  exchangeName: context.exchangeName,
38241
39963
  frameName: "",
@@ -38335,7 +40057,7 @@ class LiveUtils {
38335
40057
  if (!signal) {
38336
40058
  return false;
38337
40059
  }
38338
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40060
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38339
40061
  strategyName: context.strategyName,
38340
40062
  exchangeName: context.exchangeName,
38341
40063
  frameName: "",
@@ -38404,7 +40126,7 @@ class LiveUtils {
38404
40126
  if (!signal) {
38405
40127
  return false;
38406
40128
  }
38407
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40129
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38408
40130
  strategyName: context.strategyName,
38409
40131
  exchangeName: context.exchangeName,
38410
40132
  frameName: "",
@@ -38474,7 +40196,7 @@ class LiveUtils {
38474
40196
  if (!signal) {
38475
40197
  return false;
38476
40198
  }
38477
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40199
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38478
40200
  strategyName: context.strategyName,
38479
40201
  exchangeName: context.exchangeName,
38480
40202
  frameName: "",
@@ -38552,7 +40274,7 @@ class LiveUtils {
38552
40274
  if (!signal) {
38553
40275
  return false;
38554
40276
  }
38555
- const effectivePriceOpen = await bt.strategyCoreService.getPositionAveragePrice(false, symbol, {
40277
+ const effectivePriceOpen = await bt.strategyCoreService.getPositionEffectivePrice(false, symbol, {
38556
40278
  strategyName: context.strategyName,
38557
40279
  exchangeName: context.exchangeName,
38558
40280
  frameName: "",
@@ -41976,6 +43698,98 @@ class PartialUtils {
41976
43698
  */
41977
43699
  const Partial = new PartialUtils();
41978
43700
 
43701
+ const HIGHEST_PROFIT_METHOD_NAME_GET_DATA = "HighestProfitUtils.getData";
43702
+ const HIGHEST_PROFIT_METHOD_NAME_GET_REPORT = "HighestProfitUtils.getReport";
43703
+ const HIGHEST_PROFIT_METHOD_NAME_DUMP = "HighestProfitUtils.dump";
43704
+ /**
43705
+ * Utility class for accessing highest profit reports and statistics.
43706
+ *
43707
+ * Provides static-like methods (via singleton instance) to retrieve data
43708
+ * accumulated by HighestProfitMarkdownService from highestProfitSubject events.
43709
+ *
43710
+ * @example
43711
+ * ```typescript
43712
+ * import { HighestProfit } from "backtest-kit";
43713
+ *
43714
+ * const stats = await HighestProfit.getData("BTCUSDT", { strategyName, exchangeName, frameName });
43715
+ * const report = await HighestProfit.getReport("BTCUSDT", { strategyName, exchangeName, frameName });
43716
+ * await HighestProfit.dump("BTCUSDT", { strategyName, exchangeName, frameName });
43717
+ * ```
43718
+ */
43719
+ class HighestProfitUtils {
43720
+ constructor() {
43721
+ /**
43722
+ * Retrieves statistical data from accumulated highest profit events.
43723
+ *
43724
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43725
+ * @param context - Execution context
43726
+ * @param backtest - Whether to query backtest data
43727
+ * @returns Promise resolving to HighestProfitStatisticsModel
43728
+ */
43729
+ this.getData = async (symbol, context, backtest = false) => {
43730
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
43731
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43732
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43733
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43734
+ {
43735
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43736
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA);
43737
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43738
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_DATA));
43739
+ }
43740
+ return await bt.highestProfitMarkdownService.getData(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
43741
+ };
43742
+ /**
43743
+ * Generates a markdown report with all highest profit events for a symbol-strategy pair.
43744
+ *
43745
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43746
+ * @param context - Execution context
43747
+ * @param backtest - Whether to query backtest data
43748
+ * @param columns - Optional column configuration
43749
+ * @returns Promise resolving to markdown formatted report string
43750
+ */
43751
+ this.getReport = async (symbol, context, backtest = false, columns) => {
43752
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
43753
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43754
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43755
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43756
+ {
43757
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43758
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT);
43759
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43760
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_GET_REPORT));
43761
+ }
43762
+ return await bt.highestProfitMarkdownService.getReport(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, columns);
43763
+ };
43764
+ /**
43765
+ * Generates and saves a markdown report to file.
43766
+ *
43767
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
43768
+ * @param context - Execution context
43769
+ * @param backtest - Whether to query backtest data
43770
+ * @param path - Output directory path (default: "./dump/highest_profit")
43771
+ * @param columns - Optional column configuration
43772
+ */
43773
+ this.dump = async (symbol, context, backtest = false, path, columns) => {
43774
+ bt.loggerService.info(HIGHEST_PROFIT_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
43775
+ bt.strategyValidationService.validate(context.strategyName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43776
+ bt.exchangeValidationService.validate(context.exchangeName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43777
+ context.frameName && bt.frameValidationService.validate(context.frameName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43778
+ {
43779
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
43780
+ riskName && bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP);
43781
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43782
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, HIGHEST_PROFIT_METHOD_NAME_DUMP));
43783
+ }
43784
+ await bt.highestProfitMarkdownService.dump(symbol, context.strategyName, context.exchangeName, context.frameName, backtest, path, columns);
43785
+ };
43786
+ }
43787
+ }
43788
+ /**
43789
+ * Global singleton instance of HighestProfitUtils.
43790
+ */
43791
+ const HighestProfit = new HighestProfitUtils();
43792
+
41979
43793
  /**
41980
43794
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
41981
43795
  *
@@ -46453,6 +48267,7 @@ exports.Constant = Constant;
46453
48267
  exports.Exchange = Exchange;
46454
48268
  exports.ExecutionContextService = ExecutionContextService;
46455
48269
  exports.Heat = Heat;
48270
+ exports.HighestProfit = HighestProfit;
46456
48271
  exports.Live = Live;
46457
48272
  exports.Log = Log;
46458
48273
  exports.Markdown = Markdown;
@@ -46532,8 +48347,17 @@ exports.getMode = getMode;
46532
48347
  exports.getNextCandles = getNextCandles;
46533
48348
  exports.getOrderBook = getOrderBook;
46534
48349
  exports.getPendingSignal = getPendingSignal;
46535
- exports.getPositionAveragePrice = getPositionAveragePrice;
48350
+ exports.getPositionCountdownMinutes = getPositionCountdownMinutes;
48351
+ exports.getPositionDrawdownMinutes = getPositionDrawdownMinutes;
48352
+ exports.getPositionEffectivePrice = getPositionEffectivePrice;
48353
+ exports.getPositionEntries = getPositionEntries;
46536
48354
  exports.getPositionEntryOverlap = getPositionEntryOverlap;
48355
+ exports.getPositionEstimateMinutes = getPositionEstimateMinutes;
48356
+ exports.getPositionHighestPnlCost = getPositionHighestPnlCost;
48357
+ exports.getPositionHighestPnlPercentage = getPositionHighestPnlPercentage;
48358
+ exports.getPositionHighestProfitBreakeven = getPositionHighestProfitBreakeven;
48359
+ exports.getPositionHighestProfitPrice = getPositionHighestProfitPrice;
48360
+ exports.getPositionHighestProfitTimestamp = getPositionHighestProfitTimestamp;
46537
48361
  exports.getPositionInvestedCost = getPositionInvestedCost;
46538
48362
  exports.getPositionInvestedCount = getPositionInvestedCount;
46539
48363
  exports.getPositionLevels = getPositionLevels;
@@ -46574,6 +48398,8 @@ exports.listenDoneWalker = listenDoneWalker;
46574
48398
  exports.listenDoneWalkerOnce = listenDoneWalkerOnce;
46575
48399
  exports.listenError = listenError;
46576
48400
  exports.listenExit = listenExit;
48401
+ exports.listenHighestProfit = listenHighestProfit;
48402
+ exports.listenHighestProfitOnce = listenHighestProfitOnce;
46577
48403
  exports.listenPartialLossAvailable = listenPartialLossAvailable;
46578
48404
  exports.listenPartialLossAvailableOnce = listenPartialLossAvailableOnce;
46579
48405
  exports.listenPartialProfitAvailable = listenPartialProfitAvailable;