backtest-kit 1.10.1 → 1.10.2

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
@@ -2792,6 +2792,19 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
2792
2792
  const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
2793
2793
  const isThresholdReached = currentPrice >= thresholdPrice;
2794
2794
  if (isThresholdReached && breakevenPrice > trailingStopLoss) {
2795
+ // Check for price intrusion before setting new SL
2796
+ if (currentPrice < breakevenPrice) {
2797
+ // Price already crossed the breakeven level - skip setting SL
2798
+ self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
2799
+ signalId: signal.id,
2800
+ position: signal.position,
2801
+ priceOpen: signal.priceOpen,
2802
+ breakevenPrice,
2803
+ currentPrice,
2804
+ reason: "currentPrice below breakevenPrice (LONG position)"
2805
+ });
2806
+ return false;
2807
+ }
2795
2808
  // Breakeven is better than current trailing SL - upgrade to breakeven
2796
2809
  signal._trailingPriceStopLoss = breakevenPrice;
2797
2810
  self.params.logger.info("BREAKEVEN_FN: upgraded negative trailing stop to breakeven", {
@@ -2844,6 +2857,19 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
2844
2857
  const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
2845
2858
  const isThresholdReached = currentPrice <= thresholdPrice;
2846
2859
  if (isThresholdReached && breakevenPrice < trailingStopLoss) {
2860
+ // Check for price intrusion before setting new SL
2861
+ if (currentPrice > breakevenPrice) {
2862
+ // Price already crossed the breakeven level - skip setting SL
2863
+ self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
2864
+ signalId: signal.id,
2865
+ position: signal.position,
2866
+ priceOpen: signal.priceOpen,
2867
+ breakevenPrice,
2868
+ currentPrice,
2869
+ reason: "currentPrice above breakevenPrice (SHORT position)"
2870
+ });
2871
+ return false;
2872
+ }
2847
2873
  // Breakeven is better than current trailing SL - upgrade to breakeven
2848
2874
  signal._trailingPriceStopLoss = breakevenPrice;
2849
2875
  self.params.logger.info("BREAKEVEN_FN: upgraded negative trailing stop to breakeven", {
@@ -2914,6 +2940,31 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
2914
2940
  });
2915
2941
  return false;
2916
2942
  }
2943
+ // Check for price intrusion before setting new SL
2944
+ if (signal.position === "long" && currentPrice < breakevenPrice) {
2945
+ // LONG: Price already crossed the breakeven level - skip setting SL
2946
+ self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
2947
+ signalId: signal.id,
2948
+ position: signal.position,
2949
+ priceOpen: signal.priceOpen,
2950
+ breakevenPrice,
2951
+ currentPrice,
2952
+ reason: "currentPrice below breakevenPrice (LONG position)"
2953
+ });
2954
+ return false;
2955
+ }
2956
+ if (signal.position === "short" && currentPrice > breakevenPrice) {
2957
+ // SHORT: Price already crossed the breakeven level - skip setting SL
2958
+ self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
2959
+ signalId: signal.id,
2960
+ position: signal.position,
2961
+ priceOpen: signal.priceOpen,
2962
+ breakevenPrice,
2963
+ currentPrice,
2964
+ reason: "currentPrice above breakevenPrice (SHORT position)"
2965
+ });
2966
+ return false;
2967
+ }
2917
2968
  // Move SL to breakeven (entry price)
2918
2969
  signal._trailingPriceStopLoss = breakevenPrice;
2919
2970
  self.params.logger.info("BREAKEVEN_FN executed", {
@@ -4075,6 +4126,112 @@ class ClientStrategy {
4075
4126
  });
4076
4127
  return this._scheduledSignal ? TO_PUBLIC_SIGNAL(this._scheduledSignal) : null;
4077
4128
  }
4129
+ /**
4130
+ * Checks if breakeven threshold has been reached for the current pending signal.
4131
+ *
4132
+ * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
4133
+ * to cover transaction costs (slippage + fees) and allow breakeven to be set.
4134
+ * Threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 transactions
4135
+ *
4136
+ * For LONG position:
4137
+ * - Returns true when: currentPrice >= priceOpen * (1 + threshold%)
4138
+ * - Example: entry=100, threshold=0.4% → true when price >= 100.4
4139
+ *
4140
+ * For SHORT position:
4141
+ * - Returns true when: currentPrice <= priceOpen * (1 - threshold%)
4142
+ * - Example: entry=100, threshold=0.4% → true when price <= 99.6
4143
+ *
4144
+ * Special cases:
4145
+ * - Returns false if no pending signal exists
4146
+ * - Returns true if trailing stop is already in profit zone (breakeven already achieved)
4147
+ * - Returns false if threshold not reached yet
4148
+ *
4149
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
4150
+ * @param currentPrice - Current market price to check against threshold
4151
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
4152
+ *
4153
+ * @example
4154
+ * ```typescript
4155
+ * // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
4156
+ * const canBreakeven = await strategy.getBreakeven("BTCUSDT", 100.5);
4157
+ * // Returns true (price >= 100.4)
4158
+ *
4159
+ * if (canBreakeven) {
4160
+ * await strategy.breakeven("BTCUSDT", 100.5, false);
4161
+ * }
4162
+ * ```
4163
+ */
4164
+ async getBreakeven(symbol, currentPrice) {
4165
+ this.params.logger.debug("ClientStrategy getBreakeven", {
4166
+ symbol,
4167
+ currentPrice,
4168
+ });
4169
+ // No pending signal - breakeven not available
4170
+ if (!this._pendingSignal) {
4171
+ return false;
4172
+ }
4173
+ const signal = this._pendingSignal;
4174
+ // Calculate breakeven threshold based on slippage and fees
4175
+ // Need to cover: entry slippage + entry fee + exit slippage + exit fee
4176
+ // Total: (slippage + fee) * 2 transactions
4177
+ const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
4178
+ // Check if trailing stop is already set
4179
+ if (signal._trailingPriceStopLoss !== undefined) {
4180
+ const trailingStopLoss = signal._trailingPriceStopLoss;
4181
+ if (signal.position === "long") {
4182
+ // LONG: trailing SL is positive if it's above entry (in profit zone)
4183
+ const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
4184
+ if (isPositiveTrailing) {
4185
+ // Trailing stop is already protecting profit - breakeven achieved
4186
+ return true;
4187
+ }
4188
+ // Trailing stop is negative (below entry)
4189
+ // Check if we can upgrade it to breakeven
4190
+ const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4191
+ const isThresholdReached = currentPrice >= thresholdPrice;
4192
+ const breakevenPrice = signal.priceOpen;
4193
+ // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
4194
+ return isThresholdReached && breakevenPrice > trailingStopLoss;
4195
+ }
4196
+ else {
4197
+ // SHORT: trailing SL is positive if it's below entry (in profit zone)
4198
+ const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
4199
+ if (isPositiveTrailing) {
4200
+ // Trailing stop is already protecting profit - breakeven achieved
4201
+ return true;
4202
+ }
4203
+ // Trailing stop is negative (above entry)
4204
+ // Check if we can upgrade it to breakeven
4205
+ const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4206
+ const isThresholdReached = currentPrice <= thresholdPrice;
4207
+ const breakevenPrice = signal.priceOpen;
4208
+ // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
4209
+ return isThresholdReached && breakevenPrice < trailingStopLoss;
4210
+ }
4211
+ }
4212
+ // No trailing stop set - proceed with normal breakeven logic
4213
+ const currentStopLoss = signal.priceStopLoss;
4214
+ const breakevenPrice = signal.priceOpen;
4215
+ // Calculate threshold price
4216
+ let thresholdPrice;
4217
+ let isThresholdReached;
4218
+ let canMoveToBreakeven;
4219
+ if (signal.position === "long") {
4220
+ // LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
4221
+ thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4222
+ isThresholdReached = currentPrice >= thresholdPrice;
4223
+ // Can move to breakeven only if threshold reached and SL is below entry
4224
+ canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
4225
+ }
4226
+ else {
4227
+ // SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
4228
+ thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4229
+ isThresholdReached = currentPrice <= thresholdPrice;
4230
+ // Can move to breakeven only if threshold reached and SL is above entry
4231
+ canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
4232
+ }
4233
+ return canMoveToBreakeven;
4234
+ }
4078
4235
  /**
4079
4236
  * Returns the stopped state of the strategy.
4080
4237
  *
@@ -4738,30 +4895,38 @@ class ClientStrategy {
4738
4895
  * - Throws if percentShift is not a finite number
4739
4896
  * - Throws if percentShift < -100 or > 100
4740
4897
  * - Throws if percentShift === 0
4898
+ * - Throws if currentPrice is not a positive finite number
4741
4899
  * - Skips if new SL would cross entry price
4900
+ * - Skips if currentPrice already crossed new SL level (price intrusion protection)
4742
4901
  *
4743
4902
  * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
4744
4903
  * @param percentShift - Percentage shift of SL distance [-100, 100], excluding 0
4904
+ * @param currentPrice - Current market price to check for intrusion
4745
4905
  * @param backtest - Whether running in backtest mode (controls persistence)
4746
4906
  * @returns Promise that resolves when trailing SL is updated and persisted
4747
4907
  *
4748
4908
  * @example
4749
4909
  * ```typescript
4750
- * // LONG position: entry=100, originalSL=90, distance=10
4910
+ * // LONG position: entry=100, originalSL=90, distance=10%, currentPrice=102
4911
+ *
4912
+ * // Move SL 50% closer to entry (tighten): reduces distance by 50%
4913
+ * await strategy.trailingStop("BTCUSDT", -50, 102, false);
4914
+ * // newDistance = 10% - 50% = 5%, newSL = 100 * (1 - 0.05) = 95
4751
4915
  *
4752
- * // Move SL 50% closer to entry (tighten)
4753
- * await strategy.trailingStop("BTCUSDT", -50, false);
4754
- * // newSL = 100 - 10*(1-0.5) = 95
4916
+ * // Move SL 30% away from entry (loosen): increases distance by 30%
4917
+ * await strategy.trailingStop("BTCUSDT", 30, 102, false);
4918
+ * // newDistance = 10% + 30% = 40%, newSL = 100 * (1 - 0.4) = 60
4755
4919
  *
4756
- * // Move SL 30% away from entry (loosen, allow more drawdown)
4757
- * await strategy.trailingStop("BTCUSDT", 30, false);
4758
- * // newSL = 100 - 10*(1+0.3) = 87 (SKIPPED: worse than current 95)
4920
+ * // Price intrusion example: currentPrice=92, trying to set SL=95
4921
+ * await strategy.trailingStop("BTCUSDT", -50, 92, false);
4922
+ * // SKIPPED: currentPrice (92) < newSL (95) - would trigger immediate stop
4759
4923
  * ```
4760
4924
  */
4761
- async trailingStop(symbol, percentShift, backtest) {
4925
+ async trailingStop(symbol, percentShift, currentPrice, backtest) {
4762
4926
  this.params.logger.debug("ClientStrategy trailingStop", {
4763
4927
  symbol,
4764
4928
  percentShift,
4929
+ currentPrice,
4765
4930
  hasPendingSignal: this._pendingSignal !== null,
4766
4931
  });
4767
4932
  // Validation: must have pending signal
@@ -4778,6 +4943,46 @@ class ClientStrategy {
4778
4943
  if (percentShift === 0) {
4779
4944
  throw new Error(`ClientStrategy trailingStop: percentShift cannot be 0`);
4780
4945
  }
4946
+ // Validation: currentPrice must be valid
4947
+ if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
4948
+ throw new Error(`ClientStrategy trailingStop: currentPrice must be a positive finite number, got ${currentPrice}`);
4949
+ }
4950
+ // Calculate what the new stop loss would be
4951
+ const signal = this._pendingSignal;
4952
+ const slDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
4953
+ const newSlDistancePercent = slDistancePercent + percentShift;
4954
+ let newStopLoss;
4955
+ if (signal.position === "long") {
4956
+ newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
4957
+ }
4958
+ else {
4959
+ newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
4960
+ }
4961
+ // Check for price intrusion before executing trailing logic
4962
+ if (signal.position === "long" && currentPrice < newStopLoss) {
4963
+ // LONG: Price already crossed the new stop loss level - skip setting SL
4964
+ this.params.logger.debug("ClientStrategy trailingStop: price intrusion detected, skipping SL update", {
4965
+ signalId: signal.id,
4966
+ position: signal.position,
4967
+ priceOpen: signal.priceOpen,
4968
+ newStopLoss,
4969
+ currentPrice,
4970
+ reason: "currentPrice below newStopLoss (LONG position)"
4971
+ });
4972
+ return;
4973
+ }
4974
+ if (signal.position === "short" && currentPrice > newStopLoss) {
4975
+ // SHORT: Price already crossed the new stop loss level - skip setting SL
4976
+ this.params.logger.debug("ClientStrategy trailingStop: price intrusion detected, skipping SL update", {
4977
+ signalId: signal.id,
4978
+ position: signal.position,
4979
+ priceOpen: signal.priceOpen,
4980
+ newStopLoss,
4981
+ currentPrice,
4982
+ reason: "currentPrice above newStopLoss (SHORT position)"
4983
+ });
4984
+ return;
4985
+ }
4781
4986
  // Execute trailing logic
4782
4987
  TRAILING_STOP_FN(this, this._pendingSignal, percentShift);
4783
4988
  // Persist updated signal state (inline setPendingSignal content)
@@ -4963,6 +5168,7 @@ class RiskUtils {
4963
5168
  strategyName: context.strategyName,
4964
5169
  });
4965
5170
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_DATA);
5171
+ bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_DATA);
4966
5172
  {
4967
5173
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
4968
5174
  riskName &&
@@ -5019,6 +5225,7 @@ class RiskUtils {
5019
5225
  strategyName: context.strategyName,
5020
5226
  });
5021
5227
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_REPORT);
5228
+ bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_REPORT);
5022
5229
  {
5023
5230
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
5024
5231
  riskName &&
@@ -5067,6 +5274,7 @@ class RiskUtils {
5067
5274
  path,
5068
5275
  });
5069
5276
  bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_DUMP);
5277
+ bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_DUMP);
5070
5278
  {
5071
5279
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
5072
5280
  riskName &&
@@ -5277,6 +5485,46 @@ class StrategyConnectionService {
5277
5485
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
5278
5486
  return await strategy.getScheduledSignal(symbol);
5279
5487
  };
5488
+ /**
5489
+ * Checks if breakeven threshold has been reached for the current pending signal.
5490
+ *
5491
+ * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
5492
+ * to cover transaction costs and allow breakeven to be set.
5493
+ *
5494
+ * Delegates to ClientStrategy.getBreakeven() with current execution context.
5495
+ *
5496
+ * @param backtest - Whether running in backtest mode
5497
+ * @param symbol - Trading pair symbol
5498
+ * @param currentPrice - Current market price to check against threshold
5499
+ * @param context - Execution context with strategyName, exchangeName, frameName
5500
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
5501
+ *
5502
+ * @example
5503
+ * ```typescript
5504
+ * // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
5505
+ * const canBreakeven = await strategyConnectionService.getBreakeven(
5506
+ * false,
5507
+ * "BTCUSDT",
5508
+ * 100.5,
5509
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
5510
+ * );
5511
+ * // Returns true (price >= 100.4)
5512
+ *
5513
+ * if (canBreakeven) {
5514
+ * await strategyConnectionService.breakeven(false, "BTCUSDT", 100.5, context);
5515
+ * }
5516
+ * ```
5517
+ */
5518
+ this.getBreakeven = async (backtest, symbol, currentPrice, context) => {
5519
+ this.loggerService.log("strategyConnectionService getBreakeven", {
5520
+ symbol,
5521
+ context,
5522
+ currentPrice,
5523
+ backtest,
5524
+ });
5525
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
5526
+ return await strategy.getBreakeven(symbol, currentPrice);
5527
+ };
5280
5528
  /**
5281
5529
  * Retrieves the stopped state of the strategy.
5282
5530
  *
@@ -5511,30 +5759,33 @@ class StrategyConnectionService {
5511
5759
  * @param backtest - Whether running in backtest mode
5512
5760
  * @param symbol - Trading pair symbol
5513
5761
  * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
5762
+ * @param currentPrice - Current market price to check for intrusion
5514
5763
  * @param context - Execution context with strategyName, exchangeName, frameName
5515
5764
  * @returns Promise that resolves when trailing SL is updated
5516
5765
  *
5517
5766
  * @example
5518
5767
  * ```typescript
5519
- * // LONG: entry=100, originalSL=90, distance=10
5520
- * // Tighten stop by 50%: newSL = 100 - 10*(1-0.5) = 95
5768
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
5769
+ * // Tighten stop by 50%: newSL = 100 - 5% = 95
5521
5770
  * await strategyConnectionService.trailingStop(
5522
5771
  * false,
5523
5772
  * "BTCUSDT",
5524
5773
  * -50,
5774
+ * 102,
5525
5775
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
5526
5776
  * );
5527
5777
  * ```
5528
5778
  */
5529
- this.trailingStop = async (backtest, symbol, percentShift, context) => {
5779
+ this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
5530
5780
  this.loggerService.log("strategyConnectionService trailingStop", {
5531
5781
  symbol,
5532
5782
  context,
5533
5783
  percentShift,
5784
+ currentPrice,
5534
5785
  backtest,
5535
5786
  });
5536
5787
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
5537
- await strategy.trailingStop(symbol, percentShift, backtest);
5788
+ await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
5538
5789
  };
5539
5790
  /**
5540
5791
  * Delegates to ClientStrategy.breakeven() with current execution context.
@@ -6675,6 +6926,46 @@ class StrategyCoreService {
6675
6926
  await this.validate(symbol, context);
6676
6927
  return await this.strategyConnectionService.getScheduledSignal(backtest, symbol, context);
6677
6928
  };
6929
+ /**
6930
+ * Checks if breakeven threshold has been reached for the current pending signal.
6931
+ *
6932
+ * Validates strategy existence and delegates to connection service
6933
+ * to check if price has moved far enough to cover transaction costs.
6934
+ *
6935
+ * Does not require execution context as this is a state query operation.
6936
+ *
6937
+ * @param backtest - Whether running in backtest mode
6938
+ * @param symbol - Trading pair symbol
6939
+ * @param currentPrice - Current market price to check against threshold
6940
+ * @param context - Execution context with strategyName, exchangeName, frameName
6941
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
6942
+ *
6943
+ * @example
6944
+ * ```typescript
6945
+ * // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
6946
+ * const canBreakeven = await strategyCoreService.getBreakeven(
6947
+ * false,
6948
+ * "BTCUSDT",
6949
+ * 100.5,
6950
+ * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
6951
+ * );
6952
+ * // Returns true (price >= 100.4)
6953
+ *
6954
+ * if (canBreakeven) {
6955
+ * await strategyCoreService.breakeven(false, "BTCUSDT", 100.5, context);
6956
+ * }
6957
+ * ```
6958
+ */
6959
+ this.getBreakeven = async (backtest, symbol, currentPrice, context) => {
6960
+ this.loggerService.log("strategyCoreService getBreakeven", {
6961
+ symbol,
6962
+ currentPrice,
6963
+ context,
6964
+ backtest,
6965
+ });
6966
+ await this.validate(symbol, context);
6967
+ return await this.strategyConnectionService.getBreakeven(backtest, symbol, currentPrice, context);
6968
+ };
6678
6969
  /**
6679
6970
  * Checks if the strategy has been stopped.
6680
6971
  *
@@ -6904,30 +7195,33 @@ class StrategyCoreService {
6904
7195
  * @param backtest - Whether running in backtest mode
6905
7196
  * @param symbol - Trading pair symbol
6906
7197
  * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
7198
+ * @param currentPrice - Current market price to check for intrusion
6907
7199
  * @param context - Execution context with strategyName, exchangeName, frameName
6908
7200
  * @returns Promise that resolves when trailing SL is updated
6909
7201
  *
6910
7202
  * @example
6911
7203
  * ```typescript
6912
- * // LONG: entry=100, originalSL=90, distance=10
6913
- * // Tighten stop by 50%: newSL = 100 - 10*(1-0.5) = 95
7204
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
7205
+ * // Tighten stop by 50%: newSL = 100 - 5% = 95
6914
7206
  * await strategyCoreService.trailingStop(
6915
7207
  * false,
6916
7208
  * "BTCUSDT",
6917
7209
  * -50,
7210
+ * 102,
6918
7211
  * { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
6919
7212
  * );
6920
7213
  * ```
6921
7214
  */
6922
- this.trailingStop = async (backtest, symbol, percentShift, context) => {
7215
+ this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
6923
7216
  this.loggerService.log("strategyCoreService trailingStop", {
6924
7217
  symbol,
6925
7218
  percentShift,
7219
+ currentPrice,
6926
7220
  context,
6927
7221
  backtest,
6928
7222
  });
6929
7223
  await this.validate(symbol, context);
6930
- return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, context);
7224
+ return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
6931
7225
  };
6932
7226
  /**
6933
7227
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -7036,6 +7330,7 @@ class RiskGlobalService {
7036
7330
  this.loggerService = inject(TYPES.loggerService);
7037
7331
  this.riskConnectionService = inject(TYPES.riskConnectionService);
7038
7332
  this.riskValidationService = inject(TYPES.riskValidationService);
7333
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
7039
7334
  /**
7040
7335
  * Validates risk configuration.
7041
7336
  * Memoized to avoid redundant validations for the same risk-exchange-frame combination.
@@ -7048,6 +7343,7 @@ class RiskGlobalService {
7048
7343
  payload,
7049
7344
  });
7050
7345
  this.riskValidationService.validate(payload.riskName, "riskGlobalService validate");
7346
+ this.exchangeValidationService.validate(payload.exchangeName, "riskGlobalService validate");
7051
7347
  });
7052
7348
  /**
7053
7349
  * Checks if a signal should be allowed based on risk limits.
@@ -15320,6 +15616,10 @@ class PartialGlobalService {
15320
15616
  * Risk validation service for validating risk existence.
15321
15617
  */
15322
15618
  this.riskValidationService = inject(TYPES.riskValidationService);
15619
+ /**
15620
+ * Exchange validation service for validating exchange existence.
15621
+ */
15622
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
15323
15623
  /**
15324
15624
  * Validates strategy and associated risk configuration.
15325
15625
  * Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
@@ -15333,6 +15633,7 @@ class PartialGlobalService {
15333
15633
  methodName,
15334
15634
  });
15335
15635
  this.strategyValidationService.validate(context.strategyName, methodName);
15636
+ this.exchangeValidationService.validate(context.exchangeName, methodName);
15336
15637
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
15337
15638
  riskName && this.riskValidationService.validate(riskName, methodName);
15338
15639
  riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
@@ -16294,6 +16595,10 @@ class BreakevenGlobalService {
16294
16595
  * Risk validation service for validating risk existence.
16295
16596
  */
16296
16597
  this.riskValidationService = inject(TYPES.riskValidationService);
16598
+ /**
16599
+ * Exchange validation service for validating exchange existence.
16600
+ */
16601
+ this.exchangeValidationService = inject(TYPES.exchangeValidationService);
16297
16602
  /**
16298
16603
  * Validates strategy and associated risk configuration.
16299
16604
  * Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
@@ -16307,6 +16612,7 @@ class BreakevenGlobalService {
16307
16612
  methodName,
16308
16613
  });
16309
16614
  this.strategyValidationService.validate(context.strategyName, methodName);
16615
+ this.exchangeValidationService.validate(context.exchangeName, methodName);
16310
16616
  const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
16311
16617
  riskName && this.riskValidationService.validate(riskName, methodName);
16312
16618
  riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
@@ -17787,21 +18093,23 @@ async function partialLoss(symbol, percentToClose) {
17787
18093
  *
17788
18094
  * @param symbol - Trading pair symbol
17789
18095
  * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
18096
+ * @param currentPrice - Current market price to check for intrusion
17790
18097
  * @returns Promise that resolves when trailing SL is updated
17791
18098
  *
17792
18099
  * @example
17793
18100
  * ```typescript
17794
18101
  * import { trailingStop } from "backtest-kit";
17795
18102
  *
17796
- * // LONG: entry=100, originalSL=90, distance=10
17797
- * // Tighten stop by 50%: newSL = 100 - 10*(1-0.5) = 95
17798
- * await trailingStop("BTCUSDT", -50);
18103
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
18104
+ * // Tighten stop by 50%: newSL = 100 - 5% = 95
18105
+ * await trailingStop("BTCUSDT", -50, 102);
17799
18106
  * ```
17800
18107
  */
17801
- async function trailingStop(symbol, percentShift) {
18108
+ async function trailingStop(symbol, percentShift, currentPrice) {
17802
18109
  bt.loggerService.info(TRAILING_STOP_METHOD_NAME, {
17803
18110
  symbol,
17804
18111
  percentShift,
18112
+ currentPrice,
17805
18113
  });
17806
18114
  if (!ExecutionContextService.hasContext()) {
17807
18115
  throw new Error("trailingStop requires an execution context");
@@ -17811,7 +18119,7 @@ async function trailingStop(symbol, percentShift) {
17811
18119
  }
17812
18120
  const { backtest: isBacktest } = bt.executionContextService.context;
17813
18121
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
17814
- await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, { exchangeName, frameName, strategyName });
18122
+ await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
17815
18123
  }
17816
18124
  /**
17817
18125
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -19753,6 +20061,7 @@ const BACKTEST_METHOD_NAME_TASK = "BacktestUtils.task";
19753
20061
  const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
19754
20062
  const BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL = "BacktestUtils.getPendingSignal";
19755
20063
  const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
20064
+ const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
19756
20065
  const BACKTEST_METHOD_NAME_CANCEL = "BacktestUtils.cancel";
19757
20066
  const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.partialProfit";
19758
20067
  const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.partialLoss";
@@ -20123,6 +20432,7 @@ class BacktestUtils {
20123
20432
  context,
20124
20433
  });
20125
20434
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL);
20435
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL);
20126
20436
  {
20127
20437
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20128
20438
  riskName &&
@@ -20154,6 +20464,7 @@ class BacktestUtils {
20154
20464
  context,
20155
20465
  });
20156
20466
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL);
20467
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL);
20157
20468
  {
20158
20469
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20159
20470
  riskName &&
@@ -20163,6 +20474,47 @@ class BacktestUtils {
20163
20474
  }
20164
20475
  return await bt.strategyCoreService.getScheduledSignal(true, symbol, context);
20165
20476
  };
20477
+ /**
20478
+ * Checks if breakeven threshold has been reached for the current pending signal.
20479
+ *
20480
+ * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
20481
+ * to cover transaction costs (slippage + fees) and allow breakeven to be set.
20482
+ *
20483
+ * @param symbol - Trading pair symbol
20484
+ * @param currentPrice - Current market price to check against threshold
20485
+ * @param context - Execution context with strategyName, exchangeName, frameName
20486
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
20487
+ *
20488
+ * @example
20489
+ * ```typescript
20490
+ * const canBreakeven = await Backtest.getBreakeven("BTCUSDT", 100.5, {
20491
+ * strategyName: "my-strategy",
20492
+ * exchangeName: "binance",
20493
+ * frameName: "backtest_frame"
20494
+ * });
20495
+ * if (canBreakeven) {
20496
+ * console.log("Breakeven threshold reached");
20497
+ * await Backtest.breakeven("BTCUSDT", 100.5, context);
20498
+ * }
20499
+ * ```
20500
+ */
20501
+ this.getBreakeven = async (symbol, currentPrice, context) => {
20502
+ bt.loggerService.info(BACKTEST_METHOD_NAME_GET_BREAKEVEN, {
20503
+ symbol,
20504
+ currentPrice,
20505
+ context,
20506
+ });
20507
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
20508
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
20509
+ {
20510
+ const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20511
+ riskName &&
20512
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
20513
+ riskList &&
20514
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_BREAKEVEN));
20515
+ }
20516
+ return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
20517
+ };
20166
20518
  /**
20167
20519
  * Stops the strategy from generating new signals.
20168
20520
  *
@@ -20191,6 +20543,7 @@ class BacktestUtils {
20191
20543
  context,
20192
20544
  });
20193
20545
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_STOP);
20546
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_STOP);
20194
20547
  {
20195
20548
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20196
20549
  riskName &&
@@ -20230,6 +20583,7 @@ class BacktestUtils {
20230
20583
  cancelId,
20231
20584
  });
20232
20585
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL);
20586
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL);
20233
20587
  {
20234
20588
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20235
20589
  riskName &&
@@ -20273,6 +20627,7 @@ class BacktestUtils {
20273
20627
  context,
20274
20628
  });
20275
20629
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT);
20630
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT);
20276
20631
  {
20277
20632
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20278
20633
  riskName &&
@@ -20316,6 +20671,7 @@ class BacktestUtils {
20316
20671
  context,
20317
20672
  });
20318
20673
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_LOSS);
20674
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_LOSS);
20319
20675
  {
20320
20676
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20321
20677
  riskName &&
@@ -20333,27 +20689,30 @@ class BacktestUtils {
20333
20689
  *
20334
20690
  * @param symbol - Trading pair symbol
20335
20691
  * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
20692
+ * @param currentPrice - Current market price to check for intrusion
20336
20693
  * @param context - Execution context with strategyName, exchangeName, and frameName
20337
20694
  * @returns Promise that resolves when trailing SL is updated
20338
20695
  *
20339
20696
  * @example
20340
20697
  * ```typescript
20341
- * // LONG: entry=100, originalSL=90, distance=10
20342
- * // Tighten stop by 50%: newSL = 100 - 10*(1-0.5) = 95
20343
- * await Backtest.trailingStop("BTCUSDT", -50, {
20698
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
20699
+ * // Tighten stop by 50%: newSL = 100 - 5% = 95
20700
+ * await Backtest.trailingStop("BTCUSDT", -50, 102, {
20344
20701
  * exchangeName: "binance",
20345
20702
  * frameName: "frame1",
20346
20703
  * strategyName: "my-strategy"
20347
20704
  * });
20348
20705
  * ```
20349
20706
  */
20350
- this.trailingStop = async (symbol, percentShift, context) => {
20707
+ this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
20351
20708
  bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_STOP, {
20352
20709
  symbol,
20353
20710
  percentShift,
20711
+ currentPrice,
20354
20712
  context,
20355
20713
  });
20356
20714
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_STOP);
20715
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_STOP);
20357
20716
  {
20358
20717
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20359
20718
  riskName &&
@@ -20361,7 +20720,7 @@ class BacktestUtils {
20361
20720
  riskList &&
20362
20721
  riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP));
20363
20722
  }
20364
- await bt.strategyCoreService.trailingStop(true, symbol, percentShift, context);
20723
+ await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
20365
20724
  };
20366
20725
  /**
20367
20726
  * Moves stop-loss to breakeven when price reaches threshold.
@@ -20391,6 +20750,7 @@ class BacktestUtils {
20391
20750
  context,
20392
20751
  });
20393
20752
  bt.strategyValidationService.validate(context.strategyName, "Backtest.breakeven");
20753
+ bt.exchangeValidationService.validate(context.exchangeName, "Backtest.breakeven");
20394
20754
  {
20395
20755
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20396
20756
  riskName &&
@@ -20424,6 +20784,7 @@ class BacktestUtils {
20424
20784
  context,
20425
20785
  });
20426
20786
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_DATA);
20787
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_DATA);
20427
20788
  {
20428
20789
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20429
20790
  riskName &&
@@ -20458,6 +20819,7 @@ class BacktestUtils {
20458
20819
  context,
20459
20820
  });
20460
20821
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
20822
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_REPORT);
20461
20823
  {
20462
20824
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20463
20825
  riskName &&
@@ -20500,6 +20862,7 @@ class BacktestUtils {
20500
20862
  path,
20501
20863
  });
20502
20864
  bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_DUMP);
20865
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_DUMP);
20503
20866
  {
20504
20867
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20505
20868
  riskName &&
@@ -20558,6 +20921,7 @@ const LIVE_METHOD_NAME_TASK = "LiveUtils.task";
20558
20921
  const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
20559
20922
  const LIVE_METHOD_NAME_GET_PENDING_SIGNAL = "LiveUtils.getPendingSignal";
20560
20923
  const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
20924
+ const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
20561
20925
  const LIVE_METHOD_NAME_CANCEL = "LiveUtils.cancel";
20562
20926
  const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.partialProfit";
20563
20927
  const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.partialLoss";
@@ -20899,6 +21263,7 @@ class LiveUtils {
20899
21263
  context,
20900
21264
  });
20901
21265
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
21266
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
20902
21267
  {
20903
21268
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20904
21269
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
@@ -20932,6 +21297,7 @@ class LiveUtils {
20932
21297
  context,
20933
21298
  });
20934
21299
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
21300
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
20935
21301
  {
20936
21302
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20937
21303
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
@@ -20943,6 +21309,48 @@ class LiveUtils {
20943
21309
  frameName: "",
20944
21310
  });
20945
21311
  };
21312
+ /**
21313
+ * Checks if breakeven threshold has been reached for the current pending signal.
21314
+ *
21315
+ * Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
21316
+ * to cover transaction costs (slippage + fees) and allow breakeven to be set.
21317
+ *
21318
+ * @param symbol - Trading pair symbol
21319
+ * @param currentPrice - Current market price to check against threshold
21320
+ * @param context - Execution context with strategyName and exchangeName
21321
+ * @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
21322
+ *
21323
+ * @example
21324
+ * ```typescript
21325
+ * const canBreakeven = await Live.getBreakeven("BTCUSDT", 100.5, {
21326
+ * strategyName: "my-strategy",
21327
+ * exchangeName: "binance"
21328
+ * });
21329
+ * if (canBreakeven) {
21330
+ * console.log("Breakeven threshold reached");
21331
+ * await Live.breakeven("BTCUSDT", 100.5, context);
21332
+ * }
21333
+ * ```
21334
+ */
21335
+ this.getBreakeven = async (symbol, currentPrice, context) => {
21336
+ bt.loggerService.info(LIVE_METHOD_NAME_GET_BREAKEVEN, {
21337
+ symbol,
21338
+ currentPrice,
21339
+ context,
21340
+ });
21341
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_BREAKEVEN);
21342
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_BREAKEVEN);
21343
+ {
21344
+ const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21345
+ riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN);
21346
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN));
21347
+ }
21348
+ return await bt.strategyCoreService.getBreakeven(false, symbol, currentPrice, {
21349
+ strategyName: context.strategyName,
21350
+ exchangeName: context.exchangeName,
21351
+ frameName: "",
21352
+ });
21353
+ };
20946
21354
  /**
20947
21355
  * Stops the strategy from generating new signals.
20948
21356
  *
@@ -20966,6 +21374,7 @@ class LiveUtils {
20966
21374
  context,
20967
21375
  });
20968
21376
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_STOP);
21377
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_STOP);
20969
21378
  {
20970
21379
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
20971
21380
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP);
@@ -21007,6 +21416,7 @@ class LiveUtils {
21007
21416
  cancelId,
21008
21417
  });
21009
21418
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL);
21419
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL);
21010
21420
  {
21011
21421
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21012
21422
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL);
@@ -21051,6 +21461,7 @@ class LiveUtils {
21051
21461
  context,
21052
21462
  });
21053
21463
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
21464
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
21054
21465
  {
21055
21466
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21056
21467
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
@@ -21095,6 +21506,7 @@ class LiveUtils {
21095
21506
  context,
21096
21507
  });
21097
21508
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS);
21509
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS);
21098
21510
  {
21099
21511
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21100
21512
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS);
@@ -21114,32 +21526,35 @@ class LiveUtils {
21114
21526
  *
21115
21527
  * @param symbol - Trading pair symbol
21116
21528
  * @param percentShift - Percentage adjustment to SL distance (-100 to 100)
21529
+ * @param currentPrice - Current market price to check for intrusion
21117
21530
  * @param context - Execution context with strategyName and exchangeName
21118
21531
  * @returns Promise that resolves when trailing SL is updated
21119
21532
  *
21120
21533
  * @example
21121
21534
  * ```typescript
21122
- * // LONG: entry=100, originalSL=90, distance=10
21123
- * // Tighten stop by 50%: newSL = 100 - 10*(1-0.5) = 95
21124
- * await Live.trailingStop("BTCUSDT", -50, {
21535
+ * // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
21536
+ * // Tighten stop by 50%: newSL = 100 - 5% = 95
21537
+ * await Live.trailingStop("BTCUSDT", -50, 102, {
21125
21538
  * exchangeName: "binance",
21126
21539
  * strategyName: "my-strategy"
21127
21540
  * });
21128
21541
  * ```
21129
21542
  */
21130
- this.trailingStop = async (symbol, percentShift, context) => {
21543
+ this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
21131
21544
  bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_STOP, {
21132
21545
  symbol,
21133
21546
  percentShift,
21547
+ currentPrice,
21134
21548
  context,
21135
21549
  });
21136
21550
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_STOP);
21551
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP);
21137
21552
  {
21138
21553
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21139
21554
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP);
21140
21555
  riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP));
21141
21556
  }
21142
- await bt.strategyCoreService.trailingStop(false, symbol, percentShift, {
21557
+ await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
21143
21558
  strategyName: context.strategyName,
21144
21559
  exchangeName: context.exchangeName,
21145
21560
  frameName: "",
@@ -21173,6 +21588,7 @@ class LiveUtils {
21173
21588
  context,
21174
21589
  });
21175
21590
  bt.strategyValidationService.validate(context.strategyName, "Live.breakeven");
21591
+ bt.exchangeValidationService.validate(context.exchangeName, "Live.breakeven");
21176
21592
  {
21177
21593
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21178
21594
  riskName && bt.riskValidationService.validate(riskName, "Live.breakeven");
@@ -21208,6 +21624,7 @@ class LiveUtils {
21208
21624
  context,
21209
21625
  });
21210
21626
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_DATA);
21627
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_DATA);
21211
21628
  {
21212
21629
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21213
21630
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA);
@@ -21240,6 +21657,7 @@ class LiveUtils {
21240
21657
  context,
21241
21658
  });
21242
21659
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_REPORT);
21660
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_REPORT);
21243
21661
  {
21244
21662
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21245
21663
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT);
@@ -21280,6 +21698,7 @@ class LiveUtils {
21280
21698
  path,
21281
21699
  });
21282
21700
  bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_DUMP);
21701
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_DUMP);
21283
21702
  {
21284
21703
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21285
21704
  riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP);
@@ -21373,6 +21792,7 @@ class ScheduleUtils {
21373
21792
  backtest,
21374
21793
  });
21375
21794
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_DATA);
21795
+ bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_DATA);
21376
21796
  {
21377
21797
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21378
21798
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_DATA);
@@ -21401,6 +21821,7 @@ class ScheduleUtils {
21401
21821
  backtest,
21402
21822
  });
21403
21823
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_REPORT);
21824
+ bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_REPORT);
21404
21825
  {
21405
21826
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21406
21827
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
@@ -21433,6 +21854,7 @@ class ScheduleUtils {
21433
21854
  path,
21434
21855
  });
21435
21856
  bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_DUMP);
21857
+ bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_DUMP);
21436
21858
  {
21437
21859
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21438
21860
  riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
@@ -21521,6 +21943,7 @@ class Performance {
21521
21943
  */
21522
21944
  static async getData(symbol, context, backtest = false) {
21523
21945
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_DATA);
21946
+ bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_DATA);
21524
21947
  {
21525
21948
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21526
21949
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_DATA);
@@ -21553,6 +21976,7 @@ class Performance {
21553
21976
  */
21554
21977
  static async getReport(symbol, context, backtest = false, columns) {
21555
21978
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
21979
+ bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_REPORT);
21556
21980
  {
21557
21981
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21558
21982
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
@@ -21582,6 +22006,7 @@ class Performance {
21582
22006
  */
21583
22007
  static async dump(symbol, context, backtest = false, path = "./dump/performance", columns) {
21584
22008
  bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_DUMP);
22009
+ bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_DUMP);
21585
22010
  {
21586
22011
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
21587
22012
  riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
@@ -22172,6 +22597,7 @@ class HeatUtils {
22172
22597
  this.getData = async (context, backtest = false) => {
22173
22598
  bt.loggerService.info(HEAT_METHOD_NAME_GET_DATA, { strategyName: context.strategyName });
22174
22599
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_DATA);
22600
+ bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_DATA);
22175
22601
  {
22176
22602
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22177
22603
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_DATA);
@@ -22213,6 +22639,7 @@ class HeatUtils {
22213
22639
  this.getReport = async (context, backtest = false, columns) => {
22214
22640
  bt.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName: context.strategyName });
22215
22641
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_REPORT);
22642
+ bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_REPORT);
22216
22643
  {
22217
22644
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22218
22645
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
@@ -22251,6 +22678,7 @@ class HeatUtils {
22251
22678
  this.dump = async (context, backtest = false, path, columns) => {
22252
22679
  bt.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName: context.strategyName, path });
22253
22680
  bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_DUMP);
22681
+ bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_DUMP);
22254
22682
  {
22255
22683
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22256
22684
  riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
@@ -22583,6 +23011,7 @@ class PartialUtils {
22583
23011
  this.getData = async (symbol, context, backtest = false) => {
22584
23012
  bt.loggerService.info(PARTIAL_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
22585
23013
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_DATA);
23014
+ bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_DATA);
22586
23015
  {
22587
23016
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22588
23017
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_DATA);
@@ -22632,6 +23061,7 @@ class PartialUtils {
22632
23061
  this.getReport = async (symbol, context, backtest = false, columns) => {
22633
23062
  bt.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
22634
23063
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
23064
+ bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_REPORT);
22635
23065
  {
22636
23066
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22637
23067
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
@@ -22674,6 +23104,7 @@ class PartialUtils {
22674
23104
  this.dump = async (symbol, context, backtest = false, path, columns) => {
22675
23105
  bt.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
22676
23106
  bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_DUMP);
23107
+ bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_DUMP);
22677
23108
  {
22678
23109
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
22679
23110
  riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
@@ -23863,6 +24294,7 @@ class BreakevenUtils {
23863
24294
  this.getData = async (symbol, context, backtest = false) => {
23864
24295
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
23865
24296
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_DATA);
24297
+ bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_DATA);
23866
24298
  {
23867
24299
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23868
24300
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_DATA);
@@ -23908,6 +24340,7 @@ class BreakevenUtils {
23908
24340
  this.getReport = async (symbol, context, backtest = false, columns) => {
23909
24341
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
23910
24342
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_REPORT);
24343
+ bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_REPORT);
23911
24344
  {
23912
24345
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23913
24346
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_REPORT);
@@ -23950,6 +24383,7 @@ class BreakevenUtils {
23950
24383
  this.dump = async (symbol, context, backtest = false, path, columns) => {
23951
24384
  bt.loggerService.info(BREAKEVEN_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
23952
24385
  bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_DUMP);
24386
+ bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_DUMP);
23953
24387
  {
23954
24388
  const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
23955
24389
  riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_DUMP);