backtest-kit 1.10.3 → 1.10.4

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.
package/build/index.cjs CHANGED
@@ -2676,193 +2676,167 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
2676
2676
  slClosed: slClosed + percentToClose,
2677
2677
  });
2678
2678
  };
2679
- const TRAILING_STOP_FN = (self, signal, percentShift) => {
2680
- // Get current effective stop-loss (trailing or original)
2681
- const currentStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
2682
- // Calculate distance between entry and CURRENT stop-loss AS PERCENTAGE of entry price
2683
- const currentSlDistancePercent = Math.abs((signal.priceOpen - currentStopLoss) / signal.priceOpen * 100);
2684
- // Calculate new stop-loss distance percentage by adding shift to CURRENT distance
2679
+ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
2680
+ // CRITICAL: Always calculate from ORIGINAL SL, not from current trailing SL
2681
+ // This prevents error accumulation on repeated calls
2682
+ const originalSlDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
2683
+ // Calculate new stop-loss distance percentage by adding shift to ORIGINAL distance
2685
2684
  // Negative percentShift: reduces distance % (tightens stop, moves SL toward entry or beyond)
2686
2685
  // Positive percentShift: increases distance % (loosens stop, moves SL away from entry)
2687
- const newSlDistancePercent = currentSlDistancePercent + percentShift;
2686
+ const newSlDistancePercent = originalSlDistancePercent + percentShift;
2688
2687
  // Calculate new stop-loss price based on new distance percentage
2689
2688
  // Negative newSlDistancePercent means SL crosses entry into profit zone
2690
2689
  let newStopLoss;
2691
2690
  if (signal.position === "long") {
2692
2691
  // LONG: SL is below entry (or above entry if in profit zone)
2693
2692
  // Formula: entry * (1 - newDistance%)
2694
- // Example: entry=100, currentSL=95 (5%), shift=-3% → newDistance=2% → 100 * 0.98 = 98 (tighter)
2693
+ // Example: entry=100, originalSL=90 (10%), shift=-5% → newDistance=5% → 100 * 0.95 = 95 (tighter)
2695
2694
  newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
2696
2695
  }
2697
2696
  else {
2698
2697
  // SHORT: SL is above entry (or below entry if in profit zone)
2699
2698
  // Formula: entry * (1 + newDistance%)
2700
- // Example: entry=100, currentSL=105 (5%), shift=-3% → newDistance=2% → 100 * 1.02 = 102 (tighter)
2699
+ // Example: entry=100, originalSL=110 (10%), shift=-5% → newDistance=5% → 100 * 1.05 = 105 (tighter)
2701
2700
  newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
2702
2701
  }
2703
- // Determine if this is the first trailing stop call (direction not set yet)
2704
- const isFirstCall = signal._trailingPriceStopLoss === undefined;
2702
+ const currentTrailingSL = signal._trailingPriceStopLoss;
2703
+ const isFirstCall = currentTrailingSL === undefined;
2705
2704
  if (isFirstCall) {
2706
- // First call: set the direction and update SL unconditionally
2705
+ // First call: set trailing SL unconditionally
2707
2706
  signal._trailingPriceStopLoss = newStopLoss;
2708
- self.params.logger.info("TRAILING_STOP_FN executed (first call - direction set)", {
2707
+ self.params.logger.info("TRAILING_STOP_FN executed (first call)", {
2709
2708
  signalId: signal.id,
2710
2709
  position: signal.position,
2711
2710
  priceOpen: signal.priceOpen,
2712
2711
  originalStopLoss: signal.priceStopLoss,
2713
- currentDistancePercent: currentSlDistancePercent,
2714
- previousStopLoss: currentStopLoss,
2712
+ originalDistancePercent: originalSlDistancePercent,
2715
2713
  newStopLoss,
2716
2714
  newDistancePercent: newSlDistancePercent,
2717
2715
  percentShift,
2718
2716
  inProfitZone: signal.position === "long" ? newStopLoss > signal.priceOpen : newStopLoss < signal.priceOpen,
2719
- direction: newStopLoss > currentStopLoss ? "up" : "down",
2720
2717
  });
2718
+ return true;
2721
2719
  }
2722
2720
  else {
2723
- // Subsequent calls: only update if new SL continues in the same direction
2724
- // Determine initial direction: "closer" or "farther" relative to entry
2725
- let initialDirection;
2726
- if (signal.position === "long") {
2727
- // LONG: closer = SL closer to entry = higher SL value (moving up)
2728
- initialDirection = signal._trailingPriceStopLoss > signal.priceStopLoss ? "closer" : "farther";
2729
- }
2730
- else {
2731
- // SHORT: closer = SL closer to entry = lower SL value (moving down)
2732
- initialDirection = signal._trailingPriceStopLoss < signal.priceStopLoss ? "closer" : "farther";
2733
- }
2734
- // Determine new direction
2735
- let newDirection;
2721
+ // CRITICAL: Larger percentShift absorbs smaller one
2722
+ // For LONG: higher SL (closer to entry) absorbs lower one
2723
+ // For SHORT: lower SL (closer to entry) absorbs higher one
2724
+ let shouldUpdate = false;
2736
2725
  if (signal.position === "long") {
2737
- // LONG: closer = higher SL value
2738
- newDirection = newStopLoss > currentStopLoss ? "closer" : "farther";
2726
+ // LONG: update only if new SL is higher (better protection)
2727
+ shouldUpdate = newStopLoss > currentTrailingSL;
2739
2728
  }
2740
2729
  else {
2741
- // SHORT: closer = lower SL value
2742
- newDirection = newStopLoss < currentStopLoss ? "closer" : "farther";
2730
+ // SHORT: update only if new SL is lower (better protection)
2731
+ shouldUpdate = newStopLoss < currentTrailingSL;
2743
2732
  }
2744
- // Only allow continuation in same direction
2745
- const shouldUpdate = initialDirection === newDirection;
2746
2733
  if (!shouldUpdate) {
2747
- self.params.logger.debug("TRAILING_STOP_FN: new SL not in same direction, skipping", {
2734
+ self.params.logger.debug("TRAILING_STOP_FN: new SL not better than current, skipping", {
2748
2735
  signalId: signal.id,
2749
2736
  position: signal.position,
2750
- currentStopLoss,
2737
+ currentTrailingSL,
2751
2738
  newStopLoss,
2752
2739
  percentShift,
2753
- initialDirection,
2754
- attemptedDirection: newDirection,
2740
+ reason: "larger percentShift absorbs smaller one",
2755
2741
  });
2756
- return;
2742
+ return false;
2757
2743
  }
2758
2744
  // Update trailing stop-loss
2745
+ const previousTrailingSL = signal._trailingPriceStopLoss;
2759
2746
  signal._trailingPriceStopLoss = newStopLoss;
2760
2747
  self.params.logger.info("TRAILING_STOP_FN executed", {
2761
2748
  signalId: signal.id,
2762
2749
  position: signal.position,
2763
2750
  priceOpen: signal.priceOpen,
2764
2751
  originalStopLoss: signal.priceStopLoss,
2765
- currentDistancePercent: currentSlDistancePercent,
2766
- previousStopLoss: currentStopLoss,
2752
+ originalDistancePercent: originalSlDistancePercent,
2753
+ previousTrailingSL,
2767
2754
  newStopLoss,
2768
2755
  newDistancePercent: newSlDistancePercent,
2769
2756
  percentShift,
2770
2757
  inProfitZone: signal.position === "long" ? newStopLoss > signal.priceOpen : newStopLoss < signal.priceOpen,
2771
- direction: initialDirection,
2772
2758
  });
2759
+ return true;
2773
2760
  }
2774
2761
  };
2775
- const TRAILING_PROFIT_FN = (self, signal, percentShift) => {
2776
- // Get current effective take-profit (trailing or original)
2777
- const currentTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
2778
- // Calculate distance between entry and CURRENT take-profit AS PERCENTAGE of entry price
2779
- const currentTpDistancePercent = Math.abs((currentTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
2780
- // Calculate new take-profit distance percentage by adding shift to CURRENT distance
2762
+ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
2763
+ // CRITICAL: Always calculate from ORIGINAL TP, not from current trailing TP
2764
+ // This prevents error accumulation on repeated calls
2765
+ const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
2766
+ // Calculate new take-profit distance percentage by adding shift to ORIGINAL distance
2781
2767
  // Negative percentShift: reduces distance % (brings TP closer to entry)
2782
2768
  // Positive percentShift: increases distance % (moves TP further from entry)
2783
- const newTpDistancePercent = currentTpDistancePercent + percentShift;
2769
+ const newTpDistancePercent = originalTpDistancePercent + percentShift;
2784
2770
  // Calculate new take-profit price based on new distance percentage
2785
2771
  let newTakeProfit;
2786
2772
  if (signal.position === "long") {
2787
2773
  // LONG: TP is above entry
2788
2774
  // Formula: entry * (1 + newDistance%)
2789
- // Example: entry=100, currentTP=115 (15%), shift=-3% → newDistance=12% → 100 * 1.12 = 112 (closer)
2775
+ // Example: entry=100, originalTP=110 (10%), shift=-3% → newDistance=7% → 100 * 1.07 = 107 (closer)
2790
2776
  newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
2791
2777
  }
2792
2778
  else {
2793
2779
  // SHORT: TP is below entry
2794
2780
  // Formula: entry * (1 - newDistance%)
2795
- // Example: entry=100, currentTP=85 (15%), shift=-3% → newDistance=12% → 100 * 0.88 = 88 (closer)
2781
+ // Example: entry=100, originalTP=90 (10%), shift=-3% → newDistance=7% → 100 * 0.93 = 93 (closer)
2796
2782
  newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
2797
2783
  }
2798
- // Determine if this is the first trailing profit call (direction not set yet)
2799
- const isFirstCall = signal._trailingPriceTakeProfit === undefined;
2784
+ const currentTrailingTP = signal._trailingPriceTakeProfit;
2785
+ const isFirstCall = currentTrailingTP === undefined;
2800
2786
  if (isFirstCall) {
2801
- // First call: set the direction and update TP unconditionally
2787
+ // First call: set trailing TP unconditionally
2802
2788
  signal._trailingPriceTakeProfit = newTakeProfit;
2803
- self.params.logger.info("TRAILING_PROFIT_FN executed (first call - direction set)", {
2789
+ self.params.logger.info("TRAILING_PROFIT_FN executed (first call)", {
2804
2790
  signalId: signal.id,
2805
2791
  position: signal.position,
2806
2792
  priceOpen: signal.priceOpen,
2807
2793
  originalTakeProfit: signal.priceTakeProfit,
2808
- currentDistancePercent: currentTpDistancePercent,
2809
- previousTakeProfit: currentTakeProfit,
2794
+ originalDistancePercent: originalTpDistancePercent,
2810
2795
  newTakeProfit,
2811
2796
  newDistancePercent: newTpDistancePercent,
2812
2797
  percentShift,
2813
- direction: newTakeProfit > currentTakeProfit ? "up" : "down",
2814
2798
  });
2799
+ return true;
2815
2800
  }
2816
2801
  else {
2817
- // Subsequent calls: only update if new TP continues in the same direction
2818
- // Determine initial direction: "closer" or "farther" relative to entry
2819
- let initialDirection;
2820
- if (signal.position === "long") {
2821
- // LONG: closer = TP closer to entry = lower TP value
2822
- initialDirection = signal._trailingPriceTakeProfit < signal.priceTakeProfit ? "closer" : "farther";
2823
- }
2824
- else {
2825
- // SHORT: closer = TP closer to entry = higher TP value
2826
- initialDirection = signal._trailingPriceTakeProfit > signal.priceTakeProfit ? "closer" : "farther";
2827
- }
2828
- // Determine new direction
2829
- let newDirection;
2802
+ // CRITICAL: Larger percentShift absorbs smaller one
2803
+ // For LONG: lower TP (closer to entry) absorbs higher one
2804
+ // For SHORT: higher TP (closer to entry) absorbs lower one
2805
+ let shouldUpdate = false;
2830
2806
  if (signal.position === "long") {
2831
- // LONG: closer = lower TP value
2832
- newDirection = newTakeProfit < currentTakeProfit ? "closer" : "farther";
2807
+ // LONG: update only if new TP is lower (closer to entry, more conservative)
2808
+ shouldUpdate = newTakeProfit < currentTrailingTP;
2833
2809
  }
2834
2810
  else {
2835
- // SHORT: closer = higher TP value
2836
- newDirection = newTakeProfit > currentTakeProfit ? "closer" : "farther";
2811
+ // SHORT: update only if new TP is higher (closer to entry, more conservative)
2812
+ shouldUpdate = newTakeProfit > currentTrailingTP;
2837
2813
  }
2838
- // Only allow continuation in same direction
2839
- const shouldUpdate = initialDirection === newDirection;
2840
2814
  if (!shouldUpdate) {
2841
- self.params.logger.debug("TRAILING_PROFIT_FN: new TP not in same direction, skipping", {
2815
+ self.params.logger.debug("TRAILING_PROFIT_FN: new TP not better than current, skipping", {
2842
2816
  signalId: signal.id,
2843
2817
  position: signal.position,
2844
- currentTakeProfit,
2818
+ currentTrailingTP,
2845
2819
  newTakeProfit,
2846
2820
  percentShift,
2847
- initialDirection,
2848
- attemptedDirection: newDirection,
2821
+ reason: "larger percentShift absorbs smaller one",
2849
2822
  });
2850
- return;
2823
+ return false;
2851
2824
  }
2852
2825
  // Update trailing take-profit
2826
+ const previousTrailingTP = signal._trailingPriceTakeProfit;
2853
2827
  signal._trailingPriceTakeProfit = newTakeProfit;
2854
2828
  self.params.logger.info("TRAILING_PROFIT_FN executed", {
2855
2829
  signalId: signal.id,
2856
2830
  position: signal.position,
2857
2831
  priceOpen: signal.priceOpen,
2858
2832
  originalTakeProfit: signal.priceTakeProfit,
2859
- currentDistancePercent: currentTpDistancePercent,
2860
- previousTakeProfit: currentTakeProfit,
2833
+ originalDistancePercent: originalTpDistancePercent,
2834
+ previousTrailingTP,
2861
2835
  newTakeProfit,
2862
2836
  newDistancePercent: newTpDistancePercent,
2863
2837
  percentShift,
2864
- direction: initialDirection,
2865
2838
  });
2839
+ return true;
2866
2840
  }
2867
2841
  };
2868
2842
  const BREAKEVEN_FN = (self, signal, currentPrice) => {
@@ -3212,7 +3186,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_FN = async (self, scheduled, activationTimestamp
3212
3186
  _isScheduled: false,
3213
3187
  };
3214
3188
  await self.setPendingSignal(activatedSignal);
3215
- await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, activationTime, self.params.execution.context.backtest);
3189
+ await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, activatedSignal, activationTime, self.params.execution.context.backtest);
3216
3190
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, self._pendingSignal, self._pendingSignal.priceOpen, activationTime, self.params.execution.context.backtest);
3217
3191
  const result = {
3218
3192
  action: "opened",
@@ -3412,13 +3386,20 @@ const CALL_IDLE_CALLBACKS_FN = functoolsKit.trycatch(async (self, symbol, curren
3412
3386
  errorEmitter.next(error);
3413
3387
  },
3414
3388
  });
3415
- const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, timestamp, backtest) => {
3389
+ const CALL_RISK_ADD_SIGNAL_FN = functoolsKit.trycatch(async (self, symbol, signal, timestamp, backtest) => {
3416
3390
  await ExecutionContextService.runInContext(async () => {
3417
3391
  await self.params.risk.addSignal(symbol, {
3418
3392
  strategyName: self.params.method.context.strategyName,
3419
3393
  riskName: self.params.riskName,
3420
3394
  exchangeName: self.params.method.context.exchangeName,
3421
3395
  frameName: self.params.method.context.frameName,
3396
+ }, {
3397
+ position: signal.position,
3398
+ priceOpen: signal.priceOpen,
3399
+ priceStopLoss: signal.priceStopLoss,
3400
+ priceTakeProfit: signal.priceTakeProfit,
3401
+ minuteEstimatedTime: signal.minuteEstimatedTime,
3402
+ openTimestamp: timestamp,
3422
3403
  });
3423
3404
  }, {
3424
3405
  when: new Date(timestamp),
@@ -3652,7 +3633,7 @@ const OPEN_NEW_PENDING_SIGNAL_FN = async (self, signal) => {
3652
3633
  if (await functoolsKit.not(CALL_RISK_CHECK_SIGNAL_FN(self, self.params.execution.context.symbol, signal, signal.priceOpen, currentTime, self.params.execution.context.backtest))) {
3653
3634
  return null;
3654
3635
  }
3655
- await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, currentTime, self.params.execution.context.backtest);
3636
+ await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, signal, currentTime, self.params.execution.context.backtest);
3656
3637
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, signal.priceOpen, currentTime, self.params.execution.context.backtest);
3657
3638
  const result = {
3658
3639
  action: "opened",
@@ -3880,7 +3861,7 @@ const ACTIVATE_SCHEDULED_SIGNAL_IN_BACKTEST_FN = async (self, scheduled, activat
3880
3861
  _isScheduled: false,
3881
3862
  };
3882
3863
  await self.setPendingSignal(activatedSignal);
3883
- await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, activationTime, self.params.execution.context.backtest);
3864
+ await CALL_RISK_ADD_SIGNAL_FN(self, self.params.execution.context.symbol, activatedSignal, activationTime, self.params.execution.context.backtest);
3884
3865
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, activatedSignal, activatedSignal.priceOpen, activationTime, self.params.execution.context.backtest);
3885
3866
  return true;
3886
3867
  };
@@ -4783,6 +4764,28 @@ class ClientStrategy {
4783
4764
  throw new Error(`ClientStrategy partialProfit: For SHORT position, currentPrice (${currentPrice}) must be < priceOpen (${this._pendingSignal.priceOpen})`);
4784
4765
  }
4785
4766
  }
4767
+ // Check if currentPrice already crossed take profit level
4768
+ const effectiveTakeProfit = this._pendingSignal._trailingPriceTakeProfit ?? this._pendingSignal.priceTakeProfit;
4769
+ if (this._pendingSignal.position === "long" && currentPrice >= effectiveTakeProfit) {
4770
+ this.params.logger.debug("ClientStrategy partialProfit: price already at/above TP, skipping partial close", {
4771
+ signalId: this._pendingSignal.id,
4772
+ position: this._pendingSignal.position,
4773
+ currentPrice,
4774
+ effectiveTakeProfit,
4775
+ reason: "currentPrice >= effectiveTakeProfit (LONG position)"
4776
+ });
4777
+ return;
4778
+ }
4779
+ if (this._pendingSignal.position === "short" && currentPrice <= effectiveTakeProfit) {
4780
+ this.params.logger.debug("ClientStrategy partialProfit: price already at/below TP, skipping partial close", {
4781
+ signalId: this._pendingSignal.id,
4782
+ position: this._pendingSignal.position,
4783
+ currentPrice,
4784
+ effectiveTakeProfit,
4785
+ reason: "currentPrice <= effectiveTakeProfit (SHORT position)"
4786
+ });
4787
+ return;
4788
+ }
4786
4789
  // Execute partial close logic
4787
4790
  PARTIAL_PROFIT_FN(this, this._pendingSignal, percentToClose, currentPrice);
4788
4791
  // Persist updated signal state (inline setPendingSignal content)
@@ -4875,6 +4878,28 @@ class ClientStrategy {
4875
4878
  throw new Error(`ClientStrategy partialLoss: For SHORT position, currentPrice (${currentPrice}) must be > priceOpen (${this._pendingSignal.priceOpen})`);
4876
4879
  }
4877
4880
  }
4881
+ // Check if currentPrice already crossed stop loss level
4882
+ const effectiveStopLoss = this._pendingSignal._trailingPriceStopLoss ?? this._pendingSignal.priceStopLoss;
4883
+ if (this._pendingSignal.position === "long" && currentPrice <= effectiveStopLoss) {
4884
+ this.params.logger.debug("ClientStrategy partialLoss: price already at/below SL, skipping partial close", {
4885
+ signalId: this._pendingSignal.id,
4886
+ position: this._pendingSignal.position,
4887
+ currentPrice,
4888
+ effectiveStopLoss,
4889
+ reason: "currentPrice <= effectiveStopLoss (LONG position)"
4890
+ });
4891
+ return;
4892
+ }
4893
+ if (this._pendingSignal.position === "short" && currentPrice >= effectiveStopLoss) {
4894
+ this.params.logger.debug("ClientStrategy partialLoss: price already at/above SL, skipping partial close", {
4895
+ signalId: this._pendingSignal.id,
4896
+ position: this._pendingSignal.position,
4897
+ currentPrice,
4898
+ effectiveStopLoss,
4899
+ reason: "currentPrice >= effectiveStopLoss (SHORT position)"
4900
+ });
4901
+ return;
4902
+ }
4878
4903
  // Execute partial close logic
4879
4904
  PARTIAL_LOSS_FN(this, this._pendingSignal, percentToClose, currentPrice);
4880
4905
  // Persist updated signal state (inline setPendingSignal content)
@@ -4956,6 +4981,34 @@ class ClientStrategy {
4956
4981
  if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
4957
4982
  throw new Error(`ClientStrategy breakeven: currentPrice must be a positive finite number, got ${currentPrice}`);
4958
4983
  }
4984
+ // Check for conflict with existing trailing take profit
4985
+ const signal = this._pendingSignal;
4986
+ const breakevenPrice = signal.priceOpen;
4987
+ const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
4988
+ if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit) {
4989
+ // LONG: Breakeven SL would be at or above current TP - invalid configuration
4990
+ this.params.logger.debug("ClientStrategy breakeven: SL/TP conflict detected, skipping breakeven", {
4991
+ signalId: signal.id,
4992
+ position: signal.position,
4993
+ priceOpen: signal.priceOpen,
4994
+ breakevenPrice,
4995
+ effectiveTakeProfit,
4996
+ reason: "breakevenPrice >= effectiveTakeProfit (LONG position)"
4997
+ });
4998
+ return false;
4999
+ }
5000
+ if (signal.position === "short" && breakevenPrice <= effectiveTakeProfit) {
5001
+ // SHORT: Breakeven SL would be at or below current TP - invalid configuration
5002
+ this.params.logger.debug("ClientStrategy breakeven: SL/TP conflict detected, skipping breakeven", {
5003
+ signalId: signal.id,
5004
+ position: signal.position,
5005
+ priceOpen: signal.priceOpen,
5006
+ breakevenPrice,
5007
+ effectiveTakeProfit,
5008
+ reason: "breakevenPrice <= effectiveTakeProfit (SHORT position)"
5009
+ });
5010
+ return false;
5011
+ }
4959
5012
  // Execute breakeven logic
4960
5013
  const result = BREAKEVEN_FN(this, this._pendingSignal, currentPrice);
4961
5014
  // Only persist if breakeven was actually set
@@ -4980,24 +5033,35 @@ class ClientStrategy {
4980
5033
  /**
4981
5034
  * Adjusts trailing stop-loss by shifting distance between entry and original SL.
4982
5035
  *
4983
- * Calculates new SL based on percentage shift of the distance (entry - originalSL):
5036
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
5037
+ * This prevents error accumulation on repeated calls.
5038
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
5039
+ *
5040
+ * Calculates new SL based on percentage shift of the ORIGINAL distance (entry - originalSL):
4984
5041
  * - Negative %: tightens stop (moves SL closer to entry, reduces risk)
4985
5042
  * - Positive %: loosens stop (moves SL away from entry, allows more drawdown)
4986
5043
  *
4987
- * For LONG position (entry=100, originalSL=90, distance=10):
4988
- * - percentShift = -50: newSL = 100 - 10*(1-0.5) = 95 (tighter, closer to entry)
4989
- * - percentShift = +20: newSL = 100 - 10*(1+0.2) = 88 (looser, away from entry)
5044
+ * For LONG position (entry=100, originalSL=90, distance=10%):
5045
+ * - percentShift = -50: newSL = 100 - 10%*(1-0.5) = 95 (5% distance, tighter)
5046
+ * - percentShift = +20: newSL = 100 - 10%*(1+0.2) = 88 (12% distance, looser)
5047
+ *
5048
+ * For SHORT position (entry=100, originalSL=110, distance=10%):
5049
+ * - percentShift = -50: newSL = 100 + 10%*(1-0.5) = 105 (5% distance, tighter)
5050
+ * - percentShift = +20: newSL = 100 + 10%*(1+0.2) = 112 (12% distance, looser)
4990
5051
  *
4991
- * For SHORT position (entry=100, originalSL=110, distance=10):
4992
- * - percentShift = -50: newSL = 100 + 10*(1-0.5) = 105 (tighter, closer to entry)
4993
- * - percentShift = +20: newSL = 100 + 10*(1+0.2) = 112 (looser, away from entry)
5052
+ * Trailing behavior (absorption):
5053
+ * - First call: sets trailing SL unconditionally
5054
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
5055
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
5056
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
5057
+ * - Stores in _trailingPriceStopLoss, original priceStopLoss always preserved
4994
5058
  *
4995
- * Trailing behavior:
4996
- * - Only updates if new SL is BETTER (protects more profit)
4997
- * - For LONG: only accepts higher SL (never moves down)
4998
- * - For SHORT: only accepts lower SL (never moves up)
4999
- * - Validates that SL never crosses entry price
5000
- * - Stores in _trailingPriceStopLoss, original priceStopLoss preserved
5059
+ * Example of absorption (LONG, entry=100, originalSL=90):
5060
+ * ```typescript
5061
+ * await trailingStop(-50, price); // Sets SL=95 (first call)
5062
+ * await trailingStop(-30, price); // SKIPPED: SL=97 < 95 (worse, not absorbed)
5063
+ * await trailingStop(-70, price); // Sets SL=97 (better, absorbs previous)
5064
+ * ```
5001
5065
  *
5002
5066
  * Validation:
5003
5067
  * - Throws if no pending signal exists
@@ -5007,28 +5071,34 @@ class ClientStrategy {
5007
5071
  * - Throws if currentPrice is not a positive finite number
5008
5072
  * - Skips if new SL would cross entry price
5009
5073
  * - Skips if currentPrice already crossed new SL level (price intrusion protection)
5074
+ * - Skips if new SL conflicts with existing trailing take-profit
5010
5075
  *
5011
5076
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
5012
- * @param percentShift - Percentage shift of SL distance [-100, 100], excluding 0
5077
+ * @param percentShift - Percentage shift of ORIGINAL SL distance [-100, 100], excluding 0
5013
5078
  * @param currentPrice - Current market price to check for intrusion
5014
5079
  * @param backtest - Whether running in backtest mode (controls persistence)
5015
- * @returns Promise that resolves when trailing SL is updated and persisted
5080
+ * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected (absorption/intrusion/conflict)
5016
5081
  *
5017
5082
  * @example
5018
5083
  * ```typescript
5019
5084
  * // LONG position: entry=100, originalSL=90, distance=10%, currentPrice=102
5020
5085
  *
5021
5086
  * // Move SL 50% closer to entry (tighten): reduces distance by 50%
5022
- * await strategy.trailingStop("BTCUSDT", -50, 102, false);
5023
- * // newDistance = 10% - 50% = 5%, newSL = 100 * (1 - 0.05) = 95
5087
+ * const success1 = await strategy.trailingStop("BTCUSDT", -50, 102, false);
5088
+ * // success1 = true, newDistance = 10% - 50% = 5%, newSL = 100 * (1 - 0.05) = 95
5024
5089
  *
5025
- * // Move SL 30% away from entry (loosen): increases distance by 30%
5026
- * await strategy.trailingStop("BTCUSDT", 30, 102, false);
5027
- * // newDistance = 10% + 30% = 40%, newSL = 100 * (1 - 0.4) = 60
5090
+ * // Try to move SL only 30% closer (less aggressive)
5091
+ * const success2 = await strategy.trailingStop("BTCUSDT", -30, 102, false);
5092
+ * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
5093
+ *
5094
+ * // Move SL 70% closer to entry (more aggressive)
5095
+ * const success3 = await strategy.trailingStop("BTCUSDT", -70, 102, false);
5096
+ * // success3 = true, newDistance = 10% - 70% = 3%, newSL = 100 * (1 - 0.03) = 97
5097
+ * // Updated! SL=97 > 95 (better protection)
5028
5098
  *
5029
5099
  * // Price intrusion example: currentPrice=92, trying to set SL=95
5030
- * await strategy.trailingStop("BTCUSDT", -50, 92, false);
5031
- * // SKIPPED: currentPrice (92) < newSL (95) - would trigger immediate stop
5100
+ * const success4 = await strategy.trailingStop("BTCUSDT", -50, 92, false);
5101
+ * // success4 = false (SKIPPED: currentPrice (92) < newSL (95) - would trigger immediate stop)
5032
5102
  * ```
5033
5103
  */
5034
5104
  async trailingStop(symbol, percentShift, currentPrice, backtest) {
@@ -5078,7 +5148,7 @@ class ClientStrategy {
5078
5148
  currentPrice,
5079
5149
  reason: "currentPrice below newStopLoss (LONG position)"
5080
5150
  });
5081
- return;
5151
+ return false;
5082
5152
  }
5083
5153
  if (signal.position === "short" && currentPrice > newStopLoss) {
5084
5154
  // SHORT: Price already crossed the new stop loss level - skip setting SL
@@ -5090,7 +5160,7 @@ class ClientStrategy {
5090
5160
  currentPrice,
5091
5161
  reason: "currentPrice above newStopLoss (SHORT position)"
5092
5162
  });
5093
- return;
5163
+ return false;
5094
5164
  }
5095
5165
  // Check for conflict with existing trailing take profit
5096
5166
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
@@ -5104,7 +5174,7 @@ class ClientStrategy {
5104
5174
  effectiveTakeProfit,
5105
5175
  reason: "newStopLoss >= effectiveTakeProfit (LONG position)"
5106
5176
  });
5107
- return;
5177
+ return false;
5108
5178
  }
5109
5179
  if (signal.position === "short" && newStopLoss <= effectiveTakeProfit) {
5110
5180
  // SHORT: New SL would be at or below current TP - invalid configuration
@@ -5116,10 +5186,14 @@ class ClientStrategy {
5116
5186
  effectiveTakeProfit,
5117
5187
  reason: "newStopLoss <= effectiveTakeProfit (SHORT position)"
5118
5188
  });
5119
- return;
5189
+ return false;
5190
+ }
5191
+ // Execute trailing logic and get result
5192
+ const wasUpdated = TRAILING_STOP_LOSS_FN(this, this._pendingSignal, percentShift);
5193
+ // If trailing was not updated (absorption rejected), return false without persistence
5194
+ if (!wasUpdated) {
5195
+ return false;
5120
5196
  }
5121
- // Execute trailing logic
5122
- TRAILING_STOP_FN(this, this._pendingSignal, percentShift);
5123
5197
  // Persist updated signal state (inline setPendingSignal content)
5124
5198
  // Note: this._pendingSignal already mutated by TRAILING_STOP_FN, no reassignment needed
5125
5199
  this.params.logger.debug("ClientStrategy setPendingSignal (inline)", {
@@ -5133,36 +5207,67 @@ class ClientStrategy {
5133
5207
  if (!backtest) {
5134
5208
  await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
5135
5209
  }
5210
+ return true;
5136
5211
  }
5137
5212
  /**
5138
5213
  * Adjusts the trailing take-profit distance for an active pending signal.
5139
5214
  *
5140
- * Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
5141
- * Negative percentShift brings TP closer to entry, positive percentShift moves it further.
5142
- * Once direction is set on first call, subsequent calls must continue in same direction.
5215
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
5216
+ * This prevents error accumulation on repeated calls.
5217
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
5218
+ *
5219
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
5220
+ * Negative percentShift brings TP closer to entry (more conservative).
5221
+ * Positive percentShift moves TP further from entry (more aggressive).
5222
+ *
5223
+ * Trailing behavior (absorption):
5224
+ * - First call: sets trailing TP unconditionally
5225
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
5226
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
5227
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
5228
+ * - Stores in _trailingPriceTakeProfit, original priceTakeProfit always preserved
5229
+ *
5230
+ * Example of absorption (LONG, entry=100, originalTP=110):
5231
+ * ```typescript
5232
+ * await trailingTake(-30, price); // Sets TP=107 (first call, 7% distance)
5233
+ * await trailingTake(+20, price); // SKIPPED: TP=112 > 107 (less conservative)
5234
+ * await trailingTake(-50, price); // Sets TP=105 (more conservative, absorbs previous)
5235
+ * ```
5143
5236
  *
5144
5237
  * Price intrusion protection: If current price has already crossed the new TP level,
5145
5238
  * the update is skipped to prevent immediate TP triggering.
5146
5239
  *
5147
5240
  * @param symbol - Trading pair symbol
5148
- * @param percentShift - Percentage adjustment to TP distance (-100 to 100)
5241
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
5149
5242
  * @param currentPrice - Current market price to check for intrusion
5150
5243
  * @param backtest - Whether running in backtest mode
5151
- * @returns Promise that resolves when trailing TP is updated
5244
+ * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected (absorption/intrusion/conflict)
5152
5245
  *
5153
5246
  * @example
5154
5247
  * ```typescript
5155
5248
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
5156
- * // Move TP further by 50%: newTP = 100 + 15% = 115
5157
- * await strategy.trailingProfit("BTCUSDT", 50, 102, false);
5249
+ *
5250
+ * // Move TP closer by 30% (more conservative)
5251
+ * const success1 = await strategy.trailingTake("BTCUSDT", -30, 102, false);
5252
+ * // success1 = true, newDistance = 10% - 30% = 7%, newTP = 100 * (1 + 0.07) = 107
5253
+ *
5254
+ * // Try to move TP further by 20% (less conservative)
5255
+ * const success2 = await strategy.trailingTake("BTCUSDT", 20, 102, false);
5256
+ * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
5257
+ *
5258
+ * // Move TP even closer by 50% (most conservative)
5259
+ * const success3 = await strategy.trailingTake("BTCUSDT", -50, 102, false);
5260
+ * // success3 = true, newDistance = 10% - 50% = 5%, newTP = 100 * (1 + 0.05) = 105
5261
+ * // Updated! TP=105 < 107 (more conservative)
5158
5262
  *
5159
5263
  * // SHORT: entry=100, originalTP=90, distance=10%, currentPrice=98
5160
5264
  * // Move TP closer by 30%: newTP = 100 - 7% = 93
5161
- * await strategy.trailingProfit("BTCUSDT", -30, 98, false);
5265
+ * const success4 = await strategy.trailingTake("BTCUSDT", -30, 98, false);
5266
+ * // success4 = true
5162
5267
  * ```
5163
5268
  */
5164
- async trailingProfit(symbol, percentShift, currentPrice, backtest) {
5165
- this.params.logger.debug("ClientStrategy trailingProfit", {
5269
+ async trailingTake(symbol, percentShift, currentPrice, backtest) {
5270
+ this.params.logger.debug("ClientStrategy trailingTake", {
5166
5271
  symbol,
5167
5272
  percentShift,
5168
5273
  currentPrice,
@@ -5170,21 +5275,21 @@ class ClientStrategy {
5170
5275
  });
5171
5276
  // Validation: must have pending signal
5172
5277
  if (!this._pendingSignal) {
5173
- throw new Error(`ClientStrategy trailingProfit: No pending signal exists for symbol=${symbol}`);
5278
+ throw new Error(`ClientStrategy trailingTake: No pending signal exists for symbol=${symbol}`);
5174
5279
  }
5175
5280
  // Validation: percentShift must be valid
5176
5281
  if (typeof percentShift !== "number" || !isFinite(percentShift)) {
5177
- throw new Error(`ClientStrategy trailingProfit: percentShift must be a finite number, got ${percentShift} (${typeof percentShift})`);
5282
+ throw new Error(`ClientStrategy trailingTake: percentShift must be a finite number, got ${percentShift} (${typeof percentShift})`);
5178
5283
  }
5179
5284
  if (percentShift < -100 || percentShift > 100) {
5180
- throw new Error(`ClientStrategy trailingProfit: percentShift must be in range [-100, 100], got ${percentShift}`);
5285
+ throw new Error(`ClientStrategy trailingTake: percentShift must be in range [-100, 100], got ${percentShift}`);
5181
5286
  }
5182
5287
  if (percentShift === 0) {
5183
- throw new Error(`ClientStrategy trailingProfit: percentShift cannot be 0`);
5288
+ throw new Error(`ClientStrategy trailingTake: percentShift cannot be 0`);
5184
5289
  }
5185
5290
  // Validation: currentPrice must be valid
5186
5291
  if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
5187
- throw new Error(`ClientStrategy trailingProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
5292
+ throw new Error(`ClientStrategy trailingTake: currentPrice must be a positive finite number, got ${currentPrice}`);
5188
5293
  }
5189
5294
  // Calculate what the new take profit would be
5190
5295
  const signal = this._pendingSignal;
@@ -5200,7 +5305,7 @@ class ClientStrategy {
5200
5305
  // Check for price intrusion before executing trailing logic
5201
5306
  if (signal.position === "long" && currentPrice > newTakeProfit) {
5202
5307
  // LONG: Price already crossed the new take profit level - skip setting TP
5203
- this.params.logger.debug("ClientStrategy trailingProfit: price intrusion detected, skipping TP update", {
5308
+ this.params.logger.debug("ClientStrategy trailingTake: price intrusion detected, skipping TP update", {
5204
5309
  signalId: signal.id,
5205
5310
  position: signal.position,
5206
5311
  priceOpen: signal.priceOpen,
@@ -5208,11 +5313,11 @@ class ClientStrategy {
5208
5313
  currentPrice,
5209
5314
  reason: "currentPrice above newTakeProfit (LONG position)"
5210
5315
  });
5211
- return;
5316
+ return false;
5212
5317
  }
5213
5318
  if (signal.position === "short" && currentPrice < newTakeProfit) {
5214
5319
  // SHORT: Price already crossed the new take profit level - skip setting TP
5215
- this.params.logger.debug("ClientStrategy trailingProfit: price intrusion detected, skipping TP update", {
5320
+ this.params.logger.debug("ClientStrategy trailingTake: price intrusion detected, skipping TP update", {
5216
5321
  signalId: signal.id,
5217
5322
  position: signal.position,
5218
5323
  priceOpen: signal.priceOpen,
@@ -5220,13 +5325,13 @@ class ClientStrategy {
5220
5325
  currentPrice,
5221
5326
  reason: "currentPrice below newTakeProfit (SHORT position)"
5222
5327
  });
5223
- return;
5328
+ return false;
5224
5329
  }
5225
5330
  // Check for conflict with existing trailing stop loss
5226
5331
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5227
5332
  if (signal.position === "long" && newTakeProfit <= effectiveStopLoss) {
5228
5333
  // LONG: New TP would be at or below current SL - invalid configuration
5229
- this.params.logger.debug("ClientStrategy trailingProfit: TP/SL conflict detected, skipping TP update", {
5334
+ this.params.logger.debug("ClientStrategy trailingTake: TP/SL conflict detected, skipping TP update", {
5230
5335
  signalId: signal.id,
5231
5336
  position: signal.position,
5232
5337
  priceOpen: signal.priceOpen,
@@ -5234,11 +5339,11 @@ class ClientStrategy {
5234
5339
  effectiveStopLoss,
5235
5340
  reason: "newTakeProfit <= effectiveStopLoss (LONG position)"
5236
5341
  });
5237
- return;
5342
+ return false;
5238
5343
  }
5239
5344
  if (signal.position === "short" && newTakeProfit >= effectiveStopLoss) {
5240
5345
  // SHORT: New TP would be at or above current SL - invalid configuration
5241
- this.params.logger.debug("ClientStrategy trailingProfit: TP/SL conflict detected, skipping TP update", {
5346
+ this.params.logger.debug("ClientStrategy trailingTake: TP/SL conflict detected, skipping TP update", {
5242
5347
  signalId: signal.id,
5243
5348
  position: signal.position,
5244
5349
  priceOpen: signal.priceOpen,
@@ -5246,10 +5351,14 @@ class ClientStrategy {
5246
5351
  effectiveStopLoss,
5247
5352
  reason: "newTakeProfit >= effectiveStopLoss (SHORT position)"
5248
5353
  });
5249
- return;
5354
+ return false;
5355
+ }
5356
+ // Execute trailing logic and get result
5357
+ const wasUpdated = TRAILING_TAKE_PROFIT_FN(this, this._pendingSignal, percentShift);
5358
+ // If trailing was not updated (absorption rejected), return false without persistence
5359
+ if (!wasUpdated) {
5360
+ return false;
5250
5361
  }
5251
- // Execute trailing logic
5252
- TRAILING_PROFIT_FN(this, this._pendingSignal, percentShift);
5253
5362
  // Persist updated signal state (inline setPendingSignal content)
5254
5363
  // Note: this._pendingSignal already mutated by TRAILING_PROFIT_FN, no reassignment needed
5255
5364
  this.params.logger.debug("ClientStrategy setPendingSignal (inline)", {
@@ -5263,6 +5372,7 @@ class ClientStrategy {
5263
5372
  if (!backtest) {
5264
5373
  await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
5265
5374
  }
5375
+ return true;
5266
5376
  }
5267
5377
  }
5268
5378
 
@@ -5341,12 +5451,12 @@ class MergeRisk {
5341
5451
  * @param context - Context with strategyName, riskName, exchangeName and frameName
5342
5452
  * @returns Promise that resolves when all risks have registered the signal
5343
5453
  */
5344
- async addSignal(symbol, context) {
5454
+ async addSignal(symbol, context, positionData) {
5345
5455
  bt.loggerService.info("MergeRisk addSignal", {
5346
5456
  symbol,
5347
5457
  context,
5348
5458
  });
5349
- await Promise.all(this._riskList.map(async (risk) => await risk.addSignal(symbol, context)));
5459
+ await Promise.all(this._riskList.map(async (risk) => await risk.addSignal(symbol, context, positionData)));
5350
5460
  }
5351
5461
  /**
5352
5462
  * Removes a signal from all child risk profiles.
@@ -5434,6 +5544,7 @@ class RiskUtils {
5434
5544
  });
5435
5545
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_DATA);
5436
5546
  bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_DATA);
5547
+ context.frameName && bt.frameValidationService.validate(context.frameName, RISK_METHOD_NAME_GET_DATA);
5437
5548
  {
5438
5549
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
5439
5550
  riskName &&
@@ -5491,6 +5602,7 @@ class RiskUtils {
5491
5602
  });
5492
5603
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_REPORT);
5493
5604
  bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_REPORT);
5605
+ context.frameName && bt.frameValidationService.validate(context.frameName, RISK_METHOD_NAME_GET_REPORT);
5494
5606
  {
5495
5607
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
5496
5608
  riskName &&
@@ -5540,6 +5652,7 @@ class RiskUtils {
5540
5652
  });
5541
5653
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_DUMP);
5542
5654
  bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_DUMP);
5655
+ context.frameName && bt.frameValidationService.validate(context.frameName, RISK_METHOD_NAME_DUMP);
5543
5656
  {
5544
5657
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
5545
5658
  riskName &&
@@ -6050,7 +6163,7 @@ class StrategyConnectionService {
6050
6163
  backtest,
6051
6164
  });
6052
6165
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
6053
- await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
6166
+ return await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
6054
6167
  };
6055
6168
  /**
6056
6169
  * Adjusts the trailing take-profit distance for an active pending signal.
@@ -6058,7 +6171,7 @@ class StrategyConnectionService {
6058
6171
  * Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
6059
6172
  * Negative percentShift brings TP closer to entry, positive percentShift moves it further.
6060
6173
  *
6061
- * Delegates to ClientStrategy.trailingProfit() with current execution context.
6174
+ * Delegates to ClientStrategy.trailingTake() with current execution context.
6062
6175
  *
6063
6176
  * @param backtest - Whether running in backtest mode
6064
6177
  * @param symbol - Trading pair symbol
@@ -6071,7 +6184,7 @@ class StrategyConnectionService {
6071
6184
  * ```typescript
6072
6185
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
6073
6186
  * // Move TP further by 50%: newTP = 100 + 15% = 115
6074
- * await strategyConnectionService.trailingProfit(
6187
+ * await strategyConnectionService.trailingTake(
6075
6188
  * false,
6076
6189
  * "BTCUSDT",
6077
6190
  * 50,
@@ -6080,8 +6193,8 @@ class StrategyConnectionService {
6080
6193
  * );
6081
6194
  * ```
6082
6195
  */
6083
- this.trailingProfit = async (backtest, symbol, percentShift, currentPrice, context) => {
6084
- this.loggerService.log("strategyConnectionService trailingProfit", {
6196
+ this.trailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
6197
+ this.loggerService.log("strategyConnectionService trailingTake", {
6085
6198
  symbol,
6086
6199
  context,
6087
6200
  percentShift,
@@ -6089,7 +6202,7 @@ class StrategyConnectionService {
6089
6202
  backtest,
6090
6203
  });
6091
6204
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
6092
- await strategy.trailingProfit(symbol, percentShift, currentPrice, backtest);
6205
+ return await strategy.trailingTake(symbol, percentShift, currentPrice, backtest);
6093
6206
  };
6094
6207
  /**
6095
6208
  * Delegates to ClientStrategy.breakeven() with current execution context.
@@ -6783,10 +6896,11 @@ class ClientRisk {
6783
6896
  * Registers a new opened signal.
6784
6897
  * Called by StrategyConnectionService after signal is opened.
6785
6898
  */
6786
- async addSignal(symbol, context) {
6899
+ async addSignal(symbol, context, positionData) {
6787
6900
  this.params.logger.debug("ClientRisk addSignal", {
6788
6901
  symbol,
6789
6902
  context,
6903
+ positionData,
6790
6904
  backtest: this.params.backtest,
6791
6905
  });
6792
6906
  if (this._activePositions === POSITION_NEED_FETCH) {
@@ -6797,7 +6911,14 @@ class ClientRisk {
6797
6911
  riskMap.set(key, {
6798
6912
  strategyName: context.strategyName,
6799
6913
  exchangeName: context.exchangeName,
6800
- openTimestamp: Date.now(),
6914
+ frameName: context.frameName,
6915
+ symbol,
6916
+ position: positionData.position,
6917
+ priceOpen: positionData.priceOpen,
6918
+ priceStopLoss: positionData.priceStopLoss,
6919
+ priceTakeProfit: positionData.priceTakeProfit,
6920
+ minuteEstimatedTime: positionData.minuteEstimatedTime,
6921
+ openTimestamp: positionData.openTimestamp,
6801
6922
  });
6802
6923
  await this._updatePositions();
6803
6924
  }
@@ -6947,13 +7068,15 @@ class RiskConnectionService {
6947
7068
  *
6948
7069
  * @param symbol - Trading pair symbol
6949
7070
  * @param payload - Payload information (strategyName, riskName, exchangeName, frameName, backtest)
7071
+ * @param positionData - Position data (position, prices, timing)
6950
7072
  */
6951
- this.addSignal = async (symbol, payload) => {
7073
+ this.addSignal = async (symbol, payload, positionData) => {
6952
7074
  this.loggerService.log("riskConnectionService addSignal", {
6953
7075
  symbol,
6954
7076
  payload,
7077
+ positionData,
6955
7078
  });
6956
- await this.getRisk(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest).addSignal(symbol, payload);
7079
+ await this.getRisk(payload.riskName, payload.exchangeName, payload.frameName, payload.backtest).addSignal(symbol, payload, positionData);
6957
7080
  };
6958
7081
  /**
6959
7082
  * Removes a closed signal from the risk management system.
@@ -7178,6 +7301,8 @@ class StrategyCoreService {
7178
7301
  this.strategySchemaService = inject(TYPES.strategySchemaService);
7179
7302
  this.riskValidationService = inject(TYPES.riskValidationService);
7180
7303
  this.strategyValidationService = inject(TYPES.strategyValidationService);
7304
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7305
+ this.frameValidationService = inject(TYPES.frameValidationService);
7181
7306
  /**
7182
7307
  * Validates strategy and associated risk configuration.
7183
7308
  *
@@ -7187,13 +7312,14 @@ class StrategyCoreService {
7187
7312
  * @param context - Execution context with strategyName, exchangeName, frameName
7188
7313
  * @returns Promise that resolves when validation is complete
7189
7314
  */
7190
- this.validate = functoolsKit.memoize(([symbol, context]) => `${symbol}:${context.strategyName}:${context.exchangeName}:${context.frameName}`, async (symbol, context) => {
7315
+ this.validate = functoolsKit.memoize(([context]) => `${context.strategyName}:${context.exchangeName}:${context.frameName}`, async (context) => {
7191
7316
  this.loggerService.log(METHOD_NAME_VALIDATE, {
7192
- symbol,
7193
7317
  context,
7194
7318
  });
7195
7319
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
7196
7320
  this.strategyValidationService.validate(context.strategyName, METHOD_NAME_VALIDATE);
7321
+ this.exchangeValidationService.validate(context.exchangeName, METHOD_NAME_VALIDATE);
7322
+ context.frameName && this.frameValidationService.validate(context.frameName, METHOD_NAME_VALIDATE);
7197
7323
  riskName && this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE);
7198
7324
  riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, METHOD_NAME_VALIDATE));
7199
7325
  });
@@ -7212,7 +7338,7 @@ class StrategyCoreService {
7212
7338
  symbol,
7213
7339
  context,
7214
7340
  });
7215
- await this.validate(symbol, context);
7341
+ await this.validate(context);
7216
7342
  return await this.strategyConnectionService.getPendingSignal(backtest, symbol, context);
7217
7343
  };
7218
7344
  /**
@@ -7230,7 +7356,7 @@ class StrategyCoreService {
7230
7356
  symbol,
7231
7357
  context,
7232
7358
  });
7233
- await this.validate(symbol, context);
7359
+ await this.validate(context);
7234
7360
  return await this.strategyConnectionService.getScheduledSignal(backtest, symbol, context);
7235
7361
  };
7236
7362
  /**
@@ -7270,7 +7396,7 @@ class StrategyCoreService {
7270
7396
  context,
7271
7397
  backtest,
7272
7398
  });
7273
- await this.validate(symbol, context);
7399
+ await this.validate(context);
7274
7400
  return await this.strategyConnectionService.getBreakeven(backtest, symbol, currentPrice, context);
7275
7401
  };
7276
7402
  /**
@@ -7290,7 +7416,7 @@ class StrategyCoreService {
7290
7416
  context,
7291
7417
  backtest,
7292
7418
  });
7293
- await this.validate(symbol, context);
7419
+ await this.validate(context);
7294
7420
  return await this.strategyConnectionService.getStopped(backtest, symbol, context);
7295
7421
  };
7296
7422
  /**
@@ -7312,7 +7438,7 @@ class StrategyCoreService {
7312
7438
  backtest,
7313
7439
  context,
7314
7440
  });
7315
- await this.validate(symbol, context);
7441
+ await this.validate(context);
7316
7442
  return await ExecutionContextService.runInContext(async () => {
7317
7443
  return await this.strategyConnectionService.tick(symbol, context);
7318
7444
  }, {
@@ -7342,7 +7468,7 @@ class StrategyCoreService {
7342
7468
  backtest,
7343
7469
  context,
7344
7470
  });
7345
- await this.validate(symbol, context);
7471
+ await this.validate(context);
7346
7472
  return await ExecutionContextService.runInContext(async () => {
7347
7473
  return await this.strategyConnectionService.backtest(symbol, context, candles);
7348
7474
  }, {
@@ -7368,7 +7494,7 @@ class StrategyCoreService {
7368
7494
  context,
7369
7495
  backtest,
7370
7496
  });
7371
- await this.validate(symbol, context);
7497
+ await this.validate(context);
7372
7498
  return await this.strategyConnectionService.stop(backtest, symbol, context);
7373
7499
  };
7374
7500
  /**
@@ -7391,7 +7517,7 @@ class StrategyCoreService {
7391
7517
  backtest,
7392
7518
  cancelId,
7393
7519
  });
7394
- await this.validate(symbol, context);
7520
+ await this.validate(context);
7395
7521
  return await this.strategyConnectionService.cancel(backtest, symbol, context, cancelId);
7396
7522
  };
7397
7523
  /**
@@ -7407,7 +7533,7 @@ class StrategyCoreService {
7407
7533
  payload,
7408
7534
  });
7409
7535
  if (payload) {
7410
- await this.validate(payload.symbol, {
7536
+ await this.validate({
7411
7537
  strategyName: payload.strategyName,
7412
7538
  exchangeName: payload.exchangeName,
7413
7539
  frameName: payload.frameName
@@ -7450,7 +7576,7 @@ class StrategyCoreService {
7450
7576
  context,
7451
7577
  backtest,
7452
7578
  });
7453
- await this.validate(symbol, context);
7579
+ await this.validate(context);
7454
7580
  return await this.strategyConnectionService.partialProfit(backtest, symbol, percentToClose, currentPrice, context);
7455
7581
  };
7456
7582
  /**
@@ -7488,7 +7614,7 @@ class StrategyCoreService {
7488
7614
  context,
7489
7615
  backtest,
7490
7616
  });
7491
- await this.validate(symbol, context);
7617
+ await this.validate(context);
7492
7618
  return await this.strategyConnectionService.partialLoss(backtest, symbol, percentToClose, currentPrice, context);
7493
7619
  };
7494
7620
  /**
@@ -7527,7 +7653,7 @@ class StrategyCoreService {
7527
7653
  context,
7528
7654
  backtest,
7529
7655
  });
7530
- await this.validate(symbol, context);
7656
+ await this.validate(context);
7531
7657
  return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
7532
7658
  };
7533
7659
  /**
@@ -7545,7 +7671,7 @@ class StrategyCoreService {
7545
7671
  * ```typescript
7546
7672
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
7547
7673
  * // Move TP further by 50%: newTP = 100 + 15% = 115
7548
- * await strategyCoreService.trailingProfit(
7674
+ * await strategyCoreService.trailingTake(
7549
7675
  * false,
7550
7676
  * "BTCUSDT",
7551
7677
  * 50,
@@ -7554,16 +7680,16 @@ class StrategyCoreService {
7554
7680
  * );
7555
7681
  * ```
7556
7682
  */
7557
- this.trailingProfit = async (backtest, symbol, percentShift, currentPrice, context) => {
7558
- this.loggerService.log("strategyCoreService trailingProfit", {
7683
+ this.trailingTake = async (backtest, symbol, percentShift, currentPrice, context) => {
7684
+ this.loggerService.log("strategyCoreService trailingTake", {
7559
7685
  symbol,
7560
7686
  percentShift,
7561
7687
  currentPrice,
7562
7688
  context,
7563
7689
  backtest,
7564
7690
  });
7565
- await this.validate(symbol, context);
7566
- return await this.strategyConnectionService.trailingProfit(backtest, symbol, percentShift, currentPrice, context);
7691
+ await this.validate(context);
7692
+ return await this.strategyConnectionService.trailingTake(backtest, symbol, percentShift, currentPrice, context);
7567
7693
  };
7568
7694
  /**
7569
7695
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -7592,7 +7718,7 @@ class StrategyCoreService {
7592
7718
  context,
7593
7719
  backtest,
7594
7720
  });
7595
- await this.validate(symbol, context);
7721
+ await this.validate(context);
7596
7722
  return await this.strategyConnectionService.breakeven(backtest, symbol, currentPrice, context);
7597
7723
  };
7598
7724
  }
@@ -7673,6 +7799,7 @@ class RiskGlobalService {
7673
7799
  this.riskConnectionService = inject(TYPES.riskConnectionService);
7674
7800
  this.riskValidationService = inject(TYPES.riskValidationService);
7675
7801
  this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7802
+ this.frameValidationService = inject(TYPES.frameValidationService);
7676
7803
  /**
7677
7804
  * Validates risk configuration.
7678
7805
  * Memoized to avoid redundant validations for the same risk-exchange-frame combination.
@@ -7686,6 +7813,7 @@ class RiskGlobalService {
7686
7813
  });
7687
7814
  this.riskValidationService.validate(payload.riskName, "riskGlobalService validate");
7688
7815
  this.exchangeValidationService.validate(payload.exchangeName, "riskGlobalService validate");
7816
+ payload.frameName && this.frameValidationService.validate(payload.frameName, "riskGlobalService validate");
7689
7817
  });
7690
7818
  /**
7691
7819
  * Checks if a signal should be allowed based on risk limits.
@@ -7707,14 +7835,16 @@ class RiskGlobalService {
7707
7835
  *
7708
7836
  * @param symbol - Trading pair symbol
7709
7837
  * @param payload - Payload information (strategyName, riskName, exchangeName, frameName, backtest)
7838
+ * @param positionData - Position data (position, prices, timing)
7710
7839
  */
7711
- this.addSignal = async (symbol, payload) => {
7840
+ this.addSignal = async (symbol, payload, positionData) => {
7712
7841
  this.loggerService.log("riskGlobalService addSignal", {
7713
7842
  symbol,
7714
7843
  payload,
7844
+ positionData,
7715
7845
  });
7716
7846
  await this.validate(payload);
7717
- await this.riskConnectionService.addSignal(symbol, payload);
7847
+ await this.riskConnectionService.addSignal(symbol, payload, positionData);
7718
7848
  };
7719
7849
  /**
7720
7850
  * Removes a closed signal from the risk management system.
@@ -15962,6 +16092,10 @@ class PartialGlobalService {
15962
16092
  * Exchange validation service for validating exchange existence.
15963
16093
  */
15964
16094
  this.exchangeValidationService = inject(TYPES.exchangeValidationService);
16095
+ /**
16096
+ * Frame validation service for validating frame existence.
16097
+ */
16098
+ this.frameValidationService = inject(TYPES.frameValidationService);
15965
16099
  /**
15966
16100
  * Validates strategy and associated risk configuration.
15967
16101
  * Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
@@ -15976,6 +16110,7 @@ class PartialGlobalService {
15976
16110
  });
15977
16111
  this.strategyValidationService.validate(context.strategyName, methodName);
15978
16112
  this.exchangeValidationService.validate(context.exchangeName, methodName);
16113
+ context.frameName && this.frameValidationService.validate(context.frameName, methodName);
15979
16114
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
15980
16115
  riskName && this.riskValidationService.validate(riskName, methodName);
15981
16116
  riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
@@ -16941,6 +17076,10 @@ class BreakevenGlobalService {
16941
17076
  * Exchange validation service for validating exchange existence.
16942
17077
  */
16943
17078
  this.exchangeValidationService = inject(TYPES.exchangeValidationService);
17079
+ /**
17080
+ * Frame validation service for validating frame existence.
17081
+ */
17082
+ this.frameValidationService = inject(TYPES.frameValidationService);
16944
17083
  /**
16945
17084
  * Validates strategy and associated risk configuration.
16946
17085
  * Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
@@ -16955,6 +17094,7 @@ class BreakevenGlobalService {
16955
17094
  });
16956
17095
  this.strategyValidationService.validate(context.strategyName, methodName);
16957
17096
  this.exchangeValidationService.validate(context.exchangeName, methodName);
17097
+ context.frameName && this.frameValidationService.validate(context.frameName, methodName);
16958
17098
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
16959
17099
  riskName && this.riskValidationService.validate(riskName, methodName);
16960
17100
  riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
@@ -18268,7 +18408,7 @@ const CANCEL_METHOD_NAME = "strategy.cancel";
18268
18408
  const PARTIAL_PROFIT_METHOD_NAME = "strategy.partialProfit";
18269
18409
  const PARTIAL_LOSS_METHOD_NAME = "strategy.partialLoss";
18270
18410
  const TRAILING_STOP_METHOD_NAME = "strategy.trailingStop";
18271
- const TRAILING_PROFIT_METHOD_NAME = "strategy.trailingProfit";
18411
+ const TRAILING_PROFIT_METHOD_NAME = "strategy.trailingTake";
18272
18412
  const BREAKEVEN_METHOD_NAME = "strategy.breakeven";
18273
18413
  /**
18274
18414
  * Stops the strategy from generating new signals.
@@ -18429,23 +18569,44 @@ async function partialLoss(symbol, percentToClose) {
18429
18569
  /**
18430
18570
  * Adjusts the trailing stop-loss distance for an active pending signal.
18431
18571
  *
18432
- * Updates the stop-loss distance by a percentage adjustment relative to the original SL distance.
18433
- * Positive percentShift tightens the SL (reduces distance), negative percentShift loosens it.
18572
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
18573
+ * This prevents error accumulation on repeated calls.
18574
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
18575
+ *
18576
+ * Updates the stop-loss distance by a percentage adjustment relative to the ORIGINAL SL distance.
18577
+ * Negative percentShift tightens the SL (reduces distance, moves closer to entry).
18578
+ * Positive percentShift loosens the SL (increases distance, moves away from entry).
18579
+ *
18580
+ * Absorption behavior:
18581
+ * - First call: sets trailing SL unconditionally
18582
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
18583
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
18584
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
18434
18585
  *
18435
18586
  * Automatically detects backtest/live mode from execution context.
18436
18587
  *
18437
18588
  * @param symbol - Trading pair symbol
18438
- * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
18589
+ * @param percentShift - Percentage adjustment to ORIGINAL SL distance (-100 to 100)
18439
18590
  * @param currentPrice - Current market price to check for intrusion
18440
- * @returns Promise that resolves when trailing SL is updated
18591
+ * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected (absorption/intrusion/conflict)
18441
18592
  *
18442
18593
  * @example
18443
18594
  * ```typescript
18444
18595
  * import { trailingStop } from "backtest-kit";
18445
18596
  *
18446
18597
  * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
18447
- * // Tighten stop by 50%: newSL = 100 - 5% = 95
18448
- * await trailingStop("BTCUSDT", -50, 102);
18598
+ *
18599
+ * // First call: tighten by 5%
18600
+ * const success1 = await trailingStop("BTCUSDT", -5, 102);
18601
+ * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
18602
+ *
18603
+ * // Second call: try weaker protection (smaller percentShift)
18604
+ * const success2 = await trailingStop("BTCUSDT", -3, 102);
18605
+ * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
18606
+ *
18607
+ * // Third call: stronger protection (larger percentShift)
18608
+ * const success3 = await trailingStop("BTCUSDT", -7, 102);
18609
+ * // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
18449
18610
  * ```
18450
18611
  */
18451
18612
  async function trailingStop(symbol, percentShift, currentPrice) {
@@ -18462,46 +18623,66 @@ async function trailingStop(symbol, percentShift, currentPrice) {
18462
18623
  }
18463
18624
  const { backtest: isBacktest } = bt.executionContextService.context;
18464
18625
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
18465
- await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
18626
+ return await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
18466
18627
  }
18467
18628
  /**
18468
18629
  * Adjusts the trailing take-profit distance for an active pending signal.
18469
18630
  *
18470
- * Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
18471
- * Negative percentShift brings TP closer to entry, positive percentShift moves it further.
18472
- * Once direction is set on first call, subsequent calls must continue in same direction.
18631
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
18632
+ * This prevents error accumulation on repeated calls.
18633
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
18634
+ *
18635
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
18636
+ * Negative percentShift brings TP closer to entry (more conservative).
18637
+ * Positive percentShift moves TP further from entry (more aggressive).
18638
+ *
18639
+ * Absorption behavior:
18640
+ * - First call: sets trailing TP unconditionally
18641
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
18642
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
18643
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
18473
18644
  *
18474
18645
  * Automatically detects backtest/live mode from execution context.
18475
18646
  *
18476
18647
  * @param symbol - Trading pair symbol
18477
- * @param percentShift - Percentage adjustment to TP distance (-100 to 100)
18648
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
18478
18649
  * @param currentPrice - Current market price to check for intrusion
18479
- * @returns Promise that resolves when trailing TP is updated
18650
+ * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected (absorption/intrusion/conflict)
18480
18651
  *
18481
18652
  * @example
18482
18653
  * ```typescript
18483
- * import { trailingProfit } from "backtest-kit";
18654
+ * import { trailingTake } from "backtest-kit";
18484
18655
  *
18485
18656
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
18486
- * // Move TP further by 50%: newTP = 100 + 15% = 115
18487
- * await trailingProfit("BTCUSDT", 50, 102);
18657
+ *
18658
+ * // First call: bring TP closer by 3%
18659
+ * const success1 = await trailingTake("BTCUSDT", -3, 102);
18660
+ * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
18661
+ *
18662
+ * // Second call: try to move TP further (less conservative)
18663
+ * const success2 = await trailingTake("BTCUSDT", 2, 102);
18664
+ * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
18665
+ *
18666
+ * // Third call: even more conservative
18667
+ * const success3 = await trailingTake("BTCUSDT", -5, 102);
18668
+ * // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
18488
18669
  * ```
18489
18670
  */
18490
- async function trailingProfit(symbol, percentShift, currentPrice) {
18671
+ async function trailingTake(symbol, percentShift, currentPrice) {
18491
18672
  bt.loggerService.info(TRAILING_PROFIT_METHOD_NAME, {
18492
18673
  symbol,
18493
18674
  percentShift,
18494
18675
  currentPrice,
18495
18676
  });
18496
18677
  if (!ExecutionContextService.hasContext()) {
18497
- throw new Error("trailingProfit requires an execution context");
18678
+ throw new Error("trailingTake requires an execution context");
18498
18679
  }
18499
18680
  if (!MethodContextService.hasContext()) {
18500
- throw new Error("trailingProfit requires a method context");
18681
+ throw new Error("trailingTake requires a method context");
18501
18682
  }
18502
18683
  const { backtest: isBacktest } = bt.executionContextService.context;
18503
18684
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
18504
- await bt.strategyCoreService.trailingProfit(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
18685
+ return await bt.strategyCoreService.trailingTake(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
18505
18686
  }
18506
18687
  /**
18507
18688
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -20448,7 +20629,7 @@ const BACKTEST_METHOD_NAME_CANCEL = "BacktestUtils.cancel";
20448
20629
  const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.partialProfit";
20449
20630
  const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.partialLoss";
20450
20631
  const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.trailingStop";
20451
- const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.trailingProfit";
20632
+ const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.trailingTake";
20452
20633
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
20453
20634
  /**
20454
20635
  * Internal task function that runs backtest and handles completion.
@@ -21067,11 +21248,22 @@ class BacktestUtils {
21067
21248
  /**
21068
21249
  * Adjusts the trailing stop-loss distance for an active pending signal.
21069
21250
  *
21070
- * Updates the stop-loss distance by a percentage adjustment relative to the original SL distance.
21071
- * Positive percentShift tightens the SL (reduces distance), negative percentShift loosens it.
21251
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
21252
+ * This prevents error accumulation on repeated calls.
21253
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
21254
+ *
21255
+ * Updates the stop-loss distance by a percentage adjustment relative to the ORIGINAL SL distance.
21256
+ * Negative percentShift tightens the SL (reduces distance, moves closer to entry).
21257
+ * Positive percentShift loosens the SL (increases distance, moves away from entry).
21258
+ *
21259
+ * Absorption behavior:
21260
+ * - First call: sets trailing SL unconditionally
21261
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
21262
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
21263
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
21072
21264
  *
21073
21265
  * @param symbol - Trading pair symbol
21074
- * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
21266
+ * @param percentShift - Percentage adjustment to ORIGINAL SL distance (-100 to 100)
21075
21267
  * @param currentPrice - Current market price to check for intrusion
21076
21268
  * @param context - Execution context with strategyName, exchangeName, and frameName
21077
21269
  * @returns Promise that resolves when trailing SL is updated
@@ -21079,12 +21271,22 @@ class BacktestUtils {
21079
21271
  * @example
21080
21272
  * ```typescript
21081
21273
  * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
21082
- * // Tighten stop by 50%: newSL = 100 - 5% = 95
21083
- * await Backtest.trailingStop("BTCUSDT", -50, 102, {
21274
+ *
21275
+ * // First call: tighten by 5%
21276
+ * await Backtest.trailingStop("BTCUSDT", -5, 102, {
21084
21277
  * exchangeName: "binance",
21085
21278
  * frameName: "frame1",
21086
21279
  * strategyName: "my-strategy"
21087
21280
  * });
21281
+ * // newDistance = 10% - 5% = 5%, newSL = 95
21282
+ *
21283
+ * // Second call: try weaker protection (smaller percentShift)
21284
+ * await Backtest.trailingStop("BTCUSDT", -3, 102, context);
21285
+ * // SKIPPED: newSL=97 < 95 (worse protection, larger % absorbs smaller)
21286
+ *
21287
+ * // Third call: stronger protection (larger percentShift)
21288
+ * await Backtest.trailingStop("BTCUSDT", -7, 102, context);
21289
+ * // ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95 (better protection)
21088
21290
  * ```
21089
21291
  */
21090
21292
  this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
@@ -21103,17 +21305,27 @@ class BacktestUtils {
21103
21305
  riskList &&
21104
21306
  riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP));
21105
21307
  }
21106
- await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
21308
+ return await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
21107
21309
  };
21108
21310
  /**
21109
21311
  * Adjusts the trailing take-profit distance for an active pending signal.
21110
21312
  *
21111
- * Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
21112
- * Negative percentShift brings TP closer to entry, positive percentShift moves it further.
21113
- * Once direction is set on first call, subsequent calls must continue in same direction.
21313
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
21314
+ * This prevents error accumulation on repeated calls.
21315
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
21316
+ *
21317
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
21318
+ * Negative percentShift brings TP closer to entry (more conservative).
21319
+ * Positive percentShift moves TP further from entry (more aggressive).
21320
+ *
21321
+ * Absorption behavior:
21322
+ * - First call: sets trailing TP unconditionally
21323
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
21324
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
21325
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
21114
21326
  *
21115
21327
  * @param symbol - Trading pair symbol
21116
- * @param percentShift - Percentage adjustment to TP distance (-100 to 100)
21328
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
21117
21329
  * @param currentPrice - Current market price to check for intrusion
21118
21330
  * @param context - Execution context with strategyName, exchangeName, and frameName
21119
21331
  * @returns Promise that resolves when trailing TP is updated
@@ -21121,15 +21333,25 @@ class BacktestUtils {
21121
21333
  * @example
21122
21334
  * ```typescript
21123
21335
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
21124
- * // Move TP further by 50%: newTP = 100 + 15% = 115
21125
- * await Backtest.trailingProfit("BTCUSDT", 50, 102, {
21336
+ *
21337
+ * // First call: bring TP closer by 3%
21338
+ * await Backtest.trailingTake("BTCUSDT", -3, 102, {
21126
21339
  * exchangeName: "binance",
21127
21340
  * frameName: "frame1",
21128
21341
  * strategyName: "my-strategy"
21129
21342
  * });
21343
+ * // newDistance = 10% - 3% = 7%, newTP = 107
21344
+ *
21345
+ * // Second call: try to move TP further (less conservative)
21346
+ * await Backtest.trailingTake("BTCUSDT", 2, 102, context);
21347
+ * // SKIPPED: newTP=112 > 107 (less conservative, larger % absorbs smaller)
21348
+ *
21349
+ * // Third call: even more conservative
21350
+ * await Backtest.trailingTake("BTCUSDT", -5, 102, context);
21351
+ * // ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107 (more conservative)
21130
21352
  * ```
21131
21353
  */
21132
- this.trailingProfit = async (symbol, percentShift, currentPrice, context) => {
21354
+ this.trailingTake = async (symbol, percentShift, currentPrice, context) => {
21133
21355
  bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_PROFIT, {
21134
21356
  symbol,
21135
21357
  percentShift,
@@ -21145,7 +21367,7 @@ class BacktestUtils {
21145
21367
  riskList &&
21146
21368
  riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT));
21147
21369
  }
21148
- await bt.strategyCoreService.trailingProfit(true, symbol, percentShift, currentPrice, context);
21370
+ return await bt.strategyCoreService.trailingTake(true, symbol, percentShift, currentPrice, context);
21149
21371
  };
21150
21372
  /**
21151
21373
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -21351,7 +21573,7 @@ const LIVE_METHOD_NAME_CANCEL = "LiveUtils.cancel";
21351
21573
  const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.partialProfit";
21352
21574
  const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.partialLoss";
21353
21575
  const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.trailingStop";
21354
- const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.trailingProfit";
21576
+ const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.trailingTake";
21355
21577
  /**
21356
21578
  * Internal task function that runs live trading and handles completion.
21357
21579
  * Consumes live trading results and updates instance state flags.
@@ -21947,23 +22169,44 @@ class LiveUtils {
21947
22169
  /**
21948
22170
  * Adjusts the trailing stop-loss distance for an active pending signal.
21949
22171
  *
21950
- * Updates the stop-loss distance by a percentage adjustment relative to the original SL distance.
21951
- * Positive percentShift tightens the SL (reduces distance), negative percentShift loosens it.
22172
+ * CRITICAL: Always calculates from ORIGINAL SL, not from current trailing SL.
22173
+ * This prevents error accumulation on repeated calls.
22174
+ * Larger percentShift ABSORBS smaller one (updates only towards better protection).
22175
+ *
22176
+ * Updates the stop-loss distance by a percentage adjustment relative to the ORIGINAL SL distance.
22177
+ * Negative percentShift tightens the SL (reduces distance, moves closer to entry).
22178
+ * Positive percentShift loosens the SL (increases distance, moves away from entry).
22179
+ *
22180
+ * Absorption behavior:
22181
+ * - First call: sets trailing SL unconditionally
22182
+ * - Subsequent calls: updates only if new SL is BETTER (protects more profit)
22183
+ * - For LONG: only accepts HIGHER SL (never moves down, closer to entry wins)
22184
+ * - For SHORT: only accepts LOWER SL (never moves up, closer to entry wins)
21952
22185
  *
21953
22186
  * @param symbol - Trading pair symbol
21954
- * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
22187
+ * @param percentShift - Percentage adjustment to ORIGINAL SL distance (-100 to 100)
21955
22188
  * @param currentPrice - Current market price to check for intrusion
21956
22189
  * @param context - Execution context with strategyName and exchangeName
21957
- * @returns Promise that resolves when trailing SL is updated
22190
+ * @returns Promise<boolean> - true if trailing SL was set/updated, false if rejected (absorption/intrusion/conflict)
21958
22191
  *
21959
22192
  * @example
21960
22193
  * ```typescript
21961
22194
  * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
21962
- * // Tighten stop by 50%: newSL = 100 - 5% = 95
21963
- * await Live.trailingStop("BTCUSDT", -50, 102, {
22195
+ *
22196
+ * // First call: tighten by 5%
22197
+ * const success1 = await Live.trailingStop("BTCUSDT", -5, 102, {
21964
22198
  * exchangeName: "binance",
21965
22199
  * strategyName: "my-strategy"
21966
22200
  * });
22201
+ * // success1 = true, newDistance = 10% - 5% = 5%, newSL = 95
22202
+ *
22203
+ * // Second call: try weaker protection (smaller percentShift)
22204
+ * const success2 = await Live.trailingStop("BTCUSDT", -3, 102, context);
22205
+ * // success2 = false (SKIPPED: newSL=97 < 95, worse protection, larger % absorbs smaller)
22206
+ *
22207
+ * // Third call: stronger protection (larger percentShift)
22208
+ * const success3 = await Live.trailingStop("BTCUSDT", -7, 102, context);
22209
+ * // success3 = true (ACCEPTED: newDistance = 10% - 7% = 3%, newSL = 97 > 95, better protection)
21967
22210
  * ```
21968
22211
  */
21969
22212
  this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
@@ -21980,7 +22223,7 @@ class LiveUtils {
21980
22223
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP);
21981
22224
  riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP));
21982
22225
  }
21983
- await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
22226
+ return await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
21984
22227
  strategyName: context.strategyName,
21985
22228
  exchangeName: context.exchangeName,
21986
22229
  frameName: "",
@@ -21989,27 +22232,47 @@ class LiveUtils {
21989
22232
  /**
21990
22233
  * Adjusts the trailing take-profit distance for an active pending signal.
21991
22234
  *
21992
- * Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
21993
- * Negative percentShift brings TP closer to entry, positive percentShift moves it further.
21994
- * Once direction is set on first call, subsequent calls must continue in same direction.
22235
+ * CRITICAL: Always calculates from ORIGINAL TP, not from current trailing TP.
22236
+ * This prevents error accumulation on repeated calls.
22237
+ * Larger percentShift ABSORBS smaller one (updates only towards more conservative TP).
22238
+ *
22239
+ * Updates the take-profit distance by a percentage adjustment relative to the ORIGINAL TP distance.
22240
+ * Negative percentShift brings TP closer to entry (more conservative).
22241
+ * Positive percentShift moves TP further from entry (more aggressive).
22242
+ *
22243
+ * Absorption behavior:
22244
+ * - First call: sets trailing TP unconditionally
22245
+ * - Subsequent calls: updates only if new TP is MORE CONSERVATIVE (closer to entry)
22246
+ * - For LONG: only accepts LOWER TP (never moves up, closer to entry wins)
22247
+ * - For SHORT: only accepts HIGHER TP (never moves down, closer to entry wins)
21995
22248
  *
21996
22249
  * @param symbol - Trading pair symbol
21997
- * @param percentShift - Percentage adjustment to TP distance (-100 to 100)
22250
+ * @param percentShift - Percentage adjustment to ORIGINAL TP distance (-100 to 100)
21998
22251
  * @param currentPrice - Current market price to check for intrusion
21999
22252
  * @param context - Execution context with strategyName and exchangeName
22000
- * @returns Promise that resolves when trailing TP is updated
22253
+ * @returns Promise<boolean> - true if trailing TP was set/updated, false if rejected (absorption/intrusion/conflict)
22001
22254
  *
22002
22255
  * @example
22003
22256
  * ```typescript
22004
22257
  * // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
22005
- * // Move TP further by 50%: newTP = 100 + 15% = 115
22006
- * await Live.trailingProfit("BTCUSDT", 50, 102, {
22258
+ *
22259
+ * // First call: bring TP closer by 3%
22260
+ * const success1 = await Live.trailingTake("BTCUSDT", -3, 102, {
22007
22261
  * exchangeName: "binance",
22008
22262
  * strategyName: "my-strategy"
22009
22263
  * });
22264
+ * // success1 = true, newDistance = 10% - 3% = 7%, newTP = 107
22265
+ *
22266
+ * // Second call: try to move TP further (less conservative)
22267
+ * const success2 = await Live.trailingTake("BTCUSDT", 2, 102, context);
22268
+ * // success2 = false (SKIPPED: newTP=112 > 107, less conservative, larger % absorbs smaller)
22269
+ *
22270
+ * // Third call: even more conservative
22271
+ * const success3 = await Live.trailingTake("BTCUSDT", -5, 102, context);
22272
+ * // success3 = true (ACCEPTED: newDistance = 10% - 5% = 5%, newTP = 105 < 107, more conservative)
22010
22273
  * ```
22011
22274
  */
22012
- this.trailingProfit = async (symbol, percentShift, currentPrice, context) => {
22275
+ this.trailingTake = async (symbol, percentShift, currentPrice, context) => {
22013
22276
  bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_PROFIT, {
22014
22277
  symbol,
22015
22278
  percentShift,
@@ -22023,7 +22286,7 @@ class LiveUtils {
22023
22286
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT);
22024
22287
  riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT));
22025
22288
  }
22026
- await bt.strategyCoreService.trailingProfit(false, symbol, percentShift, currentPrice, {
22289
+ return await bt.strategyCoreService.trailingTake(false, symbol, percentShift, currentPrice, {
22027
22290
  strategyName: context.strategyName,
22028
22291
  exchangeName: context.exchangeName,
22029
22292
  frameName: "",
@@ -22262,6 +22525,7 @@ class ScheduleUtils {
22262
22525
  });
22263
22526
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_DATA);
22264
22527
  bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_DATA);
22528
+ context.frameName && bt.frameValidationService.validate(context.frameName, SCHEDULE_METHOD_NAME_GET_DATA);
22265
22529
  {
22266
22530
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22267
22531
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_DATA);
@@ -22291,6 +22555,7 @@ class ScheduleUtils {
22291
22555
  });
22292
22556
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_REPORT);
22293
22557
  bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_REPORT);
22558
+ context.frameName && bt.frameValidationService.validate(context.frameName, SCHEDULE_METHOD_NAME_GET_REPORT);
22294
22559
  {
22295
22560
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22296
22561
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
@@ -22324,6 +22589,7 @@ class ScheduleUtils {
22324
22589
  });
22325
22590
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_DUMP);
22326
22591
  bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_DUMP);
22592
+ context.frameName && bt.frameValidationService.validate(context.frameName, SCHEDULE_METHOD_NAME_DUMP);
22327
22593
  {
22328
22594
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22329
22595
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
@@ -22413,6 +22679,7 @@ class Performance {
22413
22679
  static async getData(symbol, context, backtest = false) {
22414
22680
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_DATA);
22415
22681
  bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_DATA);
22682
+ context.frameName && bt.frameValidationService.validate(context.frameName, PERFORMANCE_METHOD_NAME_GET_DATA);
22416
22683
  {
22417
22684
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22418
22685
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_DATA);
@@ -22446,6 +22713,7 @@ class Performance {
22446
22713
  static async getReport(symbol, context, backtest = false, columns) {
22447
22714
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
22448
22715
  bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_REPORT);
22716
+ context.frameName && bt.frameValidationService.validate(context.frameName, PERFORMANCE_METHOD_NAME_GET_REPORT);
22449
22717
  {
22450
22718
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22451
22719
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
@@ -22476,6 +22744,7 @@ class Performance {
22476
22744
  static async dump(symbol, context, backtest = false, path = "./dump/performance", columns) {
22477
22745
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_DUMP);
22478
22746
  bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_DUMP);
22747
+ context.frameName && bt.frameValidationService.validate(context.frameName, PERFORMANCE_METHOD_NAME_DUMP);
22479
22748
  {
22480
22749
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22481
22750
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
@@ -23067,6 +23336,7 @@ class HeatUtils {
23067
23336
  bt.loggerService.info(HEAT_METHOD_NAME_GET_DATA, { strategyName: context.strategyName });
23068
23337
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_DATA);
23069
23338
  bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_DATA);
23339
+ context.frameName && bt.frameValidationService.validate(context.frameName, HEAT_METHOD_NAME_GET_DATA);
23070
23340
  {
23071
23341
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23072
23342
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_DATA);
@@ -23109,6 +23379,7 @@ class HeatUtils {
23109
23379
  bt.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName: context.strategyName });
23110
23380
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_REPORT);
23111
23381
  bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_REPORT);
23382
+ context.frameName && bt.frameValidationService.validate(context.frameName, HEAT_METHOD_NAME_GET_REPORT);
23112
23383
  {
23113
23384
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23114
23385
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
@@ -23148,6 +23419,7 @@ class HeatUtils {
23148
23419
  bt.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName: context.strategyName, path });
23149
23420
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_DUMP);
23150
23421
  bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_DUMP);
23422
+ context.frameName && bt.frameValidationService.validate(context.frameName, HEAT_METHOD_NAME_DUMP);
23151
23423
  {
23152
23424
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23153
23425
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
@@ -23481,6 +23753,7 @@ class PartialUtils {
23481
23753
  bt.loggerService.info(PARTIAL_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
23482
23754
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_DATA);
23483
23755
  bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_DATA);
23756
+ context.frameName && bt.frameValidationService.validate(context.frameName, PARTIAL_METHOD_NAME_GET_DATA);
23484
23757
  {
23485
23758
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23486
23759
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_DATA);
@@ -23531,6 +23804,7 @@ class PartialUtils {
23531
23804
  bt.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
23532
23805
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
23533
23806
  bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_REPORT);
23807
+ context.frameName && bt.frameValidationService.validate(context.frameName, PARTIAL_METHOD_NAME_GET_REPORT);
23534
23808
  {
23535
23809
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23536
23810
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
@@ -23574,6 +23848,7 @@ class PartialUtils {
23574
23848
  bt.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
23575
23849
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_DUMP);
23576
23850
  bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_DUMP);
23851
+ context.frameName && bt.frameValidationService.validate(context.frameName, PARTIAL_METHOD_NAME_DUMP);
23577
23852
  {
23578
23853
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23579
23854
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
@@ -24764,6 +25039,7 @@ class BreakevenUtils {
24764
25039
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
24765
25040
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_DATA);
24766
25041
  bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_DATA);
25042
+ context.frameName && bt.frameValidationService.validate(context.frameName, BREAKEVEN_METHOD_NAME_GET_DATA);
24767
25043
  {
24768
25044
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
24769
25045
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_DATA);
@@ -24810,6 +25086,7 @@ class BreakevenUtils {
24810
25086
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
24811
25087
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_REPORT);
24812
25088
  bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_REPORT);
25089
+ context.frameName && bt.frameValidationService.validate(context.frameName, BREAKEVEN_METHOD_NAME_GET_REPORT);
24813
25090
  {
24814
25091
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
24815
25092
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_REPORT);
@@ -24853,6 +25130,7 @@ class BreakevenUtils {
24853
25130
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
24854
25131
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_DUMP);
24855
25132
  bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_DUMP);
25133
+ context.frameName && bt.frameValidationService.validate(context.frameName, BREAKEVEN_METHOD_NAME_DUMP);
24856
25134
  {
24857
25135
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
24858
25136
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_DUMP);
@@ -24969,6 +25247,6 @@ exports.setColumns = setColumns;
24969
25247
  exports.setConfig = setConfig;
24970
25248
  exports.setLogger = setLogger;
24971
25249
  exports.stop = stop;
24972
- exports.trailingProfit = trailingProfit;
24973
25250
  exports.trailingStop = trailingStop;
25251
+ exports.trailingTake = trailingTake;
24974
25252
  exports.validate = validate;