backtest-kit 6.11.0 → 6.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/build/index.cjs +2081 -183
  2. package/build/index.mjs +2073 -184
  3. package/package.json +2 -2
  4. package/types.d.ts +1136 -85
package/build/index.cjs CHANGED
@@ -1002,6 +1002,12 @@ const PERSIST_MEMORY_UTILS_METHOD_NAME_LIST_DATA = "PersistMemoryUtils.listMemor
1002
1002
  const PERSIST_MEMORY_UTILS_METHOD_NAME_HAS_DATA = "PersistMemoryUtils.hasMemoryData";
1003
1003
  const PERSIST_MEMORY_UTILS_METHOD_NAME_CLEAR = "PersistMemoryUtils.clear";
1004
1004
  const PERSIST_MEMORY_UTILS_METHOD_NAME_DISPOSE = "PersistMemoryUtils.dispose";
1005
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER = "PersistRecentUtils.usePersistRecentAdapter";
1006
+ const PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA = "PersistRecentUtils.readRecentData";
1007
+ const PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA = "PersistRecentUtils.writeRecentData";
1008
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON = "PersistRecentUtils.useJson";
1009
+ const PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY = "PersistRecentUtils.useDummy";
1010
+ const PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR = "PersistRecentUtils.clear";
1005
1011
  const BASE_WAIT_FOR_INIT_FN_METHOD_NAME = "PersistBase.waitForInitFn";
1006
1012
  const BASE_UNLINK_RETRY_COUNT = 5;
1007
1013
  const BASE_UNLINK_RETRY_DELAY = 1000;
@@ -2848,6 +2854,115 @@ class PersistMemoryUtils {
2848
2854
  * ```
2849
2855
  */
2850
2856
  const PersistMemoryAdapter = new PersistMemoryUtils();
2857
+ /**
2858
+ * Utility class for managing recent signal persistence.
2859
+ *
2860
+ * Features:
2861
+ * - Memoized storage instances per (symbol, strategyName, exchangeName, frameName) context
2862
+ * - Custom adapter support
2863
+ * - Atomic read/write operations
2864
+ * - Crash-safe recent signal state management
2865
+ *
2866
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2867
+ */
2868
+ class PersistRecentUtils {
2869
+ constructor() {
2870
+ this.PersistRecentFactory = PersistBase;
2871
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":"), (symbol, strategyName, exchangeName, frameName, backtest) => Reflect.construct(this.PersistRecentFactory, [
2872
+ this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join("_"),
2873
+ `./dump/data/recent/`,
2874
+ ]));
2875
+ /**
2876
+ * Reads the latest persisted recent signal for a given context.
2877
+ *
2878
+ * Returns null if no recent signal exists.
2879
+ *
2880
+ * @param symbol - Trading pair symbol
2881
+ * @param strategyName - Strategy identifier
2882
+ * @param exchangeName - Exchange identifier
2883
+ * @param frameName - Frame identifier
2884
+ * @returns Promise resolving to recent signal or null
2885
+ */
2886
+ this.readRecentData = async (symbol, strategyName, exchangeName, frameName, backtest) => {
2887
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_READ_DATA);
2888
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2889
+ const isInitial = !this.getStorage.has(key);
2890
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2891
+ await stateStorage.waitForInit(isInitial);
2892
+ if (await stateStorage.hasValue(symbol)) {
2893
+ return await stateStorage.readValue(symbol);
2894
+ }
2895
+ return null;
2896
+ };
2897
+ /**
2898
+ * Writes the latest recent signal to disk with atomic file writes.
2899
+ *
2900
+ * Uses symbol as the entity ID within the per-context storage instance.
2901
+ * Uses atomic writes to prevent corruption on crashes.
2902
+ *
2903
+ * @param signalRow - Recent signal data to persist
2904
+ * @param symbol - Trading pair symbol
2905
+ * @param strategyName - Strategy identifier
2906
+ * @param exchangeName - Exchange identifier
2907
+ * @param frameName - Frame identifier
2908
+ * @returns Promise that resolves when write is complete
2909
+ */
2910
+ this.writeRecentData = async (signalRow, symbol, strategyName, exchangeName, frameName, backtest) => {
2911
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_WRITE_DATA);
2912
+ const key = this.createKeyParts(symbol, strategyName, exchangeName, frameName, backtest).join(":");
2913
+ const isInitial = !this.getStorage.has(key);
2914
+ const stateStorage = this.getStorage(symbol, strategyName, exchangeName, frameName, backtest);
2915
+ await stateStorage.waitForInit(isInitial);
2916
+ await stateStorage.writeValue(symbol, signalRow);
2917
+ };
2918
+ }
2919
+ createKeyParts(symbol, strategyName, exchangeName, frameName, backtest) {
2920
+ const parts = [symbol, strategyName, exchangeName];
2921
+ if (frameName)
2922
+ parts.push(frameName);
2923
+ parts.push(backtest ? "backtest" : "live");
2924
+ return parts;
2925
+ }
2926
+ /**
2927
+ * Registers a custom persistence adapter.
2928
+ *
2929
+ * @param Ctor - Custom PersistBase constructor
2930
+ */
2931
+ usePersistRecentAdapter(Ctor) {
2932
+ LOGGER_SERVICE$7.info(PERSIST_RECENT_UTILS_METHOD_NAME_USE_PERSIST_RECENT_ADAPTER);
2933
+ this.PersistRecentFactory = Ctor;
2934
+ }
2935
+ /**
2936
+ * Clears the memoized storage cache.
2937
+ * Call this when process.cwd() changes between strategy iterations
2938
+ * so new storage instances are created with the updated base path.
2939
+ */
2940
+ clear() {
2941
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_CLEAR);
2942
+ this.getStorage.clear();
2943
+ }
2944
+ /**
2945
+ * Switches to the default JSON persist adapter.
2946
+ * All future persistence writes will use JSON storage.
2947
+ */
2948
+ useJson() {
2949
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_JSON);
2950
+ this.usePersistRecentAdapter(PersistBase);
2951
+ }
2952
+ /**
2953
+ * Switches to a dummy persist adapter that discards all writes.
2954
+ * All future persistence writes will be no-ops.
2955
+ */
2956
+ useDummy() {
2957
+ LOGGER_SERVICE$7.log(PERSIST_RECENT_UTILS_METHOD_NAME_USE_DUMMY);
2958
+ this.usePersistRecentAdapter(PersistDummy);
2959
+ }
2960
+ }
2961
+ /**
2962
+ * Global singleton instance of PersistRecentUtils.
2963
+ * Used by RecentPersistBacktestUtils/RecentPersistLiveUtils for recent signal persistence.
2964
+ */
2965
+ const PersistRecentAdapter = new PersistRecentUtils();
2851
2966
 
2852
2967
  var _a$2, _b$2;
2853
2968
  const BUSY_DELAY = 100;
@@ -6593,7 +6708,7 @@ const RETURN_IDLE_FN = async (self, currentPrice) => {
6593
6708
  await CALL_TICK_CALLBACKS_FN(self, self.params.execution.context.symbol, result, currentTime, self.params.execution.context.backtest);
6594
6709
  return result;
6595
6710
  };
6596
- const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId) => {
6711
+ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePrice, closeTimestamp, reason, cancelId, cancelNote) => {
6597
6712
  self.params.logger.info("ClientStrategy backtest scheduled signal cancelled", {
6598
6713
  symbol: self.params.execution.context.symbol,
6599
6714
  signalId: scheduled.id,
@@ -6618,7 +6733,7 @@ const CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, averagePr
6618
6733
  totalPartials: scheduled._partial?.length ?? 0,
6619
6734
  originalPriceOpen: scheduled.priceOpen,
6620
6735
  pnl: toProfitLossDto(scheduled, averagePrice),
6621
- note: scheduled.note,
6736
+ note: cancelNote ?? scheduled.note,
6622
6737
  });
6623
6738
  }
6624
6739
  await CALL_CANCEL_CALLBACKS_FN(self, self.params.execution.context.symbol, scheduled, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6785,7 +6900,7 @@ const CLOSE_USER_PENDING_SIGNAL_IN_BACKTEST_FN = async (self, closedSignal, aver
6785
6900
  totalPartials: closedSignal._partial?.length ?? 0,
6786
6901
  originalPriceOpen: closedSignal.priceOpen,
6787
6902
  pnl: toProfitLossDto(closedSignal, averagePrice),
6788
- note: closedSignal.note,
6903
+ note: closedSignal.closeNote ?? closedSignal.note,
6789
6904
  });
6790
6905
  await CALL_CLOSE_CALLBACKS_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
6791
6906
  await CALL_PARTIAL_CLEAR_FN(self, self.params.execution.context.symbol, closedSignal, averagePrice, closeTimestamp, self.params.execution.context.backtest);
@@ -6832,7 +6947,8 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6832
6947
  if (self._cancelledSignal) {
6833
6948
  // Сигнал был отменен через cancel() в onSchedulePing
6834
6949
  const cancelId = self._cancelledSignal.cancelId;
6835
- const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId);
6950
+ const cancelNote = self._cancelledSignal.cancelNote;
6951
+ const result = await CANCEL_SCHEDULED_SIGNAL_IN_BACKTEST_FN(self, scheduled, averagePrice, candle.timestamp, "user", cancelId, cancelNote);
6836
6952
  return { outcome: "cancelled", result };
6837
6953
  }
6838
6954
  // КРИТИЧНО: Проверяем был ли сигнал активирован пользователем через activateScheduled()
@@ -6886,7 +7002,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6886
7002
  totalPartials: activatedSignal._partial?.length ?? 0,
6887
7003
  originalPriceOpen: activatedSignal.priceOpen,
6888
7004
  pnl: toProfitLossDto(activatedSignal, averagePrice),
6889
- note: activatedSignal.note,
7005
+ note: activatedSignal.activateNote ?? activatedSignal.note,
6890
7006
  });
6891
7007
  return { outcome: "pending" };
6892
7008
  }
@@ -6918,7 +7034,7 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles, fra
6918
7034
  pendingAt: publicSignalForCommit.pendingAt,
6919
7035
  totalEntries: publicSignalForCommit.totalEntries,
6920
7036
  totalPartials: publicSignalForCommit.totalPartials,
6921
- note: publicSignalForCommit.note,
7037
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
6922
7038
  });
6923
7039
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
6924
7040
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -8039,6 +8155,44 @@ class ClientStrategy {
8039
8155
  const currentPnl = toProfitLossDto(this._pendingSignal, currentPrice);
8040
8156
  return Math.max(0, currentPnl.pnlCost - this._pendingSignal._fall.pnlCost);
8041
8157
  }
8158
+ /**
8159
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
8160
+ *
8161
+ * Measures the total swing from the stored `_peak.pnlPercentage` to the stored `_fall.pnlPercentage`.
8162
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
8163
+ *
8164
+ * Returns null if no pending signal exists.
8165
+ *
8166
+ * @param symbol - Trading pair symbol
8167
+ * @param currentPrice - Current market price
8168
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
8169
+ */
8170
+ async getMaxDrawdownDistancePnlPercentage(symbol, currentPrice) {
8171
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlPercentage", { symbol, currentPrice });
8172
+ if (!this._pendingSignal) {
8173
+ return null;
8174
+ }
8175
+ return Math.max(0, this._pendingSignal._peak.pnlPercentage - this._pendingSignal._fall.pnlPercentage);
8176
+ }
8177
+ /**
8178
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
8179
+ *
8180
+ * Measures the total swing from the stored `_peak.pnlCost` to the stored `_fall.pnlCost`.
8181
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
8182
+ *
8183
+ * Returns null if no pending signal exists.
8184
+ *
8185
+ * @param symbol - Trading pair symbol
8186
+ * @param currentPrice - Current market price
8187
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
8188
+ */
8189
+ async getMaxDrawdownDistancePnlCost(symbol, currentPrice) {
8190
+ this.params.logger.debug("ClientStrategy getMaxDrawdownDistancePnlCost", { symbol, currentPrice });
8191
+ if (!this._pendingSignal) {
8192
+ return null;
8193
+ }
8194
+ return Math.max(0, this._pendingSignal._peak.pnlCost - this._pendingSignal._fall.pnlCost);
8195
+ }
8042
8196
  /**
8043
8197
  * Performs a single tick of strategy execution.
8044
8198
  *
@@ -8103,7 +8257,7 @@ class ClientStrategy {
8103
8257
  totalPartials: cancelledSignal._partial?.length ?? 0,
8104
8258
  originalPriceOpen: cancelledSignal.priceOpen,
8105
8259
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8106
- note: cancelledSignal.note,
8260
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8107
8261
  });
8108
8262
  // Call onCancel callback
8109
8263
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8157,7 +8311,7 @@ class ClientStrategy {
8157
8311
  totalPartials: closedSignal._partial?.length ?? 0,
8158
8312
  originalPriceOpen: closedSignal.priceOpen,
8159
8313
  pnl: toProfitLossDto(closedSignal, currentPrice),
8160
- note: closedSignal.note,
8314
+ note: closedSignal.closeNote ?? closedSignal.note,
8161
8315
  });
8162
8316
  // Call onClose callback
8163
8317
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8239,7 +8393,7 @@ class ClientStrategy {
8239
8393
  totalPartials: activatedSignal._partial?.length ?? 0,
8240
8394
  originalPriceOpen: activatedSignal.priceOpen,
8241
8395
  pnl: toProfitLossDto(activatedSignal, currentPrice),
8242
- note: activatedSignal.note,
8396
+ note: activatedSignal.activateNote ?? activatedSignal.note,
8243
8397
  });
8244
8398
  return await RETURN_IDLE_FN(this, currentPrice);
8245
8399
  }
@@ -8270,7 +8424,7 @@ class ClientStrategy {
8270
8424
  pendingAt: publicSignalForCommit.pendingAt,
8271
8425
  totalEntries: publicSignalForCommit.totalEntries,
8272
8426
  totalPartials: publicSignalForCommit.totalPartials,
8273
- note: publicSignalForCommit.note,
8427
+ note: activatedSignal.activateNote ?? publicSignalForCommit.note,
8274
8428
  });
8275
8429
  // Call onOpen callback
8276
8430
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -8406,7 +8560,7 @@ class ClientStrategy {
8406
8560
  totalPartials: cancelledSignal._partial?.length ?? 0,
8407
8561
  originalPriceOpen: cancelledSignal.priceOpen,
8408
8562
  pnl: toProfitLossDto(cancelledSignal, currentPrice),
8409
- note: cancelledSignal.note,
8563
+ note: cancelledSignal.cancelNote ?? cancelledSignal.note,
8410
8564
  });
8411
8565
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8412
8566
  const cancelledResult = {
@@ -8461,7 +8615,7 @@ class ClientStrategy {
8461
8615
  totalPartials: closedSignal._partial?.length ?? 0,
8462
8616
  originalPriceOpen: closedSignal.priceOpen,
8463
8617
  pnl: toProfitLossDto(closedSignal, currentPrice),
8464
- note: closedSignal.note,
8618
+ note: closedSignal.closeNote ?? closedSignal.note,
8465
8619
  });
8466
8620
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
8467
8621
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -8645,7 +8799,8 @@ class ClientStrategy {
8645
8799
  * // Strategy continues, can generate new signals
8646
8800
  * ```
8647
8801
  */
8648
- async cancelScheduled(symbol, backtest, cancelId) {
8802
+ async cancelScheduled(symbol, backtest, payload) {
8803
+ const cancelId = payload.id;
8649
8804
  this.params.logger.debug("ClientStrategy cancelScheduled", {
8650
8805
  symbol,
8651
8806
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8657,6 +8812,7 @@ class ClientStrategy {
8657
8812
  if (this._scheduledSignal) {
8658
8813
  this._cancelledSignal = Object.assign({}, this._scheduledSignal, {
8659
8814
  cancelId,
8815
+ cancelNote: payload.note,
8660
8816
  });
8661
8817
  this._scheduledSignal = null;
8662
8818
  }
@@ -8688,7 +8844,8 @@ class ClientStrategy {
8688
8844
  * // Scheduled signal becomes pending signal immediately
8689
8845
  * ```
8690
8846
  */
8691
- async activateScheduled(symbol, backtest, activateId) {
8847
+ async activateScheduled(symbol, backtest, payload) {
8848
+ const activateId = payload.id;
8692
8849
  this.params.logger.debug("ClientStrategy activateScheduled", {
8693
8850
  symbol,
8694
8851
  hasScheduledSignal: this._scheduledSignal !== null,
@@ -8706,6 +8863,7 @@ class ClientStrategy {
8706
8863
  if (this._scheduledSignal) {
8707
8864
  this._activatedSignal = Object.assign({}, this._scheduledSignal, {
8708
8865
  activateId,
8866
+ activateNote: payload.note,
8709
8867
  });
8710
8868
  this._scheduledSignal = null;
8711
8869
  }
@@ -8737,7 +8895,8 @@ class ClientStrategy {
8737
8895
  * // Strategy continues, can generate new signals
8738
8896
  * ```
8739
8897
  */
8740
- async closePending(symbol, backtest, closeId) {
8898
+ async closePending(symbol, backtest, payload) {
8899
+ const closeId = payload.id;
8741
8900
  this.params.logger.debug("ClientStrategy closePending", {
8742
8901
  symbol,
8743
8902
  hasPendingSignal: this._pendingSignal !== null,
@@ -8748,6 +8907,7 @@ class ClientStrategy {
8748
8907
  if (this._pendingSignal) {
8749
8908
  this._closedSignal = Object.assign({}, this._pendingSignal, {
8750
8909
  closeId,
8910
+ closeNote: payload.note,
8751
8911
  });
8752
8912
  this._pendingSignal = null;
8753
8913
  }
@@ -10130,7 +10290,7 @@ const GET_RISK_FN = (dto, backtest, exchangeName, frameName, self) => {
10130
10290
  * @param backtest - Whether running in backtest mode
10131
10291
  * @returns Unique string key for memoization
10132
10292
  */
10133
- const CREATE_KEY_FN$t = (symbol, strategyName, exchangeName, frameName, backtest) => {
10293
+ const CREATE_KEY_FN$u = (symbol, strategyName, exchangeName, frameName, backtest) => {
10134
10294
  const parts = [symbol, strategyName, exchangeName];
10135
10295
  if (frameName)
10136
10296
  parts.push(frameName);
@@ -10397,7 +10557,7 @@ class StrategyConnectionService {
10397
10557
  * @param backtest - Whether running in backtest mode
10398
10558
  * @returns Configured ClientStrategy instance
10399
10559
  */
10400
- this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10560
+ this.getStrategy = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$u(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => {
10401
10561
  const { riskName = "", riskList = [], getSignal, interval = STRATEGY_DEFAULT_INTERVAL, callbacks, } = this.strategySchemaService.get(strategyName);
10402
10562
  return new ClientStrategy({
10403
10563
  symbol,
@@ -11238,6 +11398,48 @@ class StrategyConnectionService {
11238
11398
  const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11239
11399
  return await strategy.getPositionHighestMaxDrawdownPnlCost(symbol, currentPrice);
11240
11400
  };
11401
+ /**
11402
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
11403
+ *
11404
+ * Resolves current price via priceMetaService and delegates to
11405
+ * ClientStrategy.getMaxDrawdownDistancePnlPercentage().
11406
+ * Returns null if no pending signal exists.
11407
+ *
11408
+ * @param backtest - Whether running in backtest mode
11409
+ * @param symbol - Trading pair symbol
11410
+ * @param context - Execution context with strategyName, exchangeName, frameName
11411
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
11412
+ */
11413
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
11414
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlPercentage", {
11415
+ symbol,
11416
+ context,
11417
+ });
11418
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11419
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11420
+ return await strategy.getMaxDrawdownDistancePnlPercentage(symbol, currentPrice);
11421
+ };
11422
+ /**
11423
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
11424
+ *
11425
+ * Resolves current price via priceMetaService and delegates to
11426
+ * ClientStrategy.getMaxDrawdownDistancePnlCost().
11427
+ * Returns null if no pending signal exists.
11428
+ *
11429
+ * @param backtest - Whether running in backtest mode
11430
+ * @param symbol - Trading pair symbol
11431
+ * @param context - Execution context with strategyName, exchangeName, frameName
11432
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
11433
+ */
11434
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
11435
+ this.loggerService.log("strategyConnectionService getMaxDrawdownDistancePnlCost", {
11436
+ symbol,
11437
+ context,
11438
+ });
11439
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11440
+ const currentPrice = await this.priceMetaService.getCurrentPrice(symbol, context, backtest);
11441
+ return await strategy.getMaxDrawdownDistancePnlCost(symbol, currentPrice);
11442
+ };
11241
11443
  /**
11242
11444
  * Disposes the ClientStrategy instance for the given context.
11243
11445
  *
@@ -11276,7 +11478,7 @@ class StrategyConnectionService {
11276
11478
  }
11277
11479
  return;
11278
11480
  }
11279
- const key = CREATE_KEY_FN$t(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11481
+ const key = CREATE_KEY_FN$u(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
11280
11482
  if (!this.getStrategy.has(key)) {
11281
11483
  return;
11282
11484
  }
@@ -11297,17 +11499,17 @@ class StrategyConnectionService {
11297
11499
  * @param backtest - Whether running in backtest mode
11298
11500
  * @param symbol - Trading pair symbol
11299
11501
  * @param ctx - Context with strategyName, exchangeName, frameName
11300
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
11502
+ * @param payload - Optional commit payload with id and note
11301
11503
  * @returns Promise that resolves when scheduled signal is cancelled
11302
11504
  */
11303
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
11505
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
11304
11506
  this.loggerService.log("strategyConnectionService cancelScheduled", {
11305
11507
  symbol,
11306
11508
  context,
11307
- cancelId,
11509
+ payload,
11308
11510
  });
11309
11511
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11310
- await strategy.cancelScheduled(symbol, backtest, cancelId);
11512
+ await strategy.cancelScheduled(symbol, backtest, payload);
11311
11513
  };
11312
11514
  /**
11313
11515
  * Closes the pending signal without stopping the strategy.
@@ -11322,17 +11524,17 @@ class StrategyConnectionService {
11322
11524
  * @param backtest - Whether running in backtest mode
11323
11525
  * @param symbol - Trading pair symbol
11324
11526
  * @param context - Context with strategyName, exchangeName, frameName
11325
- * @param closeId - Optional close ID for user-initiated closes
11527
+ * @param payload - Optional commit payload with id and note
11326
11528
  * @returns Promise that resolves when pending signal is closed
11327
11529
  */
11328
- this.closePending = async (backtest, symbol, context, closeId) => {
11530
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
11329
11531
  this.loggerService.log("strategyConnectionService closePending", {
11330
11532
  symbol,
11331
11533
  context,
11332
- closeId,
11534
+ payload,
11333
11535
  });
11334
11536
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11335
- await strategy.closePending(symbol, backtest, closeId);
11537
+ await strategy.closePending(symbol, backtest, payload);
11336
11538
  };
11337
11539
  /**
11338
11540
  * Checks whether `partialProfit` would succeed without executing it.
@@ -11611,7 +11813,7 @@ class StrategyConnectionService {
11611
11813
  * @param backtest - Whether running in backtest mode
11612
11814
  * @param symbol - Trading pair symbol
11613
11815
  * @param context - Execution context with strategyName, exchangeName, frameName
11614
- * @param activateId - Optional identifier for the activation reason
11816
+ * @param payload - Optional commit payload with id and note
11615
11817
  * @returns Promise that resolves when activation flag is set
11616
11818
  *
11617
11819
  * @example
@@ -11621,19 +11823,19 @@ class StrategyConnectionService {
11621
11823
  * false,
11622
11824
  * "BTCUSDT",
11623
11825
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
11624
- * "manual-activation"
11826
+ * { id: "manual-activation" }
11625
11827
  * );
11626
11828
  * ```
11627
11829
  */
11628
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
11830
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
11629
11831
  this.loggerService.log("strategyConnectionService activateScheduled", {
11630
11832
  symbol,
11631
11833
  context,
11632
11834
  backtest,
11633
- activateId,
11835
+ payload,
11634
11836
  });
11635
11837
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
11636
- return await strategy.activateScheduled(symbol, backtest, activateId);
11838
+ return await strategy.activateScheduled(symbol, backtest, payload);
11637
11839
  };
11638
11840
  /**
11639
11841
  * Checks whether `averageBuy` would succeed without executing it.
@@ -12445,7 +12647,7 @@ class ClientRisk {
12445
12647
  * @param backtest - Whether running in backtest mode
12446
12648
  * @returns Unique string key for memoization
12447
12649
  */
12448
- const CREATE_KEY_FN$s = (riskName, exchangeName, frameName, backtest) => {
12650
+ const CREATE_KEY_FN$t = (riskName, exchangeName, frameName, backtest) => {
12449
12651
  const parts = [riskName, exchangeName];
12450
12652
  if (frameName)
12451
12653
  parts.push(frameName);
@@ -12545,7 +12747,7 @@ class RiskConnectionService {
12545
12747
  * @param backtest - True if backtest mode, false if live mode
12546
12748
  * @returns Configured ClientRisk instance
12547
12749
  */
12548
- this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12750
+ this.getRisk = functoolsKit.memoize(([riskName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$t(riskName, exchangeName, frameName, backtest), (riskName, exchangeName, frameName, backtest) => {
12549
12751
  const schema = this.riskSchemaService.get(riskName);
12550
12752
  return new ClientRisk({
12551
12753
  ...schema,
@@ -12614,7 +12816,7 @@ class RiskConnectionService {
12614
12816
  payload,
12615
12817
  });
12616
12818
  if (payload) {
12617
- const key = CREATE_KEY_FN$s(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12819
+ const key = CREATE_KEY_FN$t(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest);
12618
12820
  this.getRisk.clear(key);
12619
12821
  }
12620
12822
  else {
@@ -13658,7 +13860,7 @@ class ClientAction {
13658
13860
  * @param backtest - Whether running in backtest mode
13659
13861
  * @returns Unique string key for memoization
13660
13862
  */
13661
- const CREATE_KEY_FN$r = (actionName, strategyName, exchangeName, frameName, backtest) => {
13863
+ const CREATE_KEY_FN$s = (actionName, strategyName, exchangeName, frameName, backtest) => {
13662
13864
  const parts = [actionName, strategyName, exchangeName];
13663
13865
  if (frameName)
13664
13866
  parts.push(frameName);
@@ -13710,7 +13912,7 @@ class ActionConnectionService {
13710
13912
  * @param backtest - True if backtest mode, false if live mode
13711
13913
  * @returns Configured ClientAction instance
13712
13914
  */
13713
- this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$r(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13915
+ this.getAction = functoolsKit.memoize(([actionName, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$s(actionName, strategyName, exchangeName, frameName, backtest), (actionName, strategyName, exchangeName, frameName, backtest) => {
13714
13916
  const schema = this.actionSchemaService.get(actionName);
13715
13917
  return new ClientAction({
13716
13918
  ...schema,
@@ -13921,7 +14123,7 @@ class ActionConnectionService {
13921
14123
  await Promise.all(actions.map(async (action) => await action.dispose()));
13922
14124
  return;
13923
14125
  }
13924
- const key = CREATE_KEY_FN$r(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
14126
+ const key = CREATE_KEY_FN$s(payload.actionName, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
13925
14127
  if (!this.getAction.has(key)) {
13926
14128
  return;
13927
14129
  }
@@ -13939,7 +14141,7 @@ const METHOD_NAME_VALIDATE$2 = "exchangeCoreService validate";
13939
14141
  * @param exchangeName - Exchange name
13940
14142
  * @returns Unique string key for memoization
13941
14143
  */
13942
- const CREATE_KEY_FN$q = (exchangeName) => {
14144
+ const CREATE_KEY_FN$r = (exchangeName) => {
13943
14145
  return exchangeName;
13944
14146
  };
13945
14147
  /**
@@ -13963,7 +14165,7 @@ class ExchangeCoreService {
13963
14165
  * @param exchangeName - Name of the exchange to validate
13964
14166
  * @returns Promise that resolves when validation is complete
13965
14167
  */
13966
- this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$q(exchangeName), async (exchangeName) => {
14168
+ this.validate = functoolsKit.memoize(([exchangeName]) => CREATE_KEY_FN$r(exchangeName), async (exchangeName) => {
13967
14169
  this.loggerService.log(METHOD_NAME_VALIDATE$2, {
13968
14170
  exchangeName,
13969
14171
  });
@@ -14215,7 +14417,7 @@ const METHOD_NAME_VALIDATE$1 = "strategyCoreService validate";
14215
14417
  * @param context - Execution context with strategyName, exchangeName, frameName
14216
14418
  * @returns Unique string key for memoization
14217
14419
  */
14218
- const CREATE_KEY_FN$p = (context) => {
14420
+ const CREATE_KEY_FN$q = (context) => {
14219
14421
  const parts = [context.strategyName, context.exchangeName];
14220
14422
  if (context.frameName)
14221
14423
  parts.push(context.frameName);
@@ -14247,7 +14449,7 @@ class StrategyCoreService {
14247
14449
  * @param context - Execution context with strategyName, exchangeName, frameName
14248
14450
  * @returns Promise that resolves when validation is complete
14249
14451
  */
14250
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
14452
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$q(context), async (context) => {
14251
14453
  this.loggerService.log(METHOD_NAME_VALIDATE$1, {
14252
14454
  context,
14253
14455
  });
@@ -14668,18 +14870,18 @@ class StrategyCoreService {
14668
14870
  * @param backtest - Whether running in backtest mode
14669
14871
  * @param symbol - Trading pair symbol
14670
14872
  * @param ctx - Context with strategyName, exchangeName, frameName
14671
- * @param cancelId - Optional cancellation ID for user-initiated cancellations
14873
+ * @param payload - Optional commit payload with id and note
14672
14874
  * @returns Promise that resolves when scheduled signal is cancelled
14673
14875
  */
14674
- this.cancelScheduled = async (backtest, symbol, context, cancelId) => {
14876
+ this.cancelScheduled = async (backtest, symbol, context, payload = {}) => {
14675
14877
  this.loggerService.log("strategyCoreService cancelScheduled", {
14676
14878
  symbol,
14677
14879
  context,
14678
14880
  backtest,
14679
- cancelId,
14881
+ payload,
14680
14882
  });
14681
14883
  await this.validate(context);
14682
- return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, cancelId);
14884
+ return await this.strategyConnectionService.cancelScheduled(backtest, symbol, context, payload);
14683
14885
  };
14684
14886
  /**
14685
14887
  * Closes the pending signal without stopping the strategy.
@@ -14695,18 +14897,18 @@ class StrategyCoreService {
14695
14897
  * @param backtest - Whether running in backtest mode
14696
14898
  * @param symbol - Trading pair symbol
14697
14899
  * @param context - Context with strategyName, exchangeName, frameName
14698
- * @param closeId - Optional close ID for user-initiated closes
14900
+ * @param payload - Optional commit payload with id and note
14699
14901
  * @returns Promise that resolves when pending signal is closed
14700
14902
  */
14701
- this.closePending = async (backtest, symbol, context, closeId) => {
14903
+ this.closePending = async (backtest, symbol, context, payload = {}) => {
14702
14904
  this.loggerService.log("strategyCoreService closePending", {
14703
14905
  symbol,
14704
14906
  context,
14705
14907
  backtest,
14706
- closeId,
14908
+ payload,
14707
14909
  });
14708
14910
  await this.validate(context);
14709
- return await this.strategyConnectionService.closePending(backtest, symbol, context, closeId);
14911
+ return await this.strategyConnectionService.closePending(backtest, symbol, context, payload);
14710
14912
  };
14711
14913
  /**
14712
14914
  * Disposes the ClientStrategy instance for the given context.
@@ -15051,7 +15253,7 @@ class StrategyCoreService {
15051
15253
  * @param backtest - Whether running in backtest mode
15052
15254
  * @param symbol - Trading pair symbol
15053
15255
  * @param context - Execution context with strategyName, exchangeName, frameName
15054
- * @param activateId - Optional identifier for the activation reason
15256
+ * @param payload - Optional commit payload with id and note
15055
15257
  * @returns Promise that resolves when activation flag is set
15056
15258
  *
15057
15259
  * @example
@@ -15061,19 +15263,19 @@ class StrategyCoreService {
15061
15263
  * false,
15062
15264
  * "BTCUSDT",
15063
15265
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" },
15064
- * "manual-activation"
15266
+ * { id: "manual-activation" }
15065
15267
  * );
15066
15268
  * ```
15067
15269
  */
15068
- this.activateScheduled = async (backtest, symbol, context, activateId) => {
15270
+ this.activateScheduled = async (backtest, symbol, context, payload = {}) => {
15069
15271
  this.loggerService.log("strategyCoreService activateScheduled", {
15070
15272
  symbol,
15071
15273
  context,
15072
15274
  backtest,
15073
- activateId,
15275
+ payload,
15074
15276
  });
15075
15277
  await this.validate(context);
15076
- return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
15278
+ return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, payload);
15077
15279
  };
15078
15280
  /**
15079
15281
  * Checks whether `averageBuy` would succeed without executing it.
@@ -15475,6 +15677,44 @@ class StrategyCoreService {
15475
15677
  await this.validate(context);
15476
15678
  return await this.strategyConnectionService.getPositionHighestMaxDrawdownPnlCost(backtest, symbol, context);
15477
15679
  };
15680
+ /**
15681
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
15682
+ *
15683
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlPercentage().
15684
+ * Returns null if no pending signal exists.
15685
+ *
15686
+ * @param backtest - Whether running in backtest mode
15687
+ * @param symbol - Trading pair symbol
15688
+ * @param context - Execution context with strategyName, exchangeName, frameName
15689
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
15690
+ */
15691
+ this.getMaxDrawdownDistancePnlPercentage = async (backtest, symbol, context) => {
15692
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlPercentage", {
15693
+ symbol,
15694
+ context,
15695
+ });
15696
+ await this.validate(context);
15697
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlPercentage(backtest, symbol, context);
15698
+ };
15699
+ /**
15700
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
15701
+ *
15702
+ * Delegates to StrategyConnectionService.getMaxDrawdownDistancePnlCost().
15703
+ * Returns null if no pending signal exists.
15704
+ *
15705
+ * @param backtest - Whether running in backtest mode
15706
+ * @param symbol - Trading pair symbol
15707
+ * @param context - Execution context with strategyName, exchangeName, frameName
15708
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
15709
+ */
15710
+ this.getMaxDrawdownDistancePnlCost = async (backtest, symbol, context) => {
15711
+ this.loggerService.log("strategyCoreService getMaxDrawdownDistancePnlCost", {
15712
+ symbol,
15713
+ context,
15714
+ });
15715
+ await this.validate(context);
15716
+ return await this.strategyConnectionService.getMaxDrawdownDistancePnlCost(backtest, symbol, context);
15717
+ };
15478
15718
  }
15479
15719
  }
15480
15720
 
@@ -15547,7 +15787,7 @@ class SizingGlobalService {
15547
15787
  * @param context - Context with riskName, exchangeName, frameName
15548
15788
  * @returns Unique string key for memoization
15549
15789
  */
15550
- const CREATE_KEY_FN$o = (context) => {
15790
+ const CREATE_KEY_FN$p = (context) => {
15551
15791
  const parts = [context.riskName, context.exchangeName];
15552
15792
  if (context.frameName)
15553
15793
  parts.push(context.frameName);
@@ -15573,7 +15813,7 @@ class RiskGlobalService {
15573
15813
  * @param payload - Payload with riskName, exchangeName and frameName
15574
15814
  * @returns Promise that resolves when validation is complete
15575
15815
  */
15576
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15816
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$p(context), async (context) => {
15577
15817
  this.loggerService.log("riskGlobalService validate", {
15578
15818
  context,
15579
15819
  });
@@ -15651,7 +15891,7 @@ const METHOD_NAME_VALIDATE = "actionCoreService validate";
15651
15891
  * @param context - Execution context with strategyName, exchangeName, frameName
15652
15892
  * @returns Unique string key for memoization
15653
15893
  */
15654
- const CREATE_KEY_FN$n = (context) => {
15894
+ const CREATE_KEY_FN$o = (context) => {
15655
15895
  const parts = [context.strategyName, context.exchangeName];
15656
15896
  if (context.frameName)
15657
15897
  parts.push(context.frameName);
@@ -15695,7 +15935,7 @@ class ActionCoreService {
15695
15935
  * @param context - Strategy execution context with strategyName, exchangeName and frameName
15696
15936
  * @returns Promise that resolves when all validations complete
15697
15937
  */
15698
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$n(context), async (context) => {
15938
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$o(context), async (context) => {
15699
15939
  this.loggerService.log(METHOD_NAME_VALIDATE, {
15700
15940
  context,
15701
15941
  });
@@ -20738,7 +20978,7 @@ const ReportWriter = new ReportWriterAdapter();
20738
20978
  * @param backtest - Whether running in backtest mode
20739
20979
  * @returns Unique string key for memoization
20740
20980
  */
20741
- const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
20981
+ const CREATE_KEY_FN$n = (symbol, strategyName, exchangeName, frameName, backtest) => {
20742
20982
  const parts = [symbol, strategyName, exchangeName];
20743
20983
  if (frameName)
20744
20984
  parts.push(frameName);
@@ -20984,7 +21224,7 @@ class BacktestMarkdownService {
20984
21224
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
20985
21225
  * Each combination gets its own isolated storage instance.
20986
21226
  */
20987
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
21227
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$n(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$a(symbol, strategyName, exchangeName, frameName));
20988
21228
  /**
20989
21229
  * Processes tick events and accumulates closed signals.
20990
21230
  * Should be called from IStrategyCallbacks.onTick.
@@ -21141,7 +21381,7 @@ class BacktestMarkdownService {
21141
21381
  payload,
21142
21382
  });
21143
21383
  if (payload) {
21144
- const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21384
+ const key = CREATE_KEY_FN$n(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21145
21385
  this.getStorage.clear(key);
21146
21386
  }
21147
21387
  else {
@@ -21203,7 +21443,7 @@ class BacktestMarkdownService {
21203
21443
  * @param backtest - Whether running in backtest mode
21204
21444
  * @returns Unique string key for memoization
21205
21445
  */
21206
- const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
21446
+ const CREATE_KEY_FN$m = (symbol, strategyName, exchangeName, frameName, backtest) => {
21207
21447
  const parts = [symbol, strategyName, exchangeName];
21208
21448
  if (frameName)
21209
21449
  parts.push(frameName);
@@ -21698,7 +21938,7 @@ class LiveMarkdownService {
21698
21938
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
21699
21939
  * Each combination gets its own isolated storage instance.
21700
21940
  */
21701
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21941
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$m(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$9(symbol, strategyName, exchangeName, frameName));
21702
21942
  /**
21703
21943
  * Subscribes to live signal emitter to receive tick events.
21704
21944
  * Protected against multiple subscriptions.
@@ -21916,7 +22156,7 @@ class LiveMarkdownService {
21916
22156
  payload,
21917
22157
  });
21918
22158
  if (payload) {
21919
- const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22159
+ const key = CREATE_KEY_FN$m(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
21920
22160
  this.getStorage.clear(key);
21921
22161
  }
21922
22162
  else {
@@ -21936,7 +22176,7 @@ class LiveMarkdownService {
21936
22176
  * @param backtest - Whether running in backtest mode
21937
22177
  * @returns Unique string key for memoization
21938
22178
  */
21939
- const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22179
+ const CREATE_KEY_FN$l = (symbol, strategyName, exchangeName, frameName, backtest) => {
21940
22180
  const parts = [symbol, strategyName, exchangeName];
21941
22181
  if (frameName)
21942
22182
  parts.push(frameName);
@@ -22225,7 +22465,7 @@ class ScheduleMarkdownService {
22225
22465
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
22226
22466
  * Each combination gets its own isolated storage instance.
22227
22467
  */
22228
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22468
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$l(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$8(symbol, strategyName, exchangeName, frameName));
22229
22469
  /**
22230
22470
  * Subscribes to signal emitter to receive scheduled signal events.
22231
22471
  * Protected against multiple subscriptions.
@@ -22428,7 +22668,7 @@ class ScheduleMarkdownService {
22428
22668
  payload,
22429
22669
  });
22430
22670
  if (payload) {
22431
- const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22671
+ const key = CREATE_KEY_FN$l(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22432
22672
  this.getStorage.clear(key);
22433
22673
  }
22434
22674
  else {
@@ -22448,7 +22688,7 @@ class ScheduleMarkdownService {
22448
22688
  * @param backtest - Whether running in backtest mode
22449
22689
  * @returns Unique string key for memoization
22450
22690
  */
22451
- const CREATE_KEY_FN$j = (symbol, strategyName, exchangeName, frameName, backtest) => {
22691
+ const CREATE_KEY_FN$k = (symbol, strategyName, exchangeName, frameName, backtest) => {
22452
22692
  const parts = [symbol, strategyName, exchangeName];
22453
22693
  if (frameName)
22454
22694
  parts.push(frameName);
@@ -22693,7 +22933,7 @@ class PerformanceMarkdownService {
22693
22933
  * Memoized function to get or create PerformanceStorage for a symbol-strategy-exchange-frame-backtest combination.
22694
22934
  * Each combination gets its own isolated storage instance.
22695
22935
  */
22696
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22936
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$k(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new PerformanceStorage(symbol, strategyName, exchangeName, frameName));
22697
22937
  /**
22698
22938
  * Subscribes to performance emitter to receive performance events.
22699
22939
  * Protected against multiple subscriptions.
@@ -22860,7 +23100,7 @@ class PerformanceMarkdownService {
22860
23100
  payload,
22861
23101
  });
22862
23102
  if (payload) {
22863
- const key = CREATE_KEY_FN$j(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
23103
+ const key = CREATE_KEY_FN$k(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
22864
23104
  this.getStorage.clear(key);
22865
23105
  }
22866
23106
  else {
@@ -23339,7 +23579,7 @@ class WalkerMarkdownService {
23339
23579
  * @param backtest - Whether running in backtest mode
23340
23580
  * @returns Unique string key for memoization
23341
23581
  */
23342
- const CREATE_KEY_FN$i = (exchangeName, frameName, backtest) => {
23582
+ const CREATE_KEY_FN$j = (exchangeName, frameName, backtest) => {
23343
23583
  const parts = [exchangeName];
23344
23584
  if (frameName)
23345
23585
  parts.push(frameName);
@@ -23786,7 +24026,7 @@ class HeatMarkdownService {
23786
24026
  * Memoized function to get or create HeatmapStorage for exchange, frame and backtest mode.
23787
24027
  * Each exchangeName + frameName + backtest mode combination gets its own isolated heatmap storage instance.
23788
24028
  */
23789
- this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$i(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
24029
+ this.getStorage = functoolsKit.memoize(([exchangeName, frameName, backtest]) => CREATE_KEY_FN$j(exchangeName, frameName, backtest), (exchangeName, frameName, backtest) => new HeatmapStorage(exchangeName, frameName, backtest));
23790
24030
  /**
23791
24031
  * Subscribes to signal emitter to receive tick events.
23792
24032
  * Protected against multiple subscriptions.
@@ -24004,7 +24244,7 @@ class HeatMarkdownService {
24004
24244
  payload,
24005
24245
  });
24006
24246
  if (payload) {
24007
- const key = CREATE_KEY_FN$i(payload.exchangeName, payload.frameName, payload.backtest);
24247
+ const key = CREATE_KEY_FN$j(payload.exchangeName, payload.frameName, payload.backtest);
24008
24248
  this.getStorage.clear(key);
24009
24249
  }
24010
24250
  else {
@@ -25035,7 +25275,7 @@ class ClientPartial {
25035
25275
  * @param backtest - Whether running in backtest mode
25036
25276
  * @returns Unique string key for memoization
25037
25277
  */
25038
- const CREATE_KEY_FN$h = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25278
+ const CREATE_KEY_FN$i = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
25039
25279
  /**
25040
25280
  * Creates a callback function for emitting profit events to partialProfitSubject.
25041
25281
  *
@@ -25157,7 +25397,7 @@ class PartialConnectionService {
25157
25397
  * Key format: "signalId:backtest" or "signalId:live"
25158
25398
  * Value: ClientPartial instance with logger and event emitters
25159
25399
  */
25160
- this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$h(signalId, backtest), (signalId, backtest) => {
25400
+ this.getPartial = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$i(signalId, backtest), (signalId, backtest) => {
25161
25401
  return new ClientPartial({
25162
25402
  signalId,
25163
25403
  logger: this.loggerService,
@@ -25247,7 +25487,7 @@ class PartialConnectionService {
25247
25487
  const partial = this.getPartial(data.id, backtest);
25248
25488
  await partial.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
25249
25489
  await partial.clear(symbol, data, priceClose, backtest);
25250
- const key = CREATE_KEY_FN$h(data.id, backtest);
25490
+ const key = CREATE_KEY_FN$i(data.id, backtest);
25251
25491
  this.getPartial.clear(key);
25252
25492
  };
25253
25493
  }
@@ -25263,7 +25503,7 @@ class PartialConnectionService {
25263
25503
  * @param backtest - Whether running in backtest mode
25264
25504
  * @returns Unique string key for memoization
25265
25505
  */
25266
- const CREATE_KEY_FN$g = (symbol, strategyName, exchangeName, frameName, backtest) => {
25506
+ const CREATE_KEY_FN$h = (symbol, strategyName, exchangeName, frameName, backtest) => {
25267
25507
  const parts = [symbol, strategyName, exchangeName];
25268
25508
  if (frameName)
25269
25509
  parts.push(frameName);
@@ -25486,7 +25726,7 @@ class PartialMarkdownService {
25486
25726
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
25487
25727
  * Each combination gets its own isolated storage instance.
25488
25728
  */
25489
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$g(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25729
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$h(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$6(symbol, strategyName, exchangeName, frameName));
25490
25730
  /**
25491
25731
  * Subscribes to partial profit/loss signal emitters to receive events.
25492
25732
  * Protected against multiple subscriptions.
@@ -25696,7 +25936,7 @@ class PartialMarkdownService {
25696
25936
  payload,
25697
25937
  });
25698
25938
  if (payload) {
25699
- const key = CREATE_KEY_FN$g(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25939
+ const key = CREATE_KEY_FN$h(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
25700
25940
  this.getStorage.clear(key);
25701
25941
  }
25702
25942
  else {
@@ -25712,7 +25952,7 @@ class PartialMarkdownService {
25712
25952
  * @param context - Context with strategyName, exchangeName, frameName
25713
25953
  * @returns Unique string key for memoization
25714
25954
  */
25715
- const CREATE_KEY_FN$f = (context) => {
25955
+ const CREATE_KEY_FN$g = (context) => {
25716
25956
  const parts = [context.strategyName, context.exchangeName];
25717
25957
  if (context.frameName)
25718
25958
  parts.push(context.frameName);
@@ -25786,7 +26026,7 @@ class PartialGlobalService {
25786
26026
  * @param context - Context with strategyName, exchangeName and frameName
25787
26027
  * @param methodName - Name of the calling method for error tracking
25788
26028
  */
25789
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$f(context), (context, methodName) => {
26029
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$g(context), (context, methodName) => {
25790
26030
  this.loggerService.log("partialGlobalService validate", {
25791
26031
  context,
25792
26032
  methodName,
@@ -26241,7 +26481,7 @@ class ClientBreakeven {
26241
26481
  * @param backtest - Whether running in backtest mode
26242
26482
  * @returns Unique string key for memoization
26243
26483
  */
26244
- const CREATE_KEY_FN$e = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26484
+ const CREATE_KEY_FN$f = (signalId, backtest) => `${signalId}:${backtest ? "backtest" : "live"}`;
26245
26485
  /**
26246
26486
  * Creates a callback function for emitting breakeven events to breakevenSubject.
26247
26487
  *
@@ -26327,7 +26567,7 @@ class BreakevenConnectionService {
26327
26567
  * Key format: "signalId:backtest" or "signalId:live"
26328
26568
  * Value: ClientBreakeven instance with logger and event emitter
26329
26569
  */
26330
- this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$e(signalId, backtest), (signalId, backtest) => {
26570
+ this.getBreakeven = functoolsKit.memoize(([signalId, backtest]) => CREATE_KEY_FN$f(signalId, backtest), (signalId, backtest) => {
26331
26571
  return new ClientBreakeven({
26332
26572
  signalId,
26333
26573
  logger: this.loggerService,
@@ -26388,7 +26628,7 @@ class BreakevenConnectionService {
26388
26628
  const breakeven = this.getBreakeven(data.id, backtest);
26389
26629
  await breakeven.waitForInit(symbol, data.strategyName, data.exchangeName, backtest);
26390
26630
  await breakeven.clear(symbol, data, priceClose, backtest);
26391
- const key = CREATE_KEY_FN$e(data.id, backtest);
26631
+ const key = CREATE_KEY_FN$f(data.id, backtest);
26392
26632
  this.getBreakeven.clear(key);
26393
26633
  };
26394
26634
  }
@@ -26404,7 +26644,7 @@ class BreakevenConnectionService {
26404
26644
  * @param backtest - Whether running in backtest mode
26405
26645
  * @returns Unique string key for memoization
26406
26646
  */
26407
- const CREATE_KEY_FN$d = (symbol, strategyName, exchangeName, frameName, backtest) => {
26647
+ const CREATE_KEY_FN$e = (symbol, strategyName, exchangeName, frameName, backtest) => {
26408
26648
  const parts = [symbol, strategyName, exchangeName];
26409
26649
  if (frameName)
26410
26650
  parts.push(frameName);
@@ -26579,7 +26819,7 @@ class BreakevenMarkdownService {
26579
26819
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
26580
26820
  * Each combination gets its own isolated storage instance.
26581
26821
  */
26582
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$d(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26822
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$e(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$5(symbol, strategyName, exchangeName, frameName));
26583
26823
  /**
26584
26824
  * Subscribes to breakeven signal emitter to receive events.
26585
26825
  * Protected against multiple subscriptions.
@@ -26768,7 +27008,7 @@ class BreakevenMarkdownService {
26768
27008
  payload,
26769
27009
  });
26770
27010
  if (payload) {
26771
- const key = CREATE_KEY_FN$d(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27011
+ const key = CREATE_KEY_FN$e(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
26772
27012
  this.getStorage.clear(key);
26773
27013
  }
26774
27014
  else {
@@ -26784,7 +27024,7 @@ class BreakevenMarkdownService {
26784
27024
  * @param context - Context with strategyName, exchangeName, frameName
26785
27025
  * @returns Unique string key for memoization
26786
27026
  */
26787
- const CREATE_KEY_FN$c = (context) => {
27027
+ const CREATE_KEY_FN$d = (context) => {
26788
27028
  const parts = [context.strategyName, context.exchangeName];
26789
27029
  if (context.frameName)
26790
27030
  parts.push(context.frameName);
@@ -26858,7 +27098,7 @@ class BreakevenGlobalService {
26858
27098
  * @param context - Context with strategyName, exchangeName and frameName
26859
27099
  * @param methodName - Name of the calling method for error tracking
26860
27100
  */
26861
- this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$c(context), (context, methodName) => {
27101
+ this.validate = functoolsKit.memoize(([context]) => CREATE_KEY_FN$d(context), (context, methodName) => {
26862
27102
  this.loggerService.log("breakevenGlobalService validate", {
26863
27103
  context,
26864
27104
  methodName,
@@ -27079,7 +27319,7 @@ class ConfigValidationService {
27079
27319
  * @param backtest - Whether running in backtest mode
27080
27320
  * @returns Unique string key for memoization
27081
27321
  */
27082
- const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
27322
+ const CREATE_KEY_FN$c = (symbol, strategyName, exchangeName, frameName, backtest) => {
27083
27323
  const parts = [symbol, strategyName, exchangeName];
27084
27324
  if (frameName)
27085
27325
  parts.push(frameName);
@@ -27246,7 +27486,7 @@ class RiskMarkdownService {
27246
27486
  * Memoized function to get or create ReportStorage for a symbol-strategy-exchange-frame-backtest combination.
27247
27487
  * Each combination gets its own isolated storage instance.
27248
27488
  */
27249
- 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));
27489
+ 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$4(symbol, strategyName, exchangeName, frameName));
27250
27490
  /**
27251
27491
  * Subscribes to risk rejection emitter to receive rejection events.
27252
27492
  * Protected against multiple subscriptions.
@@ -27435,7 +27675,7 @@ class RiskMarkdownService {
27435
27675
  payload,
27436
27676
  });
27437
27677
  if (payload) {
27438
- const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27678
+ const key = CREATE_KEY_FN$c(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
27439
27679
  this.getStorage.clear(key);
27440
27680
  }
27441
27681
  else {
@@ -29074,7 +29314,7 @@ class StrategyReportService {
29074
29314
  /**
29075
29315
  * Logs a cancel-scheduled event when a scheduled signal is cancelled.
29076
29316
  */
29077
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId) => {
29317
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, cancelId, note) => {
29078
29318
  this.loggerService.log("strategyReportService cancelScheduled", {
29079
29319
  symbol,
29080
29320
  isBacktest,
@@ -29087,6 +29327,7 @@ class StrategyReportService {
29087
29327
  await ReportWriter.writeData("strategy", {
29088
29328
  action: "cancel-scheduled",
29089
29329
  cancelId,
29330
+ note,
29090
29331
  symbol,
29091
29332
  timestamp,
29092
29333
  createdAt,
@@ -29108,7 +29349,7 @@ class StrategyReportService {
29108
29349
  /**
29109
29350
  * Logs a close-pending event when a pending signal is closed.
29110
29351
  */
29111
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId) => {
29352
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, totalPartials, closeId, note) => {
29112
29353
  this.loggerService.log("strategyReportService closePending", {
29113
29354
  symbol,
29114
29355
  isBacktest,
@@ -29121,6 +29362,7 @@ class StrategyReportService {
29121
29362
  await ReportWriter.writeData("strategy", {
29122
29363
  action: "close-pending",
29123
29364
  closeId,
29365
+ note,
29124
29366
  symbol,
29125
29367
  timestamp,
29126
29368
  createdAt,
@@ -29370,7 +29612,7 @@ class StrategyReportService {
29370
29612
  /**
29371
29613
  * Logs an activate-scheduled event when a scheduled signal is activated early.
29372
29614
  */
29373
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
29615
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
29374
29616
  this.loggerService.log("strategyReportService activateScheduled", {
29375
29617
  symbol,
29376
29618
  currentPrice,
@@ -29384,6 +29626,7 @@ class StrategyReportService {
29384
29626
  await ReportWriter.writeData("strategy", {
29385
29627
  action: "activate-scheduled",
29386
29628
  activateId,
29629
+ note,
29387
29630
  currentPrice,
29388
29631
  symbol,
29389
29632
  timestamp,
@@ -29477,14 +29720,14 @@ class StrategyReportService {
29477
29720
  exchangeName: event.exchangeName,
29478
29721
  frameName: event.frameName,
29479
29722
  strategyName: event.strategyName,
29480
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId));
29723
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.cancelId, event.note));
29481
29724
  const unClosePending = strategyCommitSubject
29482
29725
  .filter(({ action }) => action === "close-pending")
29483
29726
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
29484
29727
  exchangeName: event.exchangeName,
29485
29728
  frameName: event.frameName,
29486
29729
  strategyName: event.strategyName,
29487
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId));
29730
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.closeId, event.note));
29488
29731
  const unPartialProfit = strategyCommitSubject
29489
29732
  .filter(({ action }) => action === "partial-profit")
29490
29733
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -29526,7 +29769,7 @@ class StrategyReportService {
29526
29769
  exchangeName: event.exchangeName,
29527
29770
  frameName: event.frameName,
29528
29771
  strategyName: event.strategyName,
29529
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
29772
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
29530
29773
  const unAverageBuy = strategyCommitSubject
29531
29774
  .filter(({ action }) => action === "average-buy")
29532
29775
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -29814,7 +30057,7 @@ class HighestProfitReportService {
29814
30057
  * @returns Colon-separated key string for memoization
29815
30058
  * @internal
29816
30059
  */
29817
- const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30060
+ const CREATE_KEY_FN$b = (symbol, strategyName, exchangeName, frameName, backtest) => {
29818
30061
  const parts = [symbol, strategyName, exchangeName];
29819
30062
  if (frameName)
29820
30063
  parts.push(frameName);
@@ -30056,7 +30299,7 @@ class StrategyMarkdownService {
30056
30299
  *
30057
30300
  * @internal
30058
30301
  */
30059
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30302
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$b(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$3(symbol, strategyName, exchangeName, frameName));
30060
30303
  /**
30061
30304
  * Records a cancel-scheduled event when a scheduled signal is cancelled.
30062
30305
  *
@@ -30065,8 +30308,9 @@ class StrategyMarkdownService {
30065
30308
  * @param context - Strategy context with strategyName, exchangeName, frameName
30066
30309
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30067
30310
  * @param cancelId - Optional identifier for the cancellation reason
30311
+ * @param note - Optional note from commit payload
30068
30312
  */
30069
- this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId) => {
30313
+ this.cancelScheduled = async (symbol, isBacktest, context, timestamp, signalId, pnl, cancelId, note) => {
30070
30314
  this.loggerService.log("strategyMarkdownService cancelScheduled", {
30071
30315
  symbol,
30072
30316
  isBacktest,
@@ -30087,6 +30331,7 @@ class StrategyMarkdownService {
30087
30331
  action: "cancel-scheduled",
30088
30332
  pnl,
30089
30333
  cancelId,
30334
+ note,
30090
30335
  createdAt,
30091
30336
  backtest: isBacktest,
30092
30337
  });
@@ -30099,8 +30344,9 @@ class StrategyMarkdownService {
30099
30344
  * @param context - Strategy context with strategyName, exchangeName, frameName
30100
30345
  * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
30101
30346
  * @param closeId - Optional identifier for the close reason
30347
+ * @param note - Optional note from commit payload
30102
30348
  */
30103
- this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId) => {
30349
+ this.closePending = async (symbol, isBacktest, context, timestamp, signalId, pnl, closeId, note) => {
30104
30350
  this.loggerService.log("strategyMarkdownService closePending", {
30105
30351
  symbol,
30106
30352
  isBacktest,
@@ -30121,6 +30367,7 @@ class StrategyMarkdownService {
30121
30367
  action: "close-pending",
30122
30368
  pnl,
30123
30369
  closeId,
30370
+ note,
30124
30371
  createdAt,
30125
30372
  backtest: isBacktest,
30126
30373
  });
@@ -30419,8 +30666,9 @@ class StrategyMarkdownService {
30419
30666
  * @param scheduledAt - Signal creation timestamp in milliseconds
30420
30667
  * @param pendingAt - Pending timestamp in milliseconds
30421
30668
  * @param activateId - Optional identifier for the activation reason
30669
+ * @param note - Optional note from commit payload
30422
30670
  */
30423
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
30671
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, signalId, pnl, totalPartials, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId, note) => {
30424
30672
  this.loggerService.log("strategyMarkdownService activateScheduled", {
30425
30673
  symbol,
30426
30674
  currentPrice,
@@ -30443,6 +30691,7 @@ class StrategyMarkdownService {
30443
30691
  pnl,
30444
30692
  totalPartials,
30445
30693
  activateId,
30694
+ note,
30446
30695
  currentPrice,
30447
30696
  createdAt,
30448
30697
  backtest: isBacktest,
@@ -30624,7 +30873,7 @@ class StrategyMarkdownService {
30624
30873
  this.clear = async (payload) => {
30625
30874
  this.loggerService.log("strategyMarkdownService clear", { payload });
30626
30875
  if (payload) {
30627
- const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30876
+ const key = CREATE_KEY_FN$b(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
30628
30877
  this.getStorage.clear(key);
30629
30878
  }
30630
30879
  else {
@@ -30647,14 +30896,14 @@ class StrategyMarkdownService {
30647
30896
  exchangeName: event.exchangeName,
30648
30897
  frameName: event.frameName,
30649
30898
  strategyName: event.strategyName,
30650
- }, event.timestamp, event.signalId, event.pnl, event.cancelId));
30899
+ }, event.timestamp, event.signalId, event.pnl, event.cancelId, event.note));
30651
30900
  const unClosePending = strategyCommitSubject
30652
30901
  .filter(({ action }) => action === "close-pending")
30653
30902
  .connect(async (event) => await this.closePending(event.symbol, event.backtest, {
30654
30903
  exchangeName: event.exchangeName,
30655
30904
  frameName: event.frameName,
30656
30905
  strategyName: event.strategyName,
30657
- }, event.timestamp, event.signalId, event.pnl, event.closeId));
30906
+ }, event.timestamp, event.signalId, event.pnl, event.closeId, event.note));
30658
30907
  const unPartialProfit = strategyCommitSubject
30659
30908
  .filter(({ action }) => action === "partial-profit")
30660
30909
  .connect(async (event) => await this.partialProfit(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
@@ -30696,7 +30945,7 @@ class StrategyMarkdownService {
30696
30945
  exchangeName: event.exchangeName,
30697
30946
  frameName: event.frameName,
30698
30947
  strategyName: event.strategyName,
30699
- }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
30948
+ }, event.timestamp, event.signalId, event.pnl, event.totalPartials, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId, event.note));
30700
30949
  const unAverageBuy = strategyCommitSubject
30701
30950
  .filter(({ action }) => action === "average-buy")
30702
30951
  .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
@@ -30732,7 +30981,7 @@ class StrategyMarkdownService {
30732
30981
  * Creates a unique key for memoizing ReportStorage instances.
30733
30982
  * Key format: "symbol:strategyName:exchangeName[:frameName]:backtest|live"
30734
30983
  */
30735
- const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
30984
+ const CREATE_KEY_FN$a = (symbol, strategyName, exchangeName, frameName, backtest) => {
30736
30985
  const parts = [symbol, strategyName, exchangeName];
30737
30986
  if (frameName)
30738
30987
  parts.push(frameName);
@@ -30925,7 +31174,7 @@ let ReportStorage$2 = class ReportStorage {
30925
31174
  class SyncMarkdownService {
30926
31175
  constructor() {
30927
31176
  this.loggerService = inject(TYPES.loggerService);
30928
- 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$2(symbol, strategyName, exchangeName, frameName, backtest));
31177
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$a(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName, backtest) => new ReportStorage$2(symbol, strategyName, exchangeName, frameName, backtest));
30929
31178
  /**
30930
31179
  * Subscribes to `syncSubject` to start receiving `SignalSyncContract` events.
30931
31180
  * Protected against multiple subscriptions via `singleshot` — subsequent calls
@@ -31121,7 +31370,7 @@ class SyncMarkdownService {
31121
31370
  this.clear = async (payload) => {
31122
31371
  this.loggerService.log("syncMarkdownService clear", { payload });
31123
31372
  if (payload) {
31124
- const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31373
+ const key = CREATE_KEY_FN$a(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31125
31374
  this.getStorage.clear(key);
31126
31375
  }
31127
31376
  else {
@@ -31134,7 +31383,7 @@ class SyncMarkdownService {
31134
31383
  /**
31135
31384
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31136
31385
  */
31137
- const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31386
+ const CREATE_KEY_FN$9 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31138
31387
  const parts = [symbol, strategyName, exchangeName];
31139
31388
  if (frameName)
31140
31389
  parts.push(frameName);
@@ -31310,7 +31559,7 @@ let ReportStorage$1 = class ReportStorage {
31310
31559
  class HighestProfitMarkdownService {
31311
31560
  constructor() {
31312
31561
  this.loggerService = inject(TYPES.loggerService);
31313
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31562
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$9(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage$1(symbol, strategyName, exchangeName, frameName));
31314
31563
  /**
31315
31564
  * Subscribes to `highestProfitSubject` to start receiving `HighestProfitContract`
31316
31565
  * events. Protected against multiple subscriptions via `singleshot` — subsequent
@@ -31476,7 +31725,7 @@ class HighestProfitMarkdownService {
31476
31725
  this.clear = async (payload) => {
31477
31726
  this.loggerService.log("highestProfitMarkdownService clear", { payload });
31478
31727
  if (payload) {
31479
- const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31728
+ const key = CREATE_KEY_FN$9(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31480
31729
  this.getStorage.clear(key);
31481
31730
  }
31482
31731
  else {
@@ -31498,7 +31747,7 @@ const LISTEN_TIMEOUT$1 = 120000;
31498
31747
  * @param backtest - Whether running in backtest mode
31499
31748
  * @returns Unique string key for memoization
31500
31749
  */
31501
- const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31750
+ const CREATE_KEY_FN$8 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31502
31751
  const parts = [symbol, strategyName, exchangeName];
31503
31752
  if (frameName)
31504
31753
  parts.push(frameName);
@@ -31541,7 +31790,7 @@ class PriceMetaService {
31541
31790
  * Each subject holds the latest currentPrice emitted by the strategy iterator for that key.
31542
31791
  * Instances are cached until clear() is called.
31543
31792
  */
31544
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31793
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$8(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31545
31794
  /**
31546
31795
  * Returns the current market price for the given symbol and context.
31547
31796
  *
@@ -31570,10 +31819,10 @@ class PriceMetaService {
31570
31819
  if (source.data) {
31571
31820
  return source.data;
31572
31821
  }
31573
- console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31822
+ console.warn(`PriceMetaService: No currentPrice available for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31574
31823
  const currentPrice = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT$1);
31575
31824
  if (typeof currentPrice === "symbol") {
31576
- throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31825
+ throw new Error(`PriceMetaService: Timeout while waiting for currentPrice for ${CREATE_KEY_FN$8(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31577
31826
  }
31578
31827
  return currentPrice;
31579
31828
  };
@@ -31615,7 +31864,7 @@ class PriceMetaService {
31615
31864
  this.getSource.clear();
31616
31865
  return;
31617
31866
  }
31618
- const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31867
+ const key = CREATE_KEY_FN$8(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31619
31868
  this.getSource.clear(key);
31620
31869
  };
31621
31870
  }
@@ -31633,7 +31882,7 @@ const LISTEN_TIMEOUT = 120000;
31633
31882
  * @param backtest - Whether running in backtest mode
31634
31883
  * @returns Unique string key for memoization
31635
31884
  */
31636
- const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31885
+ const CREATE_KEY_FN$7 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31637
31886
  const parts = [symbol, strategyName, exchangeName];
31638
31887
  if (frameName)
31639
31888
  parts.push(frameName);
@@ -31676,7 +31925,7 @@ class TimeMetaService {
31676
31925
  * Each subject holds the latest createdAt timestamp emitted by the strategy iterator for that key.
31677
31926
  * Instances are cached until clear() is called.
31678
31927
  */
31679
- this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31928
+ this.getSource = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$7(symbol, strategyName, exchangeName, frameName, backtest), () => new functoolsKit.BehaviorSubject());
31680
31929
  /**
31681
31930
  * Returns the current candle timestamp (in milliseconds) for the given symbol and context.
31682
31931
  *
@@ -31704,10 +31953,10 @@ class TimeMetaService {
31704
31953
  if (source.data) {
31705
31954
  return source.data;
31706
31955
  }
31707
- console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31956
+ console.warn(`TimeMetaService: No timestamp available for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}. Trying to fetch from strategy iterator as a fallback...`);
31708
31957
  const timestamp = await functoolsKit.waitForNext(source, (data) => !!data, LISTEN_TIMEOUT);
31709
31958
  if (typeof timestamp === "symbol") {
31710
- throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$6(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31959
+ throw new Error(`TimeMetaService: Timeout while waiting for timestamp for ${CREATE_KEY_FN$7(symbol, context.strategyName, context.exchangeName, context.frameName, backtest)}`);
31711
31960
  }
31712
31961
  return timestamp;
31713
31962
  };
@@ -31749,7 +31998,7 @@ class TimeMetaService {
31749
31998
  this.getSource.clear();
31750
31999
  return;
31751
32000
  }
31752
- const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32001
+ const key = CREATE_KEY_FN$7(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
31753
32002
  this.getSource.clear(key);
31754
32003
  };
31755
32004
  }
@@ -31845,7 +32094,7 @@ class MaxDrawdownReportService {
31845
32094
  /**
31846
32095
  * Creates a unique memoization key for a symbol-strategy-exchange-frame-backtest combination.
31847
32096
  */
31848
- const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
32097
+ const CREATE_KEY_FN$6 = (symbol, strategyName, exchangeName, frameName, backtest) => {
31849
32098
  const parts = [symbol, strategyName, exchangeName];
31850
32099
  if (frameName)
31851
32100
  parts.push(frameName);
@@ -31969,7 +32218,7 @@ class ReportStorage {
31969
32218
  class MaxDrawdownMarkdownService {
31970
32219
  constructor() {
31971
32220
  this.loggerService = inject(TYPES.loggerService);
31972
- this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
32221
+ this.getStorage = functoolsKit.memoize(([symbol, strategyName, exchangeName, frameName, backtest]) => CREATE_KEY_FN$6(symbol, strategyName, exchangeName, frameName, backtest), (symbol, strategyName, exchangeName, frameName) => new ReportStorage(symbol, strategyName, exchangeName, frameName));
31973
32222
  /**
31974
32223
  * Subscribes to `maxDrawdownSubject` to start receiving `MaxDrawdownContract`
31975
32224
  * events. Protected against multiple subscriptions via `singleshot`.
@@ -32048,7 +32297,7 @@ class MaxDrawdownMarkdownService {
32048
32297
  this.clear = async (payload) => {
32049
32298
  this.loggerService.log("maxDrawdownMarkdownService clear", { payload });
32050
32299
  if (payload) {
32051
- const key = CREATE_KEY_FN$5(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32300
+ const key = CREATE_KEY_FN$6(payload.symbol, payload.strategyName, payload.exchangeName, payload.frameName, payload.backtest);
32052
32301
  this.getStorage.clear(key);
32053
32302
  }
32054
32303
  else {
@@ -35181,6 +35430,8 @@ const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strateg
35181
35430
  const GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getPositionHighestProfitDistancePnlCost";
35182
35431
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlPercentage";
35183
35432
  const GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST_METHOD_NAME = "strategy.getPositionHighestMaxDrawdownPnlCost";
35433
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlPercentage";
35434
+ const GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME = "strategy.getMaxDrawdownDistancePnlCost";
35184
35435
  const GET_POSITION_ENTRY_OVERLAP_METHOD_NAME = "strategy.getPositionEntryOverlap";
35185
35436
  const GET_POSITION_PARTIAL_OVERLAP_METHOD_NAME = "strategy.getPositionPartialOverlap";
35186
35437
  const HAS_NO_PENDING_SIGNAL_METHOD_NAME = "strategy.hasNoPendingSignal";
@@ -35196,7 +35447,7 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35196
35447
  *
35197
35448
  * @param symbol - Trading pair symbol
35198
35449
  * @param strategyName - Strategy name
35199
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
35450
+ * @param payload - Optional commit payload with id and note
35200
35451
  * @returns Promise that resolves when scheduled signal is cancelled
35201
35452
  *
35202
35453
  * @example
@@ -35204,13 +35455,13 @@ const HAS_NO_SCHEDULED_SIGNAL_METHOD_NAME = "strategy.hasNoScheduledSignal";
35204
35455
  * import { commitCancelScheduled } from "backtest-kit";
35205
35456
  *
35206
35457
  * // Cancel scheduled signal with custom ID
35207
- * await commitCancelScheduled("BTCUSDT", "manual-cancel-001");
35458
+ * await commitCancelScheduled("BTCUSDT", { id: "manual-cancel-001" });
35208
35459
  * ```
35209
35460
  */
35210
- async function commitCancelScheduled(symbol, cancelId) {
35461
+ async function commitCancelScheduled(symbol, payload = {}) {
35211
35462
  backtest.loggerService.info(CANCEL_SCHEDULED_METHOD_NAME, {
35212
35463
  symbol,
35213
- cancelId,
35464
+ payload,
35214
35465
  });
35215
35466
  if (!ExecutionContextService.hasContext()) {
35216
35467
  throw new Error("commitCancelScheduled requires an execution context");
@@ -35220,7 +35471,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35220
35471
  }
35221
35472
  const { backtest: isBacktest } = backtest.executionContextService.context;
35222
35473
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35223
- await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, cancelId);
35474
+ await backtest.strategyCoreService.cancelScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35224
35475
  }
35225
35476
  /**
35226
35477
  * Closes the pending signal without stopping the strategy.
@@ -35232,7 +35483,7 @@ async function commitCancelScheduled(symbol, cancelId) {
35232
35483
  * Automatically detects backtest/live mode from execution context.
35233
35484
  *
35234
35485
  * @param symbol - Trading pair symbol
35235
- * @param closeId - Optional close ID for tracking user-initiated closes
35486
+ * @param payload - Optional commit payload with id and note
35236
35487
  * @returns Promise that resolves when pending signal is closed
35237
35488
  *
35238
35489
  * @example
@@ -35240,13 +35491,13 @@ async function commitCancelScheduled(symbol, cancelId) {
35240
35491
  * import { commitClosePending } from "backtest-kit";
35241
35492
  *
35242
35493
  * // Close pending signal with custom ID
35243
- * await commitClosePending("BTCUSDT", "manual-close-001");
35494
+ * await commitClosePending("BTCUSDT", { id: "manual-close-001" });
35244
35495
  * ```
35245
35496
  */
35246
- async function commitClosePending(symbol, closeId) {
35497
+ async function commitClosePending(symbol, payload = {}) {
35247
35498
  backtest.loggerService.info(CLOSE_PENDING_METHOD_NAME, {
35248
35499
  symbol,
35249
- closeId,
35500
+ payload,
35250
35501
  });
35251
35502
  if (!ExecutionContextService.hasContext()) {
35252
35503
  throw new Error("commitClosePending requires an execution context");
@@ -35256,7 +35507,7 @@ async function commitClosePending(symbol, closeId) {
35256
35507
  }
35257
35508
  const { backtest: isBacktest } = backtest.executionContextService.context;
35258
35509
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35259
- await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, closeId);
35510
+ await backtest.strategyCoreService.closePending(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35260
35511
  }
35261
35512
  /**
35262
35513
  * Executes partial close at profit level (moving toward TP).
@@ -35719,7 +35970,7 @@ async function commitBreakeven(symbol) {
35719
35970
  * Automatically detects backtest/live mode from execution context.
35720
35971
  *
35721
35972
  * @param symbol - Trading pair symbol
35722
- * @param activateId - Optional activation ID for tracking user-initiated activations
35973
+ * @param payload - Optional commit payload with id and note
35723
35974
  * @returns Promise that resolves when activation flag is set
35724
35975
  *
35725
35976
  * @example
@@ -35727,13 +35978,13 @@ async function commitBreakeven(symbol) {
35727
35978
  * import { commitActivateScheduled } from "backtest-kit";
35728
35979
  *
35729
35980
  * // Activate scheduled signal early with custom ID
35730
- * await commitActivateScheduled("BTCUSDT", "manual-activate-001");
35981
+ * await commitActivateScheduled("BTCUSDT", { id: "manual-activate-001" });
35731
35982
  * ```
35732
35983
  */
35733
- async function commitActivateScheduled(symbol, activateId) {
35984
+ async function commitActivateScheduled(symbol, payload = {}) {
35734
35985
  backtest.loggerService.info(ACTIVATE_SCHEDULED_METHOD_NAME, {
35735
35986
  symbol,
35736
- activateId,
35987
+ payload,
35737
35988
  });
35738
35989
  if (!ExecutionContextService.hasContext()) {
35739
35990
  throw new Error("commitActivateScheduled requires an execution context");
@@ -35743,7 +35994,7 @@ async function commitActivateScheduled(symbol, activateId) {
35743
35994
  }
35744
35995
  const { backtest: isBacktest } = backtest.executionContextService.context;
35745
35996
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
35746
- await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
35997
+ await backtest.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, payload);
35747
35998
  }
35748
35999
  /**
35749
36000
  * Adds a new DCA entry to the active pending signal.
@@ -36931,6 +37182,64 @@ async function getPositionHighestMaxDrawdownPnlCost(symbol) {
36931
37182
  const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
36932
37183
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
36933
37184
  }
37185
+ /**
37186
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
37187
+ *
37188
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
37189
+ * Returns null if no pending signal exists.
37190
+ *
37191
+ * @param symbol - Trading pair symbol
37192
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
37193
+ *
37194
+ * @example
37195
+ * ```typescript
37196
+ * import { getMaxDrawdownDistancePnlPercentage } from "backtest-kit";
37197
+ *
37198
+ * const dist = await getMaxDrawdownDistancePnlPercentage("BTCUSDT");
37199
+ * // e.g. 3.5 (peak was +3.5% above trough)
37200
+ * ```
37201
+ */
37202
+ async function getMaxDrawdownDistancePnlPercentage(symbol) {
37203
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE_METHOD_NAME, { symbol });
37204
+ if (!ExecutionContextService.hasContext()) {
37205
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires an execution context");
37206
+ }
37207
+ if (!MethodContextService.hasContext()) {
37208
+ throw new Error("getMaxDrawdownDistancePnlPercentage requires a method context");
37209
+ }
37210
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37211
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37212
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(isBacktest, symbol, { exchangeName, frameName, strategyName });
37213
+ }
37214
+ /**
37215
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
37216
+ *
37217
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
37218
+ * Returns null if no pending signal exists.
37219
+ *
37220
+ * @param symbol - Trading pair symbol
37221
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
37222
+ *
37223
+ * @example
37224
+ * ```typescript
37225
+ * import { getMaxDrawdownDistancePnlCost } from "backtest-kit";
37226
+ *
37227
+ * const dist = await getMaxDrawdownDistancePnlCost("BTCUSDT");
37228
+ * // e.g. 7.2 (peak was $7.2 above trough)
37229
+ * ```
37230
+ */
37231
+ async function getMaxDrawdownDistancePnlCost(symbol) {
37232
+ backtest.loggerService.info(GET_MAX_DRAWDOWN_DISTANCE_PNL_COST_METHOD_NAME, { symbol });
37233
+ if (!ExecutionContextService.hasContext()) {
37234
+ throw new Error("getMaxDrawdownDistancePnlCost requires an execution context");
37235
+ }
37236
+ if (!MethodContextService.hasContext()) {
37237
+ throw new Error("getMaxDrawdownDistancePnlCost requires a method context");
37238
+ }
37239
+ const { backtest: isBacktest } = backtest.executionContextService.context;
37240
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
37241
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(isBacktest, symbol, { exchangeName, frameName, strategyName });
37242
+ }
36934
37243
  /**
36935
37244
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
36936
37245
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -38604,6 +38913,8 @@ const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE =
38604
38913
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "BacktestUtils.getPositionHighestProfitDistancePnlCost";
38605
38914
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "BacktestUtils.getPositionHighestMaxDrawdownPnlPercentage";
38606
38915
  const BACKTEST_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "BacktestUtils.getPositionHighestMaxDrawdownPnlCost";
38916
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "BacktestUtils.getMaxDrawdownDistancePnlPercentage";
38917
+ const BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "BacktestUtils.getMaxDrawdownDistancePnlCost";
38607
38918
  const BACKTEST_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "BacktestUtils.getPositionEntryOverlap";
38608
38919
  const BACKTEST_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "BacktestUtils.getPositionPartialOverlap";
38609
38920
  const BACKTEST_METHOD_NAME_BREAKEVEN = "Backtest.commitBreakeven";
@@ -39985,6 +40296,62 @@ class BacktestUtils {
39985
40296
  }
39986
40297
  return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(true, symbol, context);
39987
40298
  };
40299
+ /**
40300
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
40301
+ *
40302
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
40303
+ * Returns null if no pending signal exists.
40304
+ *
40305
+ * @param symbol - Trading pair symbol
40306
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40307
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
40308
+ */
40309
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
40310
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
40311
+ symbol,
40312
+ context,
40313
+ });
40314
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40315
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40316
+ {
40317
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40318
+ riskName &&
40319
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
40320
+ riskList &&
40321
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40322
+ actions &&
40323
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
40324
+ }
40325
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(true, symbol, context);
40326
+ };
40327
+ /**
40328
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
40329
+ *
40330
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
40331
+ * Returns null if no pending signal exists.
40332
+ *
40333
+ * @param symbol - Trading pair symbol
40334
+ * @param context - Execution context with strategyName, exchangeName, and frameName
40335
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
40336
+ */
40337
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
40338
+ backtest.loggerService.info(BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
40339
+ symbol,
40340
+ context,
40341
+ });
40342
+ backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40343
+ backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40344
+ {
40345
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
40346
+ riskName &&
40347
+ backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
40348
+ riskList &&
40349
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40350
+ actions &&
40351
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
40352
+ }
40353
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(true, symbol, context);
40354
+ };
39988
40355
  /**
39989
40356
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
39990
40357
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -40119,7 +40486,7 @@ class BacktestUtils {
40119
40486
  * @param symbol - Trading pair symbol
40120
40487
  * @param strategyName - Strategy name
40121
40488
  * @param context - Execution context with exchangeName and frameName
40122
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
40489
+ * @param payload - Optional commit payload with id and note
40123
40490
  * @returns Promise that resolves when scheduled signal is cancelled
40124
40491
  *
40125
40492
  * @example
@@ -40129,14 +40496,14 @@ class BacktestUtils {
40129
40496
  * exchangeName: "binance",
40130
40497
  * frameName: "frame1",
40131
40498
  * strategyName: "my-strategy"
40132
- * }, "manual-cancel-001");
40499
+ * }, { id: "manual-cancel-001" });
40133
40500
  * ```
40134
40501
  */
40135
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
40502
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
40136
40503
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CANCEL_SCHEDULED, {
40137
40504
  symbol,
40138
40505
  context,
40139
- cancelId,
40506
+ payload,
40140
40507
  });
40141
40508
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
40142
40509
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED);
@@ -40149,7 +40516,7 @@ class BacktestUtils {
40149
40516
  actions &&
40150
40517
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CANCEL_SCHEDULED));
40151
40518
  }
40152
- await backtest.strategyCoreService.cancelScheduled(true, symbol, context, cancelId);
40519
+ await backtest.strategyCoreService.cancelScheduled(true, symbol, context, payload);
40153
40520
  };
40154
40521
  /**
40155
40522
  * Closes the pending signal without stopping the strategy.
@@ -40160,7 +40527,7 @@ class BacktestUtils {
40160
40527
  *
40161
40528
  * @param symbol - Trading pair symbol
40162
40529
  * @param context - Execution context with strategyName, exchangeName, and frameName
40163
- * @param closeId - Optional close ID for user-initiated closes
40530
+ * @param payload - Optional commit payload with id and note
40164
40531
  * @returns Promise that resolves when pending signal is closed
40165
40532
  *
40166
40533
  * @example
@@ -40170,14 +40537,14 @@ class BacktestUtils {
40170
40537
  * exchangeName: "binance",
40171
40538
  * strategyName: "my-strategy",
40172
40539
  * frameName: "1m"
40173
- * }, "manual-close-001");
40540
+ * }, { id: "manual-close-001" });
40174
40541
  * ```
40175
40542
  */
40176
- this.commitClosePending = async (symbol, context, closeId) => {
40543
+ this.commitClosePending = async (symbol, context, payload = {}) => {
40177
40544
  backtest.loggerService.info(BACKTEST_METHOD_NAME_CLOSE_PENDING, {
40178
40545
  symbol,
40179
40546
  context,
40180
- closeId,
40547
+ payload,
40181
40548
  });
40182
40549
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
40183
40550
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CLOSE_PENDING);
@@ -40190,7 +40557,7 @@ class BacktestUtils {
40190
40557
  actions &&
40191
40558
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_CLOSE_PENDING));
40192
40559
  }
40193
- await backtest.strategyCoreService.closePending(true, symbol, context, closeId);
40560
+ await backtest.strategyCoreService.closePending(true, symbol, context, payload);
40194
40561
  };
40195
40562
  /**
40196
40563
  * Executes partial close at profit level (moving toward TP).
@@ -40826,7 +41193,7 @@ class BacktestUtils {
40826
41193
  *
40827
41194
  * @param symbol - Trading pair symbol
40828
41195
  * @param context - Execution context with strategyName, exchangeName, and frameName
40829
- * @param activateId - Optional activation ID for tracking user-initiated activations
41196
+ * @param payload - Optional commit payload with id and note
40830
41197
  * @returns Promise that resolves when activation flag is set
40831
41198
  *
40832
41199
  * @example
@@ -40836,14 +41203,14 @@ class BacktestUtils {
40836
41203
  * strategyName: "my-strategy",
40837
41204
  * exchangeName: "binance",
40838
41205
  * frameName: "1h"
40839
- * }, "manual-activate-001");
41206
+ * }, { id: "manual-activate-001" });
40840
41207
  * ```
40841
41208
  */
40842
- this.commitActivateScheduled = async (symbol, context, activateId) => {
41209
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
40843
41210
  backtest.loggerService.info(BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED, {
40844
41211
  symbol,
40845
41212
  context,
40846
- activateId,
41213
+ payload,
40847
41214
  });
40848
41215
  backtest.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
40849
41216
  backtest.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -40856,7 +41223,7 @@ class BacktestUtils {
40856
41223
  actions &&
40857
41224
  actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED));
40858
41225
  }
40859
- await backtest.strategyCoreService.activateScheduled(true, symbol, context, activateId);
41226
+ await backtest.strategyCoreService.activateScheduled(true, symbol, context, payload);
40860
41227
  };
40861
41228
  /**
40862
41229
  * Adds a new DCA entry to the active pending signal.
@@ -41114,6 +41481,8 @@ const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "Li
41114
41481
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "LiveUtils.getPositionHighestProfitDistancePnlCost";
41115
41482
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "LiveUtils.getPositionHighestMaxDrawdownPnlPercentage";
41116
41483
  const LIVE_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "LiveUtils.getPositionHighestMaxDrawdownPnlCost";
41484
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "LiveUtils.getMaxDrawdownDistancePnlPercentage";
41485
+ const LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "LiveUtils.getMaxDrawdownDistancePnlCost";
41117
41486
  const LIVE_METHOD_NAME_GET_POSITION_ENTRY_OVERLAP = "LiveUtils.getPositionEntryOverlap";
41118
41487
  const LIVE_METHOD_NAME_GET_POSITION_PARTIAL_OVERLAP = "LiveUtils.getPositionPartialOverlap";
41119
41488
  const LIVE_METHOD_NAME_BREAKEVEN = "Live.commitBreakeven";
@@ -42638,6 +43007,70 @@ class LiveUtils {
42638
43007
  frameName: "",
42639
43008
  });
42640
43009
  };
43010
+ /**
43011
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
43012
+ *
43013
+ * Computed as: max(0, peakPnlPercentage - fallPnlPercentage).
43014
+ * Returns null if no pending signal exists.
43015
+ *
43016
+ * @param symbol - Trading pair symbol
43017
+ * @param context - Execution context with strategyName and exchangeName
43018
+ * @returns peak-to-trough PnL percentage distance (≥ 0) or null if no active position
43019
+ */
43020
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context) => {
43021
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, {
43022
+ symbol,
43023
+ context,
43024
+ });
43025
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
43026
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
43027
+ {
43028
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43029
+ riskName &&
43030
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
43031
+ riskList &&
43032
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
43033
+ actions &&
43034
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
43035
+ }
43036
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(false, symbol, {
43037
+ strategyName: context.strategyName,
43038
+ exchangeName: context.exchangeName,
43039
+ frameName: "",
43040
+ });
43041
+ };
43042
+ /**
43043
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
43044
+ *
43045
+ * Computed as: max(0, peakPnlCost - fallPnlCost).
43046
+ * Returns null if no pending signal exists.
43047
+ *
43048
+ * @param symbol - Trading pair symbol
43049
+ * @param context - Execution context with strategyName and exchangeName
43050
+ * @returns peak-to-trough PnL cost distance (≥ 0) or null if no active position
43051
+ */
43052
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context) => {
43053
+ backtest.loggerService.info(LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, {
43054
+ symbol,
43055
+ context,
43056
+ });
43057
+ backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
43058
+ backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
43059
+ {
43060
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
43061
+ riskName &&
43062
+ backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
43063
+ riskList &&
43064
+ riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
43065
+ actions &&
43066
+ actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, LIVE_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
43067
+ }
43068
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(false, symbol, {
43069
+ strategyName: context.strategyName,
43070
+ exchangeName: context.exchangeName,
43071
+ frameName: "",
43072
+ });
43073
+ };
42641
43074
  /**
42642
43075
  * Checks whether the current price falls within the tolerance zone of any existing DCA entry level.
42643
43076
  * Use this to prevent duplicate DCA entries at the same price area.
@@ -42779,7 +43212,7 @@ class LiveUtils {
42779
43212
  * @param symbol - Trading pair symbol
42780
43213
  * @param strategyName - Strategy name
42781
43214
  * @param context - Execution context with exchangeName and frameName
42782
- * @param cancelId - Optional cancellation ID for tracking user-initiated cancellations
43215
+ * @param payload - Optional commit payload with id and note
42783
43216
  * @returns Promise that resolves when scheduled signal is cancelled
42784
43217
  *
42785
43218
  * @example
@@ -42789,14 +43222,14 @@ class LiveUtils {
42789
43222
  * exchangeName: "binance",
42790
43223
  * frameName: "",
42791
43224
  * strategyName: "my-strategy"
42792
- * }, "manual-cancel-001");
43225
+ * }, { id: "manual-cancel-001" });
42793
43226
  * ```
42794
43227
  */
42795
- this.commitCancelScheduled = async (symbol, context, cancelId) => {
43228
+ this.commitCancelScheduled = async (symbol, context, payload = {}) => {
42796
43229
  backtest.loggerService.info(LIVE_METHOD_NAME_CANCEL_SCHEDULED, {
42797
43230
  symbol,
42798
43231
  context,
42799
- cancelId,
43232
+ payload,
42800
43233
  });
42801
43234
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
42802
43235
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL_SCHEDULED);
@@ -42813,7 +43246,7 @@ class LiveUtils {
42813
43246
  strategyName: context.strategyName,
42814
43247
  exchangeName: context.exchangeName,
42815
43248
  frameName: "",
42816
- }, cancelId);
43249
+ }, payload);
42817
43250
  };
42818
43251
  /**
42819
43252
  * Closes the pending signal without stopping the strategy.
@@ -42824,7 +43257,7 @@ class LiveUtils {
42824
43257
  *
42825
43258
  * @param symbol - Trading pair symbol
42826
43259
  * @param context - Execution context with strategyName and exchangeName
42827
- * @param closeId - Optional close ID for user-initiated closes
43260
+ * @param payload - Optional commit payload with id and note
42828
43261
  * @returns Promise that resolves when pending signal is closed
42829
43262
  *
42830
43263
  * @example
@@ -42833,14 +43266,14 @@ class LiveUtils {
42833
43266
  * await Live.commitClose("BTCUSDT", {
42834
43267
  * exchangeName: "binance",
42835
43268
  * strategyName: "my-strategy"
42836
- * }, "manual-close-001");
43269
+ * }, { id: "manual-close-001" });
42837
43270
  * ```
42838
43271
  */
42839
- this.commitClosePending = async (symbol, context, closeId) => {
43272
+ this.commitClosePending = async (symbol, context, payload = {}) => {
42840
43273
  backtest.loggerService.info(LIVE_METHOD_NAME_CLOSE_PENDING, {
42841
43274
  symbol,
42842
43275
  context,
42843
- closeId,
43276
+ payload,
42844
43277
  });
42845
43278
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CLOSE_PENDING);
42846
43279
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CLOSE_PENDING);
@@ -42857,7 +43290,7 @@ class LiveUtils {
42857
43290
  strategyName: context.strategyName,
42858
43291
  exchangeName: context.exchangeName,
42859
43292
  frameName: "",
42860
- }, closeId);
43293
+ }, payload);
42861
43294
  };
42862
43295
  /**
42863
43296
  * Executes partial close at profit level (moving toward TP).
@@ -43651,7 +44084,7 @@ class LiveUtils {
43651
44084
  *
43652
44085
  * @param symbol - Trading pair symbol
43653
44086
  * @param context - Execution context with strategyName and exchangeName
43654
- * @param activateId - Optional activation ID for tracking user-initiated activations
44087
+ * @param payload - Optional commit payload with id and note
43655
44088
  * @returns Promise that resolves when activation flag is set
43656
44089
  *
43657
44090
  * @example
@@ -43660,14 +44093,14 @@ class LiveUtils {
43660
44093
  * await Live.commitActivateScheduled("BTCUSDT", {
43661
44094
  * strategyName: "my-strategy",
43662
44095
  * exchangeName: "binance"
43663
- * }, "manual-activate-001");
44096
+ * }, { id: "manual-activate-001" });
43664
44097
  * ```
43665
44098
  */
43666
- this.commitActivateScheduled = async (symbol, context, activateId) => {
44099
+ this.commitActivateScheduled = async (symbol, context, payload = {}) => {
43667
44100
  backtest.loggerService.info(LIVE_METHOD_NAME_ACTIVATE_SCHEDULED, {
43668
44101
  symbol,
43669
44102
  context,
43670
- activateId,
44103
+ payload,
43671
44104
  });
43672
44105
  backtest.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
43673
44106
  backtest.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_ACTIVATE_SCHEDULED);
@@ -43684,7 +44117,7 @@ class LiveUtils {
43684
44117
  strategyName: context.strategyName,
43685
44118
  exchangeName: context.exchangeName,
43686
44119
  frameName: "",
43687
- }, activateId);
44120
+ }, payload);
43688
44121
  };
43689
44122
  /**
43690
44123
  * Adds a new DCA entry to the active pending signal.
@@ -44930,6 +45363,503 @@ async function listRiskSchema() {
44930
45363
  return await backtest.riskValidationService.list();
44931
45364
  }
44932
45365
 
45366
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistBacktestUtils.handleActivePing";
45367
+ const RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistBacktestUtils.getLatestSignal";
45368
+ const RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentPersistLiveUtils.handleActivePing";
45369
+ const RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentPersistLiveUtils.getLatestSignal";
45370
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryBacktestUtils.handleActivePing";
45371
+ const RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryBacktestUtils.getLatestSignal";
45372
+ const RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentMemoryLiveUtils.handleActivePing";
45373
+ const RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL = "RecentMemoryLiveUtils.getLatestSignal";
45374
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentBacktestAdapter.handleActivePing";
45375
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentBacktestAdapter.getLatestSignal";
45376
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentBacktestAdapter.useRecentAdapter";
45377
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentBacktestAdapter.usePersist";
45378
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentBacktestAdapter.useMemory";
45379
+ const RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR = "RecentBacktestAdapter.clear";
45380
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING = "RecentLiveAdapter.handleActivePing";
45381
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentLiveAdapter.getLatestSignal";
45382
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER = "RecentLiveAdapter.useRecentAdapter";
45383
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST = "RecentLiveAdapter.usePersist";
45384
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY = "RecentLiveAdapter.useMemory";
45385
+ const RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR = "RecentLiveAdapter.clear";
45386
+ const RECENT_ADAPTER_METHOD_NAME_ENABLE = "RecentAdapter.enable";
45387
+ const RECENT_ADAPTER_METHOD_NAME_DISABLE = "RecentAdapter.disable";
45388
+ const RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL = "RecentAdapter.getLatestSignal";
45389
+ /**
45390
+ * Builds a composite storage key from context parts.
45391
+ * Includes backtest flag as the last segment to prevent live/backtest collisions.
45392
+ * @param symbol - Trading pair symbol
45393
+ * @param strategyName - Strategy identifier
45394
+ * @param exchangeName - Exchange identifier
45395
+ * @param frameName - Frame identifier
45396
+ * @param backtest - Flag indicating if the context is backtest or live
45397
+ * @returns Composite key string
45398
+ */
45399
+ const CREATE_KEY_FN$5 = (symbol, strategyName, exchangeName, frameName, backtest) => {
45400
+ const parts = [symbol, strategyName, exchangeName];
45401
+ if (frameName)
45402
+ parts.push(frameName);
45403
+ parts.push(backtest ? "backtest" : "live");
45404
+ return parts.join(":");
45405
+ };
45406
+ /**
45407
+ * Persistent storage adapter for backtest recent signals.
45408
+ *
45409
+ * Features:
45410
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45411
+ * - Handles active ping events only
45412
+ *
45413
+ * Use this adapter for backtest recent signal persistence across sessions.
45414
+ */
45415
+ class RecentPersistBacktestUtils {
45416
+ constructor() {
45417
+ /**
45418
+ * Handles active ping event.
45419
+ * Persists the latest signal to disk via PersistRecentAdapter.
45420
+ * @param event - Active ping contract with signal data and backtest flag
45421
+ */
45422
+ this.handleActivePing = async (event) => {
45423
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45424
+ signalId: event.data.id,
45425
+ });
45426
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45427
+ };
45428
+ /**
45429
+ * Retrieves the latest persisted signal for the given context.
45430
+ * @param symbol - Trading pair symbol
45431
+ * @param strategyName - Strategy identifier
45432
+ * @param exchangeName - Exchange identifier
45433
+ * @param frameName - Frame identifier
45434
+ * @param backtest - Flag indicating if the context is backtest or live
45435
+ * @returns The latest signal or null if not found
45436
+ */
45437
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45438
+ backtest.loggerService.info(RECENT_PERSIST_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, {
45439
+ symbol,
45440
+ strategyName,
45441
+ exchangeName,
45442
+ frameName,
45443
+ backtest: backtest$1,
45444
+ });
45445
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45446
+ };
45447
+ }
45448
+ }
45449
+ /**
45450
+ * In-memory storage adapter for backtest recent signals.
45451
+ *
45452
+ * Features:
45453
+ * - Stores the latest active signal per context key in memory only
45454
+ * - Fast read/write operations
45455
+ * - Data is lost when application restarts
45456
+ *
45457
+ * Use this adapter for testing or when persistence is not required.
45458
+ */
45459
+ class RecentMemoryBacktestUtils {
45460
+ constructor() {
45461
+ /** Map of composite context keys to the latest signal */
45462
+ this._signals = new Map();
45463
+ /**
45464
+ * Handles active ping event.
45465
+ * Stores the latest signal in memory under the composite context key.
45466
+ * @param event - Active ping contract with signal data and backtest flag
45467
+ */
45468
+ this.handleActivePing = async (event) => {
45469
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_HANDLE_ACTIVE_PING, {
45470
+ signalId: event.data.id,
45471
+ });
45472
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45473
+ this._signals.set(key, event.data);
45474
+ };
45475
+ /**
45476
+ * Retrieves the latest in-memory signal for the given context.
45477
+ * @param symbol - Trading pair symbol
45478
+ * @param strategyName - Strategy identifier
45479
+ * @param exchangeName - Exchange identifier
45480
+ * @param frameName - Frame identifier
45481
+ * @param backtest - Flag indicating if the context is backtest or live
45482
+ * @returns The latest signal or null if not found
45483
+ */
45484
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45485
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45486
+ backtest.loggerService.info(RECENT_MEMORY_BACKTEST_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45487
+ return this._signals.get(key) ?? null;
45488
+ };
45489
+ }
45490
+ }
45491
+ /**
45492
+ * Persistent storage adapter for live recent signals.
45493
+ *
45494
+ * Features:
45495
+ * - Persists the latest active signal per context to disk using PersistRecentAdapter
45496
+ * - Handles active ping events only
45497
+ *
45498
+ * Use this adapter (default) for live recent signal persistence across sessions.
45499
+ */
45500
+ class RecentPersistLiveUtils {
45501
+ constructor() {
45502
+ /**
45503
+ * Handles active ping event.
45504
+ * Persists the latest signal to disk via PersistRecentAdapter.
45505
+ * @param event - Active ping contract with signal data and backtest flag
45506
+ */
45507
+ this.handleActivePing = async (event) => {
45508
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45509
+ signalId: event.data.id,
45510
+ });
45511
+ await PersistRecentAdapter.writeRecentData(event.data, event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45512
+ };
45513
+ /**
45514
+ * Retrieves the latest persisted signal for the given context.
45515
+ * @param symbol - Trading pair symbol
45516
+ * @param strategyName - Strategy identifier
45517
+ * @param exchangeName - Exchange identifier
45518
+ * @param frameName - Frame identifier
45519
+ * @param backtest - Flag indicating if the context is backtest or live
45520
+ * @returns The latest signal or null if not found
45521
+ */
45522
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45523
+ backtest.loggerService.info(RECENT_PERSIST_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, {
45524
+ symbol,
45525
+ strategyName,
45526
+ exchangeName,
45527
+ frameName,
45528
+ backtest: backtest$1,
45529
+ });
45530
+ return await PersistRecentAdapter.readRecentData(symbol, strategyName, exchangeName, frameName, backtest$1);
45531
+ };
45532
+ }
45533
+ }
45534
+ /**
45535
+ * In-memory storage adapter for live recent signals.
45536
+ *
45537
+ * Features:
45538
+ * - Stores the latest active signal per context key in memory only
45539
+ * - Fast read/write operations
45540
+ * - Data is lost when application restarts
45541
+ *
45542
+ * Use this adapter for testing or when persistence is not required.
45543
+ */
45544
+ class RecentMemoryLiveUtils {
45545
+ constructor() {
45546
+ /** Map of composite context keys to the latest signal */
45547
+ this._signals = new Map();
45548
+ /**
45549
+ * Handles active ping event.
45550
+ * Stores the latest signal in memory under the composite context key.
45551
+ * @param event - Active ping contract with signal data and backtest flag
45552
+ */
45553
+ this.handleActivePing = async (event) => {
45554
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_HANDLE_ACTIVE_PING, {
45555
+ signalId: event.data.id,
45556
+ });
45557
+ const key = CREATE_KEY_FN$5(event.symbol, event.strategyName, event.exchangeName, event.data.frameName, event.backtest);
45558
+ this._signals.set(key, event.data);
45559
+ };
45560
+ /**
45561
+ * Retrieves the latest in-memory signal for the given context.
45562
+ * @param symbol - Trading pair symbol
45563
+ * @param strategyName - Strategy identifier
45564
+ * @param exchangeName - Exchange identifier
45565
+ * @param frameName - Frame identifier
45566
+ * @param backtest - Flag indicating if the context is backtest or live
45567
+ * @returns The latest signal or null if not found
45568
+ */
45569
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45570
+ const key = CREATE_KEY_FN$5(symbol, strategyName, exchangeName, frameName, backtest$1);
45571
+ backtest.loggerService.info(RECENT_MEMORY_LIVE_METHOD_NAME_GET_LATEST_SIGNAL, { key });
45572
+ return this._signals.get(key) ?? null;
45573
+ };
45574
+ }
45575
+ }
45576
+ /**
45577
+ * Backtest recent signal adapter with pluggable storage backend.
45578
+ *
45579
+ * Features:
45580
+ * - Adapter pattern for swappable storage implementations
45581
+ * - Default adapter: RecentMemoryBacktestUtils (in-memory storage)
45582
+ * - Alternative adapter: RecentPersistBacktestUtils
45583
+ * - Convenience methods: usePersist(), useMemory()
45584
+ */
45585
+ class RecentBacktestAdapter {
45586
+ constructor() {
45587
+ /** Internal storage utils instance */
45588
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45589
+ /**
45590
+ * Handles active ping event.
45591
+ * Proxies call to the underlying storage adapter.
45592
+ * @param event - Active ping contract with signal data
45593
+ */
45594
+ this.handleActivePing = async (event) => {
45595
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45596
+ signalId: event.data.id,
45597
+ });
45598
+ return await this._recentBacktestUtils.handleActivePing(event);
45599
+ };
45600
+ /**
45601
+ * Retrieves the latest signal for the given context.
45602
+ * Proxies call to the underlying storage adapter.
45603
+ * @param symbol - Trading pair symbol
45604
+ * @param strategyName - Strategy identifier
45605
+ * @param exchangeName - Exchange identifier
45606
+ * @param frameName - Frame identifier
45607
+ * @param backtest - Flag indicating if the context is backtest or live
45608
+ * @returns The latest signal or null if not found
45609
+ */
45610
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45611
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45612
+ symbol,
45613
+ strategyName,
45614
+ exchangeName,
45615
+ frameName,
45616
+ backtest: backtest$1,
45617
+ });
45618
+ return await this._recentBacktestUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45619
+ };
45620
+ /**
45621
+ * Sets the storage adapter constructor.
45622
+ * All future storage operations will use this adapter.
45623
+ * @param Ctor - Constructor for recent adapter
45624
+ */
45625
+ this.useRecentAdapter = (Ctor) => {
45626
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_ADAPTER);
45627
+ this._recentBacktestUtils = Reflect.construct(Ctor, []);
45628
+ };
45629
+ /**
45630
+ * Switches to persistent storage adapter.
45631
+ * Signals will be persisted to disk.
45632
+ */
45633
+ this.usePersist = () => {
45634
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_PERSIST);
45635
+ this._recentBacktestUtils = new RecentPersistBacktestUtils();
45636
+ };
45637
+ /**
45638
+ * Switches to in-memory storage adapter (default).
45639
+ * Signals will be stored in memory only.
45640
+ */
45641
+ this.useMemory = () => {
45642
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_USE_MEMORY);
45643
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45644
+ };
45645
+ /**
45646
+ * Clears the cached utils instance by resetting to the default in-memory adapter.
45647
+ */
45648
+ this.clear = () => {
45649
+ backtest.loggerService.info(RECENT_BACKTEST_ADAPTER_METHOD_NAME_CLEAR);
45650
+ this._recentBacktestUtils = new RecentMemoryBacktestUtils();
45651
+ };
45652
+ }
45653
+ }
45654
+ /**
45655
+ * Live recent signal adapter with pluggable storage backend.
45656
+ *
45657
+ * Features:
45658
+ * - Adapter pattern for swappable storage implementations
45659
+ * - Default adapter: RecentPersistLiveUtils (persistent storage)
45660
+ * - Alternative adapter: RecentMemoryLiveUtils
45661
+ * - Convenience methods: usePersist(), useMemory()
45662
+ */
45663
+ class RecentLiveAdapter {
45664
+ constructor() {
45665
+ /** Internal storage utils instance */
45666
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45667
+ /**
45668
+ * Handles active ping event.
45669
+ * Proxies call to the underlying storage adapter.
45670
+ * @param event - Active ping contract with signal data
45671
+ */
45672
+ this.handleActivePing = async (event) => {
45673
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_HANDLE_ACTIVE_PING, {
45674
+ signalId: event.data.id,
45675
+ });
45676
+ return await this._recentLiveUtils.handleActivePing(event);
45677
+ };
45678
+ /**
45679
+ * Retrieves the latest signal for the given context.
45680
+ * Proxies call to the underlying storage adapter.
45681
+ * @param symbol - Trading pair symbol
45682
+ * @param strategyName - Strategy identifier
45683
+ * @param exchangeName - Exchange identifier
45684
+ * @param frameName - Frame identifier
45685
+ * @param backtest - Flag indicating if the context is backtest or live
45686
+ * @returns The latest signal or null if not found
45687
+ */
45688
+ this.getLatestSignal = async (symbol, strategyName, exchangeName, frameName, backtest$1) => {
45689
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45690
+ symbol,
45691
+ strategyName,
45692
+ exchangeName,
45693
+ frameName,
45694
+ backtest: backtest$1,
45695
+ });
45696
+ return await this._recentLiveUtils.getLatestSignal(symbol, strategyName, exchangeName, frameName, backtest$1);
45697
+ };
45698
+ /**
45699
+ * Sets the storage adapter constructor.
45700
+ * All future storage operations will use this adapter.
45701
+ * @param Ctor - Constructor for recent adapter
45702
+ */
45703
+ this.useRecentAdapter = (Ctor) => {
45704
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_ADAPTER);
45705
+ this._recentLiveUtils = Reflect.construct(Ctor, []);
45706
+ };
45707
+ /**
45708
+ * Switches to persistent storage adapter (default).
45709
+ * Signals will be persisted to disk.
45710
+ */
45711
+ this.usePersist = () => {
45712
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_PERSIST);
45713
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45714
+ };
45715
+ /**
45716
+ * Switches to in-memory storage adapter.
45717
+ * Signals will be stored in memory only.
45718
+ */
45719
+ this.useMemory = () => {
45720
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_USE_MEMORY);
45721
+ this._recentLiveUtils = new RecentMemoryLiveUtils();
45722
+ };
45723
+ /**
45724
+ * Clears the cached utils instance by resetting to the default persistent adapter.
45725
+ */
45726
+ this.clear = () => {
45727
+ backtest.loggerService.info(RECENT_LIVE_ADAPTER_METHOD_NAME_CLEAR);
45728
+ this._recentLiveUtils = new RecentPersistLiveUtils();
45729
+ };
45730
+ }
45731
+ }
45732
+ /**
45733
+ * Main recent signal adapter that manages both backtest and live recent signal storage.
45734
+ *
45735
+ * Features:
45736
+ * - Subscribes to activePingSubject for automatic storage updates
45737
+ * - Provides unified access to the latest signal for any context
45738
+ * - Singleshot enable pattern prevents duplicate subscriptions
45739
+ * - Cleanup function for proper unsubscription
45740
+ */
45741
+ class RecentAdapter {
45742
+ constructor() {
45743
+ /**
45744
+ * Enables recent signal storage by subscribing to activePingSubject.
45745
+ * Uses singleshot to ensure one-time subscription.
45746
+ *
45747
+ * @returns Cleanup function that unsubscribes from all emitters
45748
+ */
45749
+ this.enable = functoolsKit.singleshot(() => {
45750
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_ENABLE);
45751
+ let unBacktest;
45752
+ let unLive;
45753
+ {
45754
+ const unBacktestPingActive = activePingSubject
45755
+ .filter(({ backtest }) => backtest)
45756
+ .connect((event) => RecentBacktest.handleActivePing(event));
45757
+ unBacktest = functoolsKit.compose(() => unBacktestPingActive());
45758
+ }
45759
+ {
45760
+ const unLivePingActive = activePingSubject
45761
+ .filter(({ backtest }) => !backtest)
45762
+ .connect((event) => RecentLive.handleActivePing(event));
45763
+ unLive = functoolsKit.compose(() => unLivePingActive());
45764
+ }
45765
+ const unEnable = () => this.enable.clear();
45766
+ return functoolsKit.compose(() => unBacktest(), () => unLive(), () => unEnable());
45767
+ });
45768
+ /**
45769
+ * Disables recent signal storage by unsubscribing from all emitters.
45770
+ * Safe to call multiple times.
45771
+ */
45772
+ this.disable = () => {
45773
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_DISABLE);
45774
+ if (this.enable.hasValue()) {
45775
+ const lastSubscription = this.enable();
45776
+ lastSubscription();
45777
+ }
45778
+ };
45779
+ /**
45780
+ * Retrieves the latest active signal for the given symbol and context.
45781
+ * Searches backtest storage first, then live storage.
45782
+ *
45783
+ * @param symbol - Trading pair symbol
45784
+ * @param context - Execution context with strategyName, exchangeName, and frameName
45785
+ * @param backtest - Flag indicating if the context is backtest or live
45786
+ * @returns The latest signal or null if not found
45787
+ * @throws Error if RecentAdapter is not enabled
45788
+ */
45789
+ this.getLatestSignal = async (symbol, context) => {
45790
+ backtest.loggerService.info(RECENT_ADAPTER_METHOD_NAME_GET_LATEST_SIGNAL, {
45791
+ symbol,
45792
+ context,
45793
+ });
45794
+ if (!this.enable.hasValue()) {
45795
+ throw new Error("RecentAdapter is not enabled. Call enable() first.");
45796
+ }
45797
+ let result = null;
45798
+ if (result = await RecentBacktest.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, true)) {
45799
+ return result;
45800
+ }
45801
+ if (result = await RecentLive.getLatestSignal(symbol, context.strategyName, context.exchangeName, context.frameName, false)) {
45802
+ return result;
45803
+ }
45804
+ return null;
45805
+ };
45806
+ }
45807
+ }
45808
+ /**
45809
+ * Global singleton instance of RecentAdapter.
45810
+ * Provides unified recent signal management for backtest and live trading.
45811
+ */
45812
+ const Recent = new RecentAdapter();
45813
+ /**
45814
+ * Global singleton instance of RecentLiveAdapter.
45815
+ * Provides live trading recent signal storage with pluggable backends.
45816
+ */
45817
+ const RecentLive = new RecentLiveAdapter();
45818
+ /**
45819
+ * Global singleton instance of RecentBacktestAdapter.
45820
+ * Provides backtest recent signal storage with pluggable backends.
45821
+ */
45822
+ const RecentBacktest = new RecentBacktestAdapter();
45823
+
45824
+ const GET_LATEST_SIGNAL_METHOD_NAME = "signal.getLatestSignal";
45825
+ /**
45826
+ * Returns the latest signal (pending or closed) for the current strategy context.
45827
+ *
45828
+ * Does not distinguish between active and closed signals — returns whichever
45829
+ * was recorded last. Useful for cooldown logic: e.g. skip opening a new position
45830
+ * for 4 hours after a stop-loss by checking the timestamp of the latest signal
45831
+ * regardless of its outcome.
45832
+ *
45833
+ * Searches backtest storage first, then live storage.
45834
+ * Returns null if no signal exists at all.
45835
+ *
45836
+ * Automatically detects backtest/live mode from execution context.
45837
+ *
45838
+ * @param symbol - Trading pair symbol
45839
+ * @returns Promise resolving to the latest signal or null
45840
+ *
45841
+ * @example
45842
+ * ```typescript
45843
+ * import { getLatestSignal } from "backtest-kit";
45844
+ *
45845
+ * const latest = await getLatestSignal("BTCUSDT");
45846
+ * if (latest && Date.now() - latest.closedAt < 4 * 60 * 60 * 1000) {
45847
+ * return; // cooldown after SL — skip new signal for 4 hours
45848
+ * }
45849
+ * ```
45850
+ */
45851
+ async function getLatestSignal(symbol) {
45852
+ backtest.loggerService.info(GET_LATEST_SIGNAL_METHOD_NAME, { symbol });
45853
+ if (!ExecutionContextService.hasContext()) {
45854
+ throw new Error("getLatestSignal requires an execution context");
45855
+ }
45856
+ if (!MethodContextService.hasContext()) {
45857
+ throw new Error("getLatestSignal requires a method context");
45858
+ }
45859
+ const { exchangeName, frameName, strategyName } = backtest.methodContextService.context;
45860
+ return await Recent.getLatestSignal(symbol, { exchangeName, frameName, strategyName });
45861
+ }
45862
+
44933
45863
  const DEFAULT_BM25_K1 = 1.5;
44934
45864
  const DEFAULT_BM25_B = 0.75;
44935
45865
  const DEFAULT_BM25_SCORE = 0.5;
@@ -48180,6 +49110,67 @@ class LogAdapter {
48180
49110
  */
48181
49111
  const Log = new LogAdapter();
48182
49112
 
49113
+ const METHOD_NAME_CREATE_SNAPSHOT = "SessionUtils.createSnapshot";
49114
+ /** List of all global subjects whose listeners should be snapshotted for session isolation */
49115
+ const SUBJECT_ISOLATION_LIST = [
49116
+ activePingSubject,
49117
+ backtestScheduleOpenSubject,
49118
+ breakevenSubject,
49119
+ doneBacktestSubject,
49120
+ doneLiveSubject,
49121
+ errorEmitter,
49122
+ exitEmitter,
49123
+ highestProfitSubject,
49124
+ maxDrawdownSubject,
49125
+ partialLossSubject,
49126
+ partialProfitSubject,
49127
+ performanceEmitter,
49128
+ progressBacktestEmitter,
49129
+ riskSubject,
49130
+ schedulePingSubject,
49131
+ shutdownEmitter,
49132
+ signalBacktestEmitter,
49133
+ signalEmitter,
49134
+ signalLiveEmitter,
49135
+ strategyCommitSubject,
49136
+ syncSubject,
49137
+ validationSubject,
49138
+ ];
49139
+ /**
49140
+ * Creates a snapshot function for a given subject by clearing its internal
49141
+ * events map and returning a restore function that can put the original listeners back.
49142
+ * @param subject The subject to snapshot
49143
+ * @returns A function that restores the subject's original listeners when called
49144
+ */
49145
+ const CREATE_SUBJECT_SNAPSHOT_FN = (subject) => {
49146
+ const emitter = subject["_emitter"];
49147
+ const events = emitter["_events"];
49148
+ emitter["_events"] = {};
49149
+ return () => {
49150
+ emitter["_events"] = events;
49151
+ };
49152
+ };
49153
+ /**
49154
+ * Manages isolation of global event-bus state between backtest sessions.
49155
+ * Allows temporarily detaching all subject subscriptions so that one session
49156
+ * does not interfere with another, then restoring them afterwards.
49157
+ */
49158
+ class SessionUtils {
49159
+ constructor() {
49160
+ /**
49161
+ * Snapshots the current listener state of every global subject by replacing
49162
+ * their internal `_events` map with an empty object.
49163
+ * @returns A restore function that, when called, puts all original listeners back.
49164
+ */
49165
+ this.createSnapshot = () => {
49166
+ backtest.loggerService.log(METHOD_NAME_CREATE_SNAPSHOT);
49167
+ const snapshotList = SUBJECT_ISOLATION_LIST.map(CREATE_SUBJECT_SNAPSHOT_FN);
49168
+ return functoolsKit.compose(...snapshotList);
49169
+ };
49170
+ }
49171
+ }
49172
+ const Session = new SessionUtils();
49173
+
48183
49174
  const SCHEDULE_METHOD_NAME_GET_DATA = "ScheduleUtils.getData";
48184
49175
  const SCHEDULE_METHOD_NAME_GET_REPORT = "ScheduleUtils.getReport";
48185
49176
  const SCHEDULE_METHOD_NAME_DUMP = "ScheduleUtils.dump";
@@ -49755,6 +50746,741 @@ class MaxDrawdownUtils {
49755
50746
  */
49756
50747
  const MaxDrawdown = new MaxDrawdownUtils();
49757
50748
 
50749
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT = "ReflectUtils.getPositionPnlPercent";
50750
+ const REFLECT_METHOD_NAME_GET_POSITION_PNL_COST = "ReflectUtils.getPositionPnlCost";
50751
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE = "ReflectUtils.getPositionHighestProfitPrice";
50752
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP = "ReflectUtils.getPositionHighestProfitTimestamp";
50753
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestPnlPercentage";
50754
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST = "ReflectUtils.getPositionHighestPnlCost";
50755
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN = "ReflectUtils.getPositionHighestProfitBreakeven";
50756
+ const REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES = "ReflectUtils.getPositionDrawdownMinutes";
50757
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES = "ReflectUtils.getPositionHighestProfitMinutes";
50758
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES = "ReflectUtils.getPositionMaxDrawdownMinutes";
50759
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE = "ReflectUtils.getPositionMaxDrawdownPrice";
50760
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP = "ReflectUtils.getPositionMaxDrawdownTimestamp";
50761
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionMaxDrawdownPnlPercentage";
50762
+ const REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionMaxDrawdownPnlCost";
50763
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestProfitDistancePnlPercentage";
50764
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST = "ReflectUtils.getPositionHighestProfitDistancePnlCost";
50765
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE = "ReflectUtils.getPositionHighestMaxDrawdownPnlPercentage";
50766
+ const REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST = "ReflectUtils.getPositionHighestMaxDrawdownPnlCost";
50767
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE = "ReflectUtils.getMaxDrawdownDistancePnlPercentage";
50768
+ const REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST = "ReflectUtils.getMaxDrawdownDistancePnlCost";
50769
+ /**
50770
+ * Utility class for real-time position reflection: PNL, peak profit, and drawdown queries.
50771
+ *
50772
+ * Provides unified access to strategyCoreService position state methods with logging
50773
+ * and full validation (strategy, exchange, frame, risk, actions).
50774
+ * Works for both live and backtest modes via the `backtest` parameter.
50775
+ * Exported as singleton instance for convenient usage.
50776
+ *
50777
+ * @example
50778
+ * ```typescript
50779
+ * import { Reflect } from "backtest-kit";
50780
+ *
50781
+ * // Get current unrealized PNL percentage
50782
+ * const pnl = await Reflect.getPositionPnlPercent(
50783
+ * "BTCUSDT",
50784
+ * 45000,
50785
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50786
+ * );
50787
+ * console.log(`PNL: ${pnl}%`);
50788
+ *
50789
+ * // Get peak profit reached
50790
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50791
+ * "BTCUSDT",
50792
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50793
+ * );
50794
+ * console.log(`Peak PNL: ${peakPnl}%`);
50795
+ * ```
50796
+ */
50797
+ class ReflectUtils {
50798
+ constructor() {
50799
+ /**
50800
+ * Returns the unrealized PNL percentage for the current pending signal at currentPrice.
50801
+ *
50802
+ * Accounts for partial closes, DCA entries, slippage and fees.
50803
+ * Returns null if no pending signal exists.
50804
+ *
50805
+ * @param symbol - Trading pair symbol
50806
+ * @param currentPrice - Current market price
50807
+ * @param context - Execution context with strategyName, exchangeName and frameName
50808
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50809
+ * @returns Promise resolving to PNL percentage or null
50810
+ *
50811
+ * @example
50812
+ * ```typescript
50813
+ * const pnl = await Reflect.getPositionPnlPercent(
50814
+ * "BTCUSDT",
50815
+ * 45000,
50816
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50817
+ * );
50818
+ * console.log(`PNL: ${pnl}%`);
50819
+ * ```
50820
+ */
50821
+ this.getPositionPnlPercent = async (symbol, currentPrice, context, backtest$1 = false) => {
50822
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT, { symbol, currentPrice, context });
50823
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50824
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50825
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50826
+ {
50827
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50828
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT);
50829
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50830
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_PERCENT));
50831
+ }
50832
+ return await backtest.strategyCoreService.getPositionPnlPercent(backtest$1, symbol, currentPrice, context);
50833
+ };
50834
+ /**
50835
+ * Returns the unrealized PNL in dollars for the current pending signal at currentPrice.
50836
+ *
50837
+ * Calculated as: pnlPercentage / 100 × totalInvestedCost.
50838
+ * Accounts for partial closes, DCA entries, slippage and fees.
50839
+ * Returns null if no pending signal exists.
50840
+ *
50841
+ * @param symbol - Trading pair symbol
50842
+ * @param currentPrice - Current market price
50843
+ * @param context - Execution context with strategyName, exchangeName and frameName
50844
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50845
+ * @returns Promise resolving to PNL in dollars or null
50846
+ *
50847
+ * @example
50848
+ * ```typescript
50849
+ * const pnlCost = await Reflect.getPositionPnlCost(
50850
+ * "BTCUSDT",
50851
+ * 45000,
50852
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50853
+ * );
50854
+ * console.log(`PNL: $${pnlCost}`);
50855
+ * ```
50856
+ */
50857
+ this.getPositionPnlCost = async (symbol, currentPrice, context, backtest$1 = false) => {
50858
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_PNL_COST, { symbol, currentPrice, context });
50859
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50860
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50861
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50862
+ {
50863
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50864
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST);
50865
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50866
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_PNL_COST));
50867
+ }
50868
+ return await backtest.strategyCoreService.getPositionPnlCost(backtest$1, symbol, currentPrice, context);
50869
+ };
50870
+ /**
50871
+ * Returns the best price reached in the profit direction during this position's life.
50872
+ *
50873
+ * Returns null if no pending signal exists.
50874
+ *
50875
+ * @param symbol - Trading pair symbol
50876
+ * @param context - Execution context with strategyName, exchangeName and frameName
50877
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50878
+ * @returns Promise resolving to price or null
50879
+ *
50880
+ * @example
50881
+ * ```typescript
50882
+ * const peakPrice = await Reflect.getPositionHighestProfitPrice(
50883
+ * "BTCUSDT",
50884
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50885
+ * );
50886
+ * console.log(`Peak price: ${peakPrice}`);
50887
+ * ```
50888
+ */
50889
+ this.getPositionHighestProfitPrice = async (symbol, context, backtest$1 = false) => {
50890
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE, { symbol, context });
50891
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50892
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50893
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50894
+ {
50895
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50896
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE);
50897
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50898
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_PRICE));
50899
+ }
50900
+ return await backtest.strategyCoreService.getPositionHighestProfitPrice(backtest$1, symbol, context);
50901
+ };
50902
+ /**
50903
+ * Returns the timestamp when the best profit price was recorded during this position's life.
50904
+ *
50905
+ * Returns null if no pending signal exists.
50906
+ *
50907
+ * @param symbol - Trading pair symbol
50908
+ * @param context - Execution context with strategyName, exchangeName and frameName
50909
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50910
+ * @returns Promise resolving to timestamp in milliseconds or null
50911
+ *
50912
+ * @example
50913
+ * ```typescript
50914
+ * const ts = await Reflect.getPositionHighestProfitTimestamp(
50915
+ * "BTCUSDT",
50916
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50917
+ * );
50918
+ * console.log(`Peak at: ${new Date(ts).toISOString()}`);
50919
+ * ```
50920
+ */
50921
+ this.getPositionHighestProfitTimestamp = async (symbol, context, backtest$1 = false) => {
50922
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP, { symbol, context });
50923
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50924
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50925
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50926
+ {
50927
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50928
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP);
50929
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50930
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_TIMESTAMP));
50931
+ }
50932
+ return await backtest.strategyCoreService.getPositionHighestProfitTimestamp(backtest$1, symbol, context);
50933
+ };
50934
+ /**
50935
+ * Returns the PnL percentage at the moment the best profit price was recorded during this position's life.
50936
+ *
50937
+ * Returns null if no pending signal exists.
50938
+ *
50939
+ * @param symbol - Trading pair symbol
50940
+ * @param context - Execution context with strategyName, exchangeName and frameName
50941
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50942
+ * @returns Promise resolving to PnL percentage or null
50943
+ *
50944
+ * @example
50945
+ * ```typescript
50946
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
50947
+ * "BTCUSDT",
50948
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50949
+ * );
50950
+ * console.log(`Peak PNL: ${peakPnl}%`);
50951
+ * ```
50952
+ */
50953
+ this.getPositionHighestPnlPercentage = async (symbol, context, backtest$1 = false) => {
50954
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE, { symbol, context });
50955
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50956
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50957
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50958
+ {
50959
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50960
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE);
50961
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50962
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_PERCENTAGE));
50963
+ }
50964
+ return await backtest.strategyCoreService.getPositionHighestPnlPercentage(backtest$1, symbol, context);
50965
+ };
50966
+ /**
50967
+ * Returns the PnL cost (in quote currency) at the moment the best profit price was recorded during this position's life.
50968
+ *
50969
+ * Returns null if no pending signal exists.
50970
+ *
50971
+ * @param symbol - Trading pair symbol
50972
+ * @param context - Execution context with strategyName, exchangeName and frameName
50973
+ * @param backtest - True if backtest mode, false if live mode (default: false)
50974
+ * @returns Promise resolving to PnL cost in quote currency or null
50975
+ *
50976
+ * @example
50977
+ * ```typescript
50978
+ * const peakCost = await Reflect.getPositionHighestPnlCost(
50979
+ * "BTCUSDT",
50980
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
50981
+ * );
50982
+ * console.log(`Peak PNL: $${peakCost}`);
50983
+ * ```
50984
+ */
50985
+ this.getPositionHighestPnlCost = async (symbol, context, backtest$1 = false) => {
50986
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST, { symbol, context });
50987
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50988
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50989
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50990
+ {
50991
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
50992
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST);
50993
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50994
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PNL_COST));
50995
+ }
50996
+ return await backtest.strategyCoreService.getPositionHighestPnlCost(backtest$1, symbol, context);
50997
+ };
50998
+ /**
50999
+ * Returns whether breakeven was mathematically reachable at the highest profit price.
51000
+ *
51001
+ * Returns null if no pending signal exists.
51002
+ *
51003
+ * @param symbol - Trading pair symbol
51004
+ * @param context - Execution context with strategyName, exchangeName and frameName
51005
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51006
+ * @returns Promise resolving to true if breakeven was reachable at peak, false otherwise, or null
51007
+ *
51008
+ * @example
51009
+ * ```typescript
51010
+ * const wasReachable = await Reflect.getPositionHighestProfitBreakeven(
51011
+ * "BTCUSDT",
51012
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51013
+ * );
51014
+ * console.log(`Breakeven reachable at peak: ${wasReachable}`);
51015
+ * ```
51016
+ */
51017
+ this.getPositionHighestProfitBreakeven = async (symbol, context, backtest$1 = false) => {
51018
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN, { symbol, context });
51019
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
51020
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
51021
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
51022
+ {
51023
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51024
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN);
51025
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
51026
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_BREAKEVEN));
51027
+ }
51028
+ return await backtest.strategyCoreService.getPositionHighestProfitBreakeven(backtest$1, symbol, context);
51029
+ };
51030
+ /**
51031
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
51032
+ *
51033
+ * Returns null if no pending signal exists.
51034
+ *
51035
+ * @param symbol - Trading pair symbol
51036
+ * @param context - Execution context with strategyName, exchangeName and frameName
51037
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51038
+ * @returns Promise resolving to minutes since highest profit price was recorded, or null
51039
+ *
51040
+ * @example
51041
+ * ```typescript
51042
+ * const minutes = await Reflect.getPositionDrawdownMinutes(
51043
+ * "BTCUSDT",
51044
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51045
+ * );
51046
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
51047
+ * ```
51048
+ */
51049
+ this.getPositionDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
51050
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES, { symbol, context });
51051
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
51052
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
51053
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
51054
+ {
51055
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51056
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES);
51057
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
51058
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_DRAWDOWN_MINUTES));
51059
+ }
51060
+ return await backtest.strategyCoreService.getPositionDrawdownMinutes(backtest$1, symbol, context);
51061
+ };
51062
+ /**
51063
+ * Returns the number of minutes elapsed since the highest profit price was recorded.
51064
+ *
51065
+ * Alias for getPositionDrawdownMinutes — measures how long the position has been
51066
+ * pulling back from its peak profit level.
51067
+ * Returns null if no pending signal exists.
51068
+ *
51069
+ * @param symbol - Trading pair symbol
51070
+ * @param context - Execution context with strategyName, exchangeName and frameName
51071
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51072
+ * @returns Promise resolving to minutes since last profit peak or null
51073
+ *
51074
+ * @example
51075
+ * ```typescript
51076
+ * const minutes = await Reflect.getPositionHighestProfitMinutes(
51077
+ * "BTCUSDT",
51078
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51079
+ * );
51080
+ * console.log(`Pulling back from peak for ${minutes} minutes`);
51081
+ * ```
51082
+ */
51083
+ this.getPositionHighestProfitMinutes = async (symbol, context, backtest$1 = false) => {
51084
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES, { symbol, context });
51085
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
51086
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
51087
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
51088
+ {
51089
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51090
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES);
51091
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
51092
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_MINUTES));
51093
+ }
51094
+ return await backtest.strategyCoreService.getPositionHighestProfitMinutes(backtest$1, symbol, context);
51095
+ };
51096
+ /**
51097
+ * Returns the number of minutes elapsed since the worst loss price was recorded.
51098
+ *
51099
+ * Measures how long ago the deepest drawdown point occurred.
51100
+ * Zero when called at the exact moment the trough was set.
51101
+ * Returns null if no pending signal exists.
51102
+ *
51103
+ * @param symbol - Trading pair symbol
51104
+ * @param context - Execution context with strategyName, exchangeName and frameName
51105
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51106
+ * @returns Promise resolving to minutes since last drawdown trough or null
51107
+ *
51108
+ * @example
51109
+ * ```typescript
51110
+ * const minutes = await Reflect.getPositionMaxDrawdownMinutes(
51111
+ * "BTCUSDT",
51112
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51113
+ * );
51114
+ * console.log(`Drawdown trough was ${minutes} minutes ago`);
51115
+ * ```
51116
+ */
51117
+ this.getPositionMaxDrawdownMinutes = async (symbol, context, backtest$1 = false) => {
51118
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES, { symbol, context });
51119
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
51120
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
51121
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
51122
+ {
51123
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51124
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES);
51125
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
51126
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_MINUTES));
51127
+ }
51128
+ return await backtest.strategyCoreService.getPositionMaxDrawdownMinutes(backtest$1, symbol, context);
51129
+ };
51130
+ /**
51131
+ * Returns the worst price reached in the loss direction during this position's life.
51132
+ *
51133
+ * Returns null if no pending signal exists.
51134
+ *
51135
+ * @param symbol - Trading pair symbol
51136
+ * @param context - Execution context with strategyName, exchangeName and frameName
51137
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51138
+ * @returns Promise resolving to price or null
51139
+ *
51140
+ * @example
51141
+ * ```typescript
51142
+ * const troughPrice = await Reflect.getPositionMaxDrawdownPrice(
51143
+ * "BTCUSDT",
51144
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51145
+ * );
51146
+ * console.log(`Worst price: ${troughPrice}`);
51147
+ * ```
51148
+ */
51149
+ this.getPositionMaxDrawdownPrice = async (symbol, context, backtest$1 = false) => {
51150
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE, { symbol, context });
51151
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
51152
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
51153
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
51154
+ {
51155
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51156
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE);
51157
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
51158
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PRICE));
51159
+ }
51160
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPrice(backtest$1, symbol, context);
51161
+ };
51162
+ /**
51163
+ * Returns the timestamp when the worst loss price was recorded during this position's life.
51164
+ *
51165
+ * Returns null if no pending signal exists.
51166
+ *
51167
+ * @param symbol - Trading pair symbol
51168
+ * @param context - Execution context with strategyName, exchangeName and frameName
51169
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51170
+ * @returns Promise resolving to timestamp in milliseconds or null
51171
+ *
51172
+ * @example
51173
+ * ```typescript
51174
+ * const ts = await Reflect.getPositionMaxDrawdownTimestamp(
51175
+ * "BTCUSDT",
51176
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51177
+ * );
51178
+ * console.log(`Worst drawdown at: ${new Date(ts).toISOString()}`);
51179
+ * ```
51180
+ */
51181
+ this.getPositionMaxDrawdownTimestamp = async (symbol, context, backtest$1 = false) => {
51182
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP, { symbol, context });
51183
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
51184
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
51185
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
51186
+ {
51187
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51188
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP);
51189
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
51190
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_TIMESTAMP));
51191
+ }
51192
+ return await backtest.strategyCoreService.getPositionMaxDrawdownTimestamp(backtest$1, symbol, context);
51193
+ };
51194
+ /**
51195
+ * Returns the PnL percentage at the moment the worst loss price was recorded during this position's life.
51196
+ *
51197
+ * Returns null if no pending signal exists.
51198
+ *
51199
+ * @param symbol - Trading pair symbol
51200
+ * @param context - Execution context with strategyName, exchangeName and frameName
51201
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51202
+ * @returns Promise resolving to PnL percentage or null
51203
+ *
51204
+ * @example
51205
+ * ```typescript
51206
+ * const worstPnl = await Reflect.getPositionMaxDrawdownPnlPercentage(
51207
+ * "BTCUSDT",
51208
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51209
+ * );
51210
+ * console.log(`Worst PNL: ${worstPnl}%`);
51211
+ * ```
51212
+ */
51213
+ this.getPositionMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
51214
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
51215
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
51216
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
51217
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
51218
+ {
51219
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51220
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE);
51221
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
51222
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_PERCENTAGE));
51223
+ }
51224
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlPercentage(backtest$1, symbol, context);
51225
+ };
51226
+ /**
51227
+ * Returns the PnL cost (in quote currency) at the moment the worst loss price was recorded during this position's life.
51228
+ *
51229
+ * Returns null if no pending signal exists.
51230
+ *
51231
+ * @param symbol - Trading pair symbol
51232
+ * @param context - Execution context with strategyName, exchangeName and frameName
51233
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51234
+ * @returns Promise resolving to PnL cost in quote currency or null
51235
+ *
51236
+ * @example
51237
+ * ```typescript
51238
+ * const worstCost = await Reflect.getPositionMaxDrawdownPnlCost(
51239
+ * "BTCUSDT",
51240
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51241
+ * );
51242
+ * console.log(`Worst PNL: $${worstCost}`);
51243
+ * ```
51244
+ */
51245
+ this.getPositionMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
51246
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST, { symbol, context });
51247
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
51248
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
51249
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
51250
+ {
51251
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51252
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST);
51253
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
51254
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_MAX_DRAWDOWN_PNL_COST));
51255
+ }
51256
+ return await backtest.strategyCoreService.getPositionMaxDrawdownPnlCost(backtest$1, symbol, context);
51257
+ };
51258
+ /**
51259
+ * Returns the distance in PnL percentage between the current price and the highest profit peak.
51260
+ *
51261
+ * Result is ≥ 0. Returns null if no pending signal exists.
51262
+ *
51263
+ * @param symbol - Trading pair symbol
51264
+ * @param context - Execution context with strategyName, exchangeName and frameName
51265
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51266
+ * @returns Promise resolving to drawdown distance in PnL% (≥ 0) or null
51267
+ *
51268
+ * @example
51269
+ * ```typescript
51270
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlPercentage(
51271
+ * "BTCUSDT",
51272
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51273
+ * );
51274
+ * console.log(`Dropped ${distance}% from peak`);
51275
+ * ```
51276
+ */
51277
+ this.getPositionHighestProfitDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
51278
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE, { symbol, context });
51279
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
51280
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
51281
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
51282
+ {
51283
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51284
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE);
51285
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
51286
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_PERCENTAGE));
51287
+ }
51288
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlPercentage(backtest$1, symbol, context);
51289
+ };
51290
+ /**
51291
+ * Returns the distance in PnL cost between the current price and the highest profit peak.
51292
+ *
51293
+ * Result is ≥ 0. Returns null if no pending signal exists.
51294
+ *
51295
+ * @param symbol - Trading pair symbol
51296
+ * @param context - Execution context with strategyName, exchangeName and frameName
51297
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51298
+ * @returns Promise resolving to drawdown distance in PnL cost (≥ 0) or null
51299
+ *
51300
+ * @example
51301
+ * ```typescript
51302
+ * const distance = await Reflect.getPositionHighestProfitDistancePnlCost(
51303
+ * "BTCUSDT",
51304
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51305
+ * );
51306
+ * console.log(`Dropped $${distance} from peak`);
51307
+ * ```
51308
+ */
51309
+ this.getPositionHighestProfitDistancePnlCost = async (symbol, context, backtest$1 = false) => {
51310
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST, { symbol, context });
51311
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
51312
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
51313
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
51314
+ {
51315
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51316
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST);
51317
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
51318
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_PROFIT_DISTANCE_PNL_COST));
51319
+ }
51320
+ return await backtest.strategyCoreService.getPositionHighestProfitDistancePnlCost(backtest$1, symbol, context);
51321
+ };
51322
+ /**
51323
+ * Returns the distance in PnL percentage between the current price and the worst drawdown trough.
51324
+ *
51325
+ * Result is ≥ 0. Returns null if no pending signal exists.
51326
+ *
51327
+ * @param symbol - Trading pair symbol
51328
+ * @param context - Execution context with strategyName, exchangeName and frameName
51329
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51330
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL% (≥ 0) or null
51331
+ *
51332
+ * @example
51333
+ * ```typescript
51334
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlPercentage(
51335
+ * "BTCUSDT",
51336
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51337
+ * );
51338
+ * console.log(`${distance}% above worst trough`);
51339
+ * ```
51340
+ */
51341
+ this.getPositionHighestMaxDrawdownPnlPercentage = async (symbol, context, backtest$1 = false) => {
51342
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE, { symbol, context });
51343
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
51344
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
51345
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
51346
+ {
51347
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51348
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE);
51349
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
51350
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_PERCENTAGE));
51351
+ }
51352
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlPercentage(backtest$1, symbol, context);
51353
+ };
51354
+ /**
51355
+ * Returns the distance in PnL cost between the current price and the worst drawdown trough.
51356
+ *
51357
+ * Result is ≥ 0. Returns null if no pending signal exists.
51358
+ *
51359
+ * @param symbol - Trading pair symbol
51360
+ * @param context - Execution context with strategyName, exchangeName and frameName
51361
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51362
+ * @returns Promise resolving to recovery distance from worst drawdown trough in PnL cost (≥ 0) or null
51363
+ *
51364
+ * @example
51365
+ * ```typescript
51366
+ * const distance = await Reflect.getPositionHighestMaxDrawdownPnlCost(
51367
+ * "BTCUSDT",
51368
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51369
+ * );
51370
+ * console.log(`$${distance} above worst trough`);
51371
+ * ```
51372
+ */
51373
+ this.getPositionHighestMaxDrawdownPnlCost = async (symbol, context, backtest$1 = false) => {
51374
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST, { symbol, context });
51375
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
51376
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
51377
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
51378
+ {
51379
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51380
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST);
51381
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
51382
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_POSITION_HIGHEST_MAX_DRAWDOWN_PNL_COST));
51383
+ }
51384
+ return await backtest.strategyCoreService.getPositionHighestMaxDrawdownPnlCost(backtest$1, symbol, context);
51385
+ };
51386
+ /**
51387
+ * Returns the peak-to-trough PnL percentage distance between the position's highest profit and deepest drawdown.
51388
+ *
51389
+ * Result is ≥ 0. Returns null if no pending signal exists.
51390
+ *
51391
+ * @param symbol - Trading pair symbol
51392
+ * @param context - Execution context with strategyName, exchangeName and frameName
51393
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51394
+ * @returns Promise resolving to peak-to-trough PnL percentage distance (≥ 0) or null
51395
+ *
51396
+ * @example
51397
+ * ```typescript
51398
+ * const distance = await Reflect.getMaxDrawdownDistancePnlPercentage(
51399
+ * "BTCUSDT",
51400
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51401
+ * );
51402
+ * console.log(`Peak-to-trough: ${distance}%`);
51403
+ * ```
51404
+ */
51405
+ this.getMaxDrawdownDistancePnlPercentage = async (symbol, context, backtest$1 = false) => {
51406
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE, { symbol, context });
51407
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
51408
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
51409
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
51410
+ {
51411
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51412
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE);
51413
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
51414
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_PERCENTAGE));
51415
+ }
51416
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlPercentage(backtest$1, symbol, context);
51417
+ };
51418
+ /**
51419
+ * Returns the peak-to-trough PnL cost distance between the position's highest profit and deepest drawdown.
51420
+ *
51421
+ * Result is ≥ 0. Returns null if no pending signal exists.
51422
+ *
51423
+ * @param symbol - Trading pair symbol
51424
+ * @param context - Execution context with strategyName, exchangeName and frameName
51425
+ * @param backtest - True if backtest mode, false if live mode (default: false)
51426
+ * @returns Promise resolving to peak-to-trough PnL cost distance (≥ 0) or null
51427
+ *
51428
+ * @example
51429
+ * ```typescript
51430
+ * const distance = await Reflect.getMaxDrawdownDistancePnlCost(
51431
+ * "BTCUSDT",
51432
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51433
+ * );
51434
+ * console.log(`Peak-to-trough: $${distance}`);
51435
+ * ```
51436
+ */
51437
+ this.getMaxDrawdownDistancePnlCost = async (symbol, context, backtest$1 = false) => {
51438
+ backtest.loggerService.info(REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST, { symbol, context });
51439
+ backtest.strategyValidationService.validate(context.strategyName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
51440
+ backtest.exchangeValidationService.validate(context.exchangeName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
51441
+ context.frameName && backtest.frameValidationService.validate(context.frameName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
51442
+ {
51443
+ const { riskName, riskList, actions } = backtest.strategySchemaService.get(context.strategyName);
51444
+ riskName && backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST);
51445
+ riskList && riskList.forEach((riskName) => backtest.riskValidationService.validate(riskName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
51446
+ actions && actions.forEach((actionName) => backtest.actionValidationService.validate(actionName, REFLECT_METHOD_NAME_GET_MAX_DRAWDOWN_DISTANCE_PNL_COST));
51447
+ }
51448
+ return await backtest.strategyCoreService.getMaxDrawdownDistancePnlCost(backtest$1, symbol, context);
51449
+ };
51450
+ }
51451
+ }
51452
+ /**
51453
+ * Singleton instance of ReflectUtils for convenient position state queries.
51454
+ *
51455
+ * @example
51456
+ * ```typescript
51457
+ * import { Reflect } from "backtest-kit";
51458
+ *
51459
+ * // Real-time PNL
51460
+ * const pnl = await Reflect.getPositionPnlPercent(
51461
+ * "BTCUSDT",
51462
+ * 45000,
51463
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51464
+ * );
51465
+ * console.log(`PNL: ${pnl}%`);
51466
+ *
51467
+ * // Peak profit
51468
+ * const peakPnl = await Reflect.getPositionHighestPnlPercentage(
51469
+ * "BTCUSDT",
51470
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51471
+ * );
51472
+ * console.log(`Peak PNL: ${peakPnl}%`);
51473
+ *
51474
+ * // Drawdown from peak
51475
+ * const drawdown = await Reflect.getPositionHighestProfitDistancePnlPercentage(
51476
+ * "BTCUSDT",
51477
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "frame1" }
51478
+ * );
51479
+ * console.log(`Dropped ${drawdown}% from peak`);
51480
+ * ```
51481
+ */
51482
+ const Reflect$1 = new ReflectUtils();
51483
+
49758
51484
  /**
49759
51485
  * Utility class containing predefined trading constants for take-profit and stop-loss levels.
49760
51486
  *
@@ -53682,12 +55408,15 @@ const CACHE_METHOD_NAME_RUN = "CacheFnInstance.run";
53682
55408
  const CACHE_METHOD_NAME_FN = "CacheUtils.fn";
53683
55409
  const CACHE_METHOD_NAME_FN_CLEAR = "CacheUtils.fn.clear";
53684
55410
  const CACHE_METHOD_NAME_FN_GC = "CacheUtils.fn.gc";
55411
+ const CACHE_METHOD_NAME_FN_HAS_VALUE = "CacheUtils.fn.hasValue";
53685
55412
  const CACHE_METHOD_NAME_FILE = "CacheUtils.file";
53686
55413
  const CACHE_METHOD_NAME_FILE_CLEAR = "CacheUtils.file.clear";
55414
+ const CACHE_METHOD_NAME_FILE_HAS_VALUE = "CacheUtils.file.hasValue";
53687
55415
  const CACHE_METHOD_NAME_DISPOSE = "CacheUtils.dispose";
53688
55416
  const CACHE_METHOD_NAME_CLEAR = "CacheUtils.clear";
53689
55417
  const CACHE_METHOD_NAME_RESET_COUNTER = "CacheUtils.resetCounter";
53690
55418
  const CACHE_FILE_INSTANCE_METHOD_NAME_RUN = "CacheFileInstance.run";
55419
+ const CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "CacheFileInstance.hasValue";
53691
55420
  const MS_PER_MINUTE$1 = 60000;
53692
55421
  const INTERVAL_MINUTES$1 = {
53693
55422
  "1m": 1,
@@ -53877,6 +55606,36 @@ class CacheFnInstance {
53877
55606
  }
53878
55607
  }
53879
55608
  };
55609
+ /**
55610
+ * Check whether a valid (non-expired) cache entry exists for the current context and arguments.
55611
+ *
55612
+ * Returns `true` if a cached value exists and its interval is still current.
55613
+ * Returns `false` if there is no entry or the cached entry has expired.
55614
+ *
55615
+ * Requires active execution context and method context.
55616
+ *
55617
+ * @param args - Arguments to look up in the cache
55618
+ * @returns `true` if a fresh cached value exists, `false` otherwise
55619
+ */
55620
+ this.hasValue = (...args) => {
55621
+ if (!MethodContextService.hasContext()) {
55622
+ throw new Error("CacheFnInstance hasValue requires method context");
55623
+ }
55624
+ if (!ExecutionContextService.hasContext()) {
55625
+ throw new Error("CacheFnInstance hasValue requires execution context");
55626
+ }
55627
+ const contextKey = CREATE_KEY_FN$1(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
55628
+ const argKey = String(this.key(args));
55629
+ const key = `${contextKey}:${argKey}`;
55630
+ const cached = this._cacheMap.get(key);
55631
+ if (!cached) {
55632
+ return false;
55633
+ }
55634
+ const currentWhen = backtest.executionContextService.context.when;
55635
+ const currentAligned = align$1(currentWhen.getTime(), this.interval);
55636
+ const cachedAligned = align$1(cached.when.getTime(), this.interval);
55637
+ return currentAligned === cachedAligned;
55638
+ };
53880
55639
  /**
53881
55640
  * Garbage collect expired cache entries.
53882
55641
  *
@@ -54001,6 +55760,33 @@ class CacheFileInstance {
54001
55760
  await PersistMeasureAdapter.writeMeasureData({ id: entityKey, data: result, removed: false }, bucket, entityKey);
54002
55761
  return result;
54003
55762
  };
55763
+ /**
55764
+ * Check whether a cached value exists on disk for the given arguments and current interval.
55765
+ *
55766
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
55767
+ * Returns `false` if no record is found.
55768
+ *
55769
+ * Requires active execution context and method context.
55770
+ *
55771
+ * @param args - Arguments forwarded to the key generator
55772
+ * @returns `true` if a cached record exists, `false` otherwise
55773
+ */
55774
+ this.hasValue = async (...args) => {
55775
+ backtest.loggerService.debug(CACHE_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
55776
+ if (!MethodContextService.hasContext()) {
55777
+ throw new Error("CacheFileInstance hasValue requires method context");
55778
+ }
55779
+ if (!ExecutionContextService.hasContext()) {
55780
+ throw new Error("CacheFileInstance hasValue requires execution context");
55781
+ }
55782
+ const [symbol, ...rest] = args;
55783
+ const { when } = backtest.executionContextService.context;
55784
+ const alignedTs = align$1(when.getTime(), this.interval);
55785
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
55786
+ const entityKey = this.key([symbol, alignedTs, ...rest]);
55787
+ const cached = await PersistMeasureAdapter.readMeasureData(bucket, entityKey);
55788
+ return cached !== null;
55789
+ };
54004
55790
  /**
54005
55791
  * Soft-delete all persisted records for this instance's bucket.
54006
55792
  * After this call the next `run()` will recompute and re-cache the value.
@@ -54087,23 +55873,30 @@ class CacheUtils {
54087
55873
  wrappedFn.clear = () => {
54088
55874
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_CLEAR);
54089
55875
  if (!MethodContextService.hasContext()) {
54090
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
54091
- return;
55876
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires method context`);
54092
55877
  }
54093
55878
  if (!ExecutionContextService.hasContext()) {
54094
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
54095
- return;
55879
+ throw new Error(`${CACHE_METHOD_NAME_FN_CLEAR} requires execution context`);
54096
55880
  }
54097
55881
  this._getFnInstance.get(run)?.clear();
54098
55882
  };
54099
55883
  wrappedFn.gc = () => {
54100
55884
  backtest.loggerService.info(CACHE_METHOD_NAME_FN_GC);
54101
55885
  if (!ExecutionContextService.hasContext()) {
54102
- backtest.loggerService.warn(`${CACHE_METHOD_NAME_FN_GC} called without execution context, skipping`);
54103
- return;
55886
+ throw new Error(`${CACHE_METHOD_NAME_FN_GC} requires execution context`);
54104
55887
  }
54105
55888
  return this._getFnInstance.get(run)?.gc();
54106
55889
  };
55890
+ wrappedFn.hasValue = (...args) => {
55891
+ backtest.loggerService.info(CACHE_METHOD_NAME_FN_HAS_VALUE);
55892
+ if (!MethodContextService.hasContext()) {
55893
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires method context`);
55894
+ }
55895
+ if (!ExecutionContextService.hasContext()) {
55896
+ throw new Error(`${CACHE_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
55897
+ }
55898
+ return this._getFnInstance.get(run)?.hasValue(...args) ?? false;
55899
+ };
54107
55900
  return wrappedFn;
54108
55901
  };
54109
55902
  /**
@@ -54155,8 +55948,25 @@ class CacheUtils {
54155
55948
  };
54156
55949
  wrappedFn.clear = async () => {
54157
55950
  backtest.loggerService.info(CACHE_METHOD_NAME_FILE_CLEAR);
55951
+ if (!MethodContextService.hasContext()) {
55952
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires method context`);
55953
+ }
55954
+ if (!ExecutionContextService.hasContext()) {
55955
+ throw new Error(`${CACHE_METHOD_NAME_FILE_CLEAR} requires execution context`);
55956
+ }
54158
55957
  await this._getFileInstance.get(run)?.clear();
54159
55958
  };
55959
+ wrappedFn.hasValue = async (...args) => {
55960
+ backtest.loggerService.info(CACHE_METHOD_NAME_FILE_HAS_VALUE);
55961
+ if (!MethodContextService.hasContext()) {
55962
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
55963
+ }
55964
+ if (!ExecutionContextService.hasContext()) {
55965
+ throw new Error(`${CACHE_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
55966
+ }
55967
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
55968
+ return await instance.hasValue(...args);
55969
+ };
54160
55970
  return wrappedFn;
54161
55971
  };
54162
55972
  /**
@@ -54225,11 +56035,14 @@ const Cache = new CacheUtils();
54225
56035
 
54226
56036
  const INTERVAL_METHOD_NAME_RUN = "IntervalFnInstance.run";
54227
56037
  const INTERVAL_FILE_INSTANCE_METHOD_NAME_RUN = "IntervalFileInstance.run";
56038
+ const INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE = "IntervalFileInstance.hasValue";
54228
56039
  const INTERVAL_METHOD_NAME_FN = "IntervalUtils.fn";
54229
56040
  const INTERVAL_METHOD_NAME_FN_CLEAR = "IntervalUtils.fn.clear";
54230
56041
  const INTERVAL_METHOD_NAME_FN_GC = "IntervalUtils.fn.gc";
56042
+ const INTERVAL_METHOD_NAME_FN_HAS_VALUE = "IntervalUtils.fn.hasValue";
54231
56043
  const INTERVAL_METHOD_NAME_FILE = "IntervalUtils.file";
54232
56044
  const INTERVAL_METHOD_NAME_FILE_CLEAR = "IntervalUtils.file.clear";
56045
+ const INTERVAL_METHOD_NAME_FILE_HAS_VALUE = "IntervalUtils.file.hasValue";
54233
56046
  const INTERVAL_METHOD_NAME_DISPOSE = "IntervalUtils.dispose";
54234
56047
  const INTERVAL_METHOD_NAME_CLEAR = "IntervalUtils.clear";
54235
56048
  const INTERVAL_METHOD_NAME_RESET_COUNTER = "IntervalUtils.resetCounter";
@@ -54387,6 +56200,31 @@ class IntervalFnInstance {
54387
56200
  }
54388
56201
  }
54389
56202
  };
56203
+ /**
56204
+ * Check whether the function has already fired for the current interval and context.
56205
+ *
56206
+ * Returns `true` if the function fired (non-null result) within the current interval boundary.
56207
+ * Returns `false` if there is no recorded firing for this interval.
56208
+ *
56209
+ * Requires active method context and execution context.
56210
+ *
56211
+ * @param args - Arguments to look up in the state map
56212
+ * @returns `true` if the function has already fired this interval, `false` otherwise
56213
+ */
56214
+ this.hasValue = (...args) => {
56215
+ if (!MethodContextService.hasContext()) {
56216
+ throw new Error("IntervalFnInstance hasValue requires method context");
56217
+ }
56218
+ if (!ExecutionContextService.hasContext()) {
56219
+ throw new Error("IntervalFnInstance hasValue requires execution context");
56220
+ }
56221
+ const contextKey = CREATE_KEY_FN(backtest.methodContextService.context.strategyName, backtest.methodContextService.context.exchangeName, backtest.methodContextService.context.frameName, backtest.executionContextService.context.backtest);
56222
+ const currentWhen = backtest.executionContextService.context.when;
56223
+ const currentAligned = align(currentWhen.getTime(), this.interval);
56224
+ const argKey = this.key(args);
56225
+ const stateKey = `${contextKey}:${argKey}`;
56226
+ return this._stateMap.get(stateKey) === currentAligned;
56227
+ };
54390
56228
  /**
54391
56229
  * Garbage collect expired state entries.
54392
56230
  *
@@ -54508,6 +56346,33 @@ class IntervalFileInstance {
54508
56346
  }
54509
56347
  return result;
54510
56348
  };
56349
+ /**
56350
+ * Check whether the function has already fired for the current interval on disk.
56351
+ *
56352
+ * Returns `true` if a persisted record exists for the current aligned timestamp.
56353
+ * Returns `false` if no record is found.
56354
+ *
56355
+ * Requires active execution context and method context.
56356
+ *
56357
+ * @param args - Arguments forwarded to the key generator
56358
+ * @returns `true` if a fired record exists, `false` otherwise
56359
+ */
56360
+ this.hasValue = async (...args) => {
56361
+ backtest.loggerService.debug(INTERVAL_FILE_INSTANCE_METHOD_NAME_HAS_VALUE, { args });
56362
+ if (!MethodContextService.hasContext()) {
56363
+ throw new Error("IntervalFileInstance hasValue requires method context");
56364
+ }
56365
+ if (!ExecutionContextService.hasContext()) {
56366
+ throw new Error("IntervalFileInstance hasValue requires execution context");
56367
+ }
56368
+ const [symbol, ...rest] = args;
56369
+ const { when } = backtest.executionContextService.context;
56370
+ const alignedMs = align(when.getTime(), this.interval);
56371
+ const bucket = `${this.name}_${this.interval}_${this.index}`;
56372
+ const entityKey = this.key([symbol, alignedMs, ...rest]);
56373
+ const cached = await PersistIntervalAdapter.readIntervalData(bucket, entityKey);
56374
+ return cached !== null;
56375
+ };
54511
56376
  /**
54512
56377
  * Soft-delete all persisted records for this instance's bucket.
54513
56378
  * After this call the function will fire again on the next `run()`.
@@ -54588,23 +56453,30 @@ class IntervalUtils {
54588
56453
  wrappedFn.clear = () => {
54589
56454
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_CLEAR);
54590
56455
  if (!MethodContextService.hasContext()) {
54591
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without method context, skipping`);
54592
- return;
56456
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires method context`);
54593
56457
  }
54594
56458
  if (!ExecutionContextService.hasContext()) {
54595
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_CLEAR} called without execution context, skipping`);
54596
- return;
56459
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_CLEAR} requires execution context`);
54597
56460
  }
54598
56461
  this._getInstance.get(run)?.clear();
54599
56462
  };
54600
56463
  wrappedFn.gc = () => {
54601
56464
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_GC);
54602
56465
  if (!ExecutionContextService.hasContext()) {
54603
- backtest.loggerService.warn(`${INTERVAL_METHOD_NAME_FN_GC} called without execution context, skipping`);
54604
- return;
56466
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_GC} requires execution context`);
54605
56467
  }
54606
56468
  return this._getInstance.get(run)?.gc();
54607
56469
  };
56470
+ wrappedFn.hasValue = (...args) => {
56471
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FN_HAS_VALUE);
56472
+ if (!MethodContextService.hasContext()) {
56473
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires method context`);
56474
+ }
56475
+ if (!ExecutionContextService.hasContext()) {
56476
+ throw new Error(`${INTERVAL_METHOD_NAME_FN_HAS_VALUE} requires execution context`);
56477
+ }
56478
+ return this._getInstance.get(run)?.hasValue(...args) ?? false;
56479
+ };
54608
56480
  return wrappedFn;
54609
56481
  };
54610
56482
  /**
@@ -54647,8 +56519,25 @@ class IntervalUtils {
54647
56519
  };
54648
56520
  wrappedFn.clear = async () => {
54649
56521
  backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_CLEAR);
56522
+ if (!MethodContextService.hasContext()) {
56523
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires method context`);
56524
+ }
56525
+ if (!ExecutionContextService.hasContext()) {
56526
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_CLEAR} requires execution context`);
56527
+ }
54650
56528
  await this._getFileInstance.get(run)?.clear();
54651
56529
  };
56530
+ wrappedFn.hasValue = async (...args) => {
56531
+ backtest.loggerService.info(INTERVAL_METHOD_NAME_FILE_HAS_VALUE);
56532
+ if (!MethodContextService.hasContext()) {
56533
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires method context`);
56534
+ }
56535
+ if (!ExecutionContextService.hasContext()) {
56536
+ throw new Error(`${INTERVAL_METHOD_NAME_FILE_HAS_VALUE} requires execution context`);
56537
+ }
56538
+ const instance = this._getFileInstance(run, context.interval, context.name, context.key);
56539
+ return await instance.hasValue(...args);
56540
+ };
54652
56541
  return wrappedFn;
54653
56542
  };
54654
56543
  /**
@@ -55858,17 +57747,23 @@ exports.PersistMeasureAdapter = PersistMeasureAdapter;
55858
57747
  exports.PersistMemoryAdapter = PersistMemoryAdapter;
55859
57748
  exports.PersistNotificationAdapter = PersistNotificationAdapter;
55860
57749
  exports.PersistPartialAdapter = PersistPartialAdapter;
57750
+ exports.PersistRecentAdapter = PersistRecentAdapter;
55861
57751
  exports.PersistRiskAdapter = PersistRiskAdapter;
55862
57752
  exports.PersistScheduleAdapter = PersistScheduleAdapter;
55863
57753
  exports.PersistSignalAdapter = PersistSignalAdapter;
55864
57754
  exports.PersistStorageAdapter = PersistStorageAdapter;
55865
57755
  exports.Position = Position;
55866
57756
  exports.PositionSize = PositionSize;
57757
+ exports.Recent = Recent;
57758
+ exports.RecentBacktest = RecentBacktest;
57759
+ exports.RecentLive = RecentLive;
57760
+ exports.Reflect = Reflect$1;
55867
57761
  exports.Report = Report;
55868
57762
  exports.ReportBase = ReportBase;
55869
57763
  exports.ReportWriter = ReportWriter;
55870
57764
  exports.Risk = Risk;
55871
57765
  exports.Schedule = Schedule;
57766
+ exports.Session = Session;
55872
57767
  exports.Storage = Storage;
55873
57768
  exports.StorageBacktest = StorageBacktest;
55874
57769
  exports.StorageLive = StorageLive;
@@ -55922,6 +57817,9 @@ exports.getDefaultConfig = getDefaultConfig;
55922
57817
  exports.getEffectivePriceOpen = getEffectivePriceOpen;
55923
57818
  exports.getExchangeSchema = getExchangeSchema;
55924
57819
  exports.getFrameSchema = getFrameSchema;
57820
+ exports.getLatestSignal = getLatestSignal;
57821
+ exports.getMaxDrawdownDistancePnlCost = getMaxDrawdownDistancePnlCost;
57822
+ exports.getMaxDrawdownDistancePnlPercentage = getMaxDrawdownDistancePnlPercentage;
55925
57823
  exports.getMode = getMode;
55926
57824
  exports.getNextCandles = getNextCandles;
55927
57825
  exports.getOrderBook = getOrderBook;