backtest-kit 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.mjs CHANGED
@@ -2880,6 +2880,27 @@ class ExchangeConnectionService {
2880
2880
  }
2881
2881
  }
2882
2882
 
2883
+ /**
2884
+ * Returns the effective entry price for price calculations.
2885
+ *
2886
+ * When the _entry array exists and has at least one element, returns
2887
+ * the simple arithmetic mean of all entry prices (DCA average).
2888
+ * Otherwise returns the original signal.priceOpen.
2889
+ *
2890
+ * This mirrors the _trailingPriceStopLoss pattern: original price is preserved
2891
+ * in signal.priceOpen (for identity/tracking), while calculations use the
2892
+ * effective averaged price returned by this function.
2893
+ *
2894
+ * @param signal - Signal row (ISignalRow or IScheduledSignalRow)
2895
+ * @returns Effective entry price for distance and PNL calculations
2896
+ */
2897
+ const getEffectivePriceOpen = (signal) => {
2898
+ if (signal._entry && signal._entry.length > 0) {
2899
+ return signal._entry.reduce((sum, e) => sum + e.price, 0) / signal._entry.length;
2900
+ }
2901
+ return signal.priceOpen;
2902
+ };
2903
+
2883
2904
  /**
2884
2905
  * Calculates profit/loss for a closed signal with slippage and fees.
2885
2906
  *
@@ -2930,7 +2951,7 @@ class ExchangeConnectionService {
2930
2951
  * ```
2931
2952
  */
2932
2953
  const toProfitLossDto = (signal, priceClose) => {
2933
- const priceOpen = signal.priceOpen;
2954
+ const priceOpen = getEffectivePriceOpen(signal);
2934
2955
  // Calculate weighted PNL with partial closes
2935
2956
  if (signal._partial && signal._partial.length > 0) {
2936
2957
  let totalWeightedPnl = 0;
@@ -3184,6 +3205,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3184
3205
  percentToClose: commit.percentToClose,
3185
3206
  currentPrice: commit.currentPrice,
3186
3207
  timestamp,
3208
+ totalEntries: publicSignal.totalEntries,
3187
3209
  position: publicSignal.position,
3188
3210
  priceOpen: publicSignal.priceOpen,
3189
3211
  signalId: publicSignal.id,
@@ -3191,6 +3213,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3191
3213
  priceStopLoss: publicSignal.priceStopLoss,
3192
3214
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3193
3215
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3216
+ originalPriceOpen: publicSignal.originalPriceOpen,
3194
3217
  scheduledAt: publicSignal.scheduledAt,
3195
3218
  pendingAt: publicSignal.pendingAt,
3196
3219
  });
@@ -3207,6 +3230,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3207
3230
  percentToClose: commit.percentToClose,
3208
3231
  currentPrice: commit.currentPrice,
3209
3232
  timestamp,
3233
+ totalEntries: publicSignal.totalEntries,
3210
3234
  position: publicSignal.position,
3211
3235
  priceOpen: publicSignal.priceOpen,
3212
3236
  signalId: publicSignal.id,
@@ -3214,6 +3238,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3214
3238
  priceStopLoss: publicSignal.priceStopLoss,
3215
3239
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3216
3240
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3241
+ originalPriceOpen: publicSignal.originalPriceOpen,
3217
3242
  scheduledAt: publicSignal.scheduledAt,
3218
3243
  pendingAt: publicSignal.pendingAt,
3219
3244
  });
@@ -3229,6 +3254,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3229
3254
  backtest: commit.backtest,
3230
3255
  currentPrice: commit.currentPrice,
3231
3256
  timestamp,
3257
+ totalEntries: publicSignal.totalEntries,
3232
3258
  signalId: publicSignal.id,
3233
3259
  position: publicSignal.position,
3234
3260
  priceOpen: publicSignal.priceOpen,
@@ -3236,6 +3262,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3236
3262
  priceStopLoss: publicSignal.priceStopLoss,
3237
3263
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3238
3264
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3265
+ originalPriceOpen: publicSignal.originalPriceOpen,
3239
3266
  scheduledAt: publicSignal.scheduledAt,
3240
3267
  pendingAt: publicSignal.pendingAt,
3241
3268
  });
@@ -3252,6 +3279,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3252
3279
  percentShift: commit.percentShift,
3253
3280
  currentPrice: commit.currentPrice,
3254
3281
  timestamp,
3282
+ totalEntries: publicSignal.totalEntries,
3255
3283
  signalId: publicSignal.id,
3256
3284
  position: publicSignal.position,
3257
3285
  priceOpen: publicSignal.priceOpen,
@@ -3259,6 +3287,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3259
3287
  priceStopLoss: publicSignal.priceStopLoss,
3260
3288
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3261
3289
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3290
+ originalPriceOpen: publicSignal.originalPriceOpen,
3262
3291
  scheduledAt: publicSignal.scheduledAt,
3263
3292
  pendingAt: publicSignal.pendingAt,
3264
3293
  });
@@ -3275,6 +3304,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3275
3304
  percentShift: commit.percentShift,
3276
3305
  currentPrice: commit.currentPrice,
3277
3306
  timestamp,
3307
+ totalEntries: publicSignal.totalEntries,
3278
3308
  signalId: publicSignal.id,
3279
3309
  position: publicSignal.position,
3280
3310
  priceOpen: publicSignal.priceOpen,
@@ -3282,6 +3312,33 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3282
3312
  priceStopLoss: publicSignal.priceStopLoss,
3283
3313
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3284
3314
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3315
+ originalPriceOpen: publicSignal.originalPriceOpen,
3316
+ scheduledAt: publicSignal.scheduledAt,
3317
+ pendingAt: publicSignal.pendingAt,
3318
+ });
3319
+ continue;
3320
+ }
3321
+ if (commit.action === "average-buy") {
3322
+ const effectivePriceOpen = getEffectivePriceOpen(self._pendingSignal);
3323
+ await CALL_COMMIT_FN(self, {
3324
+ action: "average-buy",
3325
+ symbol: commit.symbol,
3326
+ strategyName: self.params.strategyName,
3327
+ exchangeName: self.params.exchangeName,
3328
+ frameName: self.params.frameName,
3329
+ backtest: commit.backtest,
3330
+ currentPrice: commit.currentPrice,
3331
+ effectivePriceOpen,
3332
+ timestamp,
3333
+ totalEntries: publicSignal.totalEntries,
3334
+ signalId: publicSignal.id,
3335
+ position: publicSignal.position,
3336
+ priceOpen: publicSignal.originalPriceOpen,
3337
+ priceTakeProfit: publicSignal.priceTakeProfit,
3338
+ priceStopLoss: publicSignal.priceStopLoss,
3339
+ originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3340
+ originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3341
+ originalPriceOpen: publicSignal.originalPriceOpen,
3285
3342
  scheduledAt: publicSignal.scheduledAt,
3286
3343
  pendingAt: publicSignal.pendingAt,
3287
3344
  });
@@ -3342,13 +3399,20 @@ const TO_PUBLIC_SIGNAL = (signal) => {
3342
3399
  const partialExecuted = ("_partial" in signal && Array.isArray(signal._partial))
3343
3400
  ? signal._partial.reduce((sum, partial) => sum + partial.percent, 0)
3344
3401
  : 0;
3402
+ const totalEntries = ("_entry" in signal && Array.isArray(signal._entry))
3403
+ ? signal._entry.length
3404
+ : 1;
3405
+ const effectivePriceOpen = "_entry" in signal ? getEffectivePriceOpen(signal) : signal.priceOpen;
3345
3406
  return {
3346
3407
  ...structuredClone(signal),
3408
+ priceOpen: effectivePriceOpen,
3347
3409
  priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
3348
3410
  priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
3411
+ originalPriceOpen: signal.priceOpen,
3349
3412
  originalPriceStopLoss: signal.priceStopLoss,
3350
3413
  originalPriceTakeProfit: signal.priceTakeProfit,
3351
3414
  partialExecuted,
3415
+ totalEntries,
3352
3416
  };
3353
3417
  };
3354
3418
  const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
@@ -3678,6 +3742,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
3678
3742
  scheduledAt: currentTime,
3679
3743
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
3680
3744
  _isScheduled: false,
3745
+ _entry: [{ price: signal.priceOpen }],
3681
3746
  };
3682
3747
  // Валидируем сигнал перед возвратом
3683
3748
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -3699,6 +3764,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
3699
3764
  scheduledAt: currentTime,
3700
3765
  pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
3701
3766
  _isScheduled: true,
3767
+ _entry: [{ price: signal.priceOpen }],
3702
3768
  };
3703
3769
  // Валидируем сигнал перед возвратом
3704
3770
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -3716,6 +3782,7 @@ const GET_SIGNAL_FN = trycatch(async (self) => {
3716
3782
  scheduledAt: currentTime,
3717
3783
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
3718
3784
  _isScheduled: false,
3785
+ _entry: [{ price: currentPrice }],
3719
3786
  };
3720
3787
  // Валидируем сигнал перед возвратом
3721
3788
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -3862,9 +3929,10 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
3862
3929
  return true;
3863
3930
  };
3864
3931
  const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3932
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
3865
3933
  // CRITICAL: Always calculate from ORIGINAL SL, not from current trailing SL
3866
3934
  // This prevents error accumulation on repeated calls
3867
- const originalSlDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
3935
+ const originalSlDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
3868
3936
  // Calculate new stop-loss distance percentage by adding shift to ORIGINAL distance
3869
3937
  // Negative percentShift: reduces distance % (tightens stop, moves SL toward entry or beyond)
3870
3938
  // Positive percentShift: increases distance % (loosens stop, moves SL away from entry)
@@ -3876,13 +3944,13 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3876
3944
  // LONG: SL is below entry (or above entry if in profit zone)
3877
3945
  // Formula: entry * (1 - newDistance%)
3878
3946
  // Example: entry=100, originalSL=90 (10%), shift=-5% → newDistance=5% → 100 * 0.95 = 95 (tighter)
3879
- newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
3947
+ newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
3880
3948
  }
3881
3949
  else {
3882
3950
  // SHORT: SL is above entry (or below entry if in profit zone)
3883
3951
  // Formula: entry * (1 + newDistance%)
3884
3952
  // Example: entry=100, originalSL=110 (10%), shift=-5% → newDistance=5% → 100 * 1.05 = 105 (tighter)
3885
- newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
3953
+ newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
3886
3954
  }
3887
3955
  const currentTrailingSL = signal._trailingPriceStopLoss;
3888
3956
  const isFirstCall = currentTrailingSL === undefined;
@@ -3945,9 +4013,10 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3945
4013
  }
3946
4014
  };
3947
4015
  const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
4016
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
3948
4017
  // CRITICAL: Always calculate from ORIGINAL TP, not from current trailing TP
3949
4018
  // This prevents error accumulation on repeated calls
3950
- const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
4019
+ const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
3951
4020
  // Calculate new take-profit distance percentage by adding shift to ORIGINAL distance
3952
4021
  // Negative percentShift: reduces distance % (brings TP closer to entry)
3953
4022
  // Positive percentShift: increases distance % (moves TP further from entry)
@@ -3958,13 +4027,13 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
3958
4027
  // LONG: TP is above entry
3959
4028
  // Formula: entry * (1 + newDistance%)
3960
4029
  // Example: entry=100, originalTP=110 (10%), shift=-3% → newDistance=7% → 100 * 1.07 = 107 (closer)
3961
- newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
4030
+ newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
3962
4031
  }
3963
4032
  else {
3964
4033
  // SHORT: TP is below entry
3965
4034
  // Formula: entry * (1 - newDistance%)
3966
4035
  // Example: entry=100, originalTP=90 (10%), shift=-3% → newDistance=7% → 100 * 0.93 = 93 (closer)
3967
- newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
4036
+ newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
3968
4037
  }
3969
4038
  const currentTrailingTP = signal._trailingPriceTakeProfit;
3970
4039
  const isFirstCall = currentTrailingTP === undefined;
@@ -4025,6 +4094,7 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
4025
4094
  }
4026
4095
  };
4027
4096
  const BREAKEVEN_FN = (self, signal, currentPrice) => {
4097
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
4028
4098
  // Calculate breakeven threshold based on slippage and fees
4029
4099
  // Need to cover: entry slippage + entry fee + exit slippage + exit fee
4030
4100
  // Total: (slippage + fee) * 2 transactions
@@ -4032,10 +4102,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4032
4102
  // Check if trailing stop is already set
4033
4103
  if (signal._trailingPriceStopLoss !== undefined) {
4034
4104
  const trailingStopLoss = signal._trailingPriceStopLoss;
4035
- const breakevenPrice = signal.priceOpen;
4105
+ const breakevenPrice = effectivePriceOpen;
4036
4106
  if (signal.position === "long") {
4037
4107
  // LONG: trailing SL is positive if it's above entry (in profit zone)
4038
- const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
4108
+ const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
4039
4109
  if (isPositiveTrailing) {
4040
4110
  // Trailing stop is already protecting profit - consider breakeven achieved
4041
4111
  self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
@@ -4051,7 +4121,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4051
4121
  else {
4052
4122
  // Trailing stop is negative (below entry)
4053
4123
  // Check if we can upgrade it to breakeven
4054
- const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4124
+ const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
4055
4125
  const isThresholdReached = currentPrice >= thresholdPrice;
4056
4126
  if (isThresholdReached && breakevenPrice > trailingStopLoss) {
4057
4127
  // Check for price intrusion before setting new SL
@@ -4100,7 +4170,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4100
4170
  }
4101
4171
  else {
4102
4172
  // SHORT: trailing SL is positive if it's below entry (in profit zone)
4103
- const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
4173
+ const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
4104
4174
  if (isPositiveTrailing) {
4105
4175
  // Trailing stop is already protecting profit - consider breakeven achieved
4106
4176
  self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
@@ -4116,7 +4186,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4116
4186
  else {
4117
4187
  // Trailing stop is negative (above entry)
4118
4188
  // Check if we can upgrade it to breakeven
4119
- const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4189
+ const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
4120
4190
  const isThresholdReached = currentPrice <= thresholdPrice;
4121
4191
  if (isThresholdReached && breakevenPrice < trailingStopLoss) {
4122
4192
  // Check for price intrusion before setting new SL
@@ -4166,21 +4236,21 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4166
4236
  }
4167
4237
  // No trailing stop set - proceed with normal breakeven logic
4168
4238
  const currentStopLoss = signal.priceStopLoss;
4169
- const breakevenPrice = signal.priceOpen;
4239
+ const breakevenPrice = effectivePriceOpen;
4170
4240
  // Calculate threshold price
4171
4241
  let thresholdPrice;
4172
4242
  let isThresholdReached;
4173
4243
  let canMoveToBreakeven;
4174
4244
  if (signal.position === "long") {
4175
4245
  // LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
4176
- thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4246
+ thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
4177
4247
  isThresholdReached = currentPrice >= thresholdPrice;
4178
4248
  // Can move to breakeven only if threshold reached and SL is below entry
4179
4249
  canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
4180
4250
  }
4181
4251
  else {
4182
4252
  // SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
4183
- thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4253
+ thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
4184
4254
  isThresholdReached = currentPrice <= thresholdPrice;
4185
4255
  // Can move to breakeven only if threshold reached and SL is above entry
4186
4256
  canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
@@ -4240,8 +4310,51 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4240
4310
  thresholdPrice,
4241
4311
  breakevenThresholdPercent,
4242
4312
  profitDistancePercent: signal.position === "long"
4243
- ? ((currentPrice - signal.priceOpen) / signal.priceOpen * 100)
4244
- : ((signal.priceOpen - currentPrice) / signal.priceOpen * 100),
4313
+ ? ((currentPrice - effectivePriceOpen) / effectivePriceOpen * 100)
4314
+ : ((effectivePriceOpen - currentPrice) / effectivePriceOpen * 100),
4315
+ });
4316
+ return true;
4317
+ };
4318
+ const AVERAGE_BUY_FN = (self, signal, currentPrice) => {
4319
+ // Ensure _entry is initialized (handles signals loaded from disk without _entry)
4320
+ if (!signal._entry || signal._entry.length === 0) {
4321
+ signal._entry = [{ price: signal.priceOpen }];
4322
+ }
4323
+ const lastEntry = signal._entry[signal._entry.length - 1];
4324
+ if (signal.position === "long") {
4325
+ // LONG: averaging down = currentPrice must be strictly lower than last entry
4326
+ if (currentPrice >= lastEntry.price) {
4327
+ self.params.logger.debug("AVERAGE_BUY_FN: rejected — currentPrice >= last entry (LONG)", {
4328
+ signalId: signal.id,
4329
+ position: signal.position,
4330
+ currentPrice,
4331
+ lastEntryPrice: lastEntry.price,
4332
+ reason: "must average down for LONG",
4333
+ });
4334
+ return false;
4335
+ }
4336
+ }
4337
+ else {
4338
+ // SHORT: averaging down = currentPrice must be strictly higher than last entry
4339
+ if (currentPrice <= lastEntry.price) {
4340
+ self.params.logger.debug("AVERAGE_BUY_FN: rejected — currentPrice <= last entry (SHORT)", {
4341
+ signalId: signal.id,
4342
+ position: signal.position,
4343
+ currentPrice,
4344
+ lastEntryPrice: lastEntry.price,
4345
+ reason: "must average down for SHORT",
4346
+ });
4347
+ return false;
4348
+ }
4349
+ }
4350
+ signal._entry.push({ price: currentPrice });
4351
+ self.params.logger.info("AVERAGE_BUY_FN executed", {
4352
+ signalId: signal.id,
4353
+ position: signal.position,
4354
+ originalPriceOpen: signal.priceOpen,
4355
+ newEntryPrice: currentPrice,
4356
+ newEffectivePrice: getEffectivePriceOpen(signal),
4357
+ totalEntries: signal._entry.length,
4245
4358
  });
4246
4359
  return true;
4247
4360
  };
@@ -4970,9 +5083,10 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4970
5083
  await CALL_ACTIVE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentTime, self.params.execution.context.backtest);
4971
5084
  // Calculate percentage of path to TP/SL for partial fill/loss callbacks
4972
5085
  {
5086
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
4973
5087
  if (signal.position === "long") {
4974
5088
  // For long: calculate progress towards TP or SL
4975
- const currentDistance = currentPrice - signal.priceOpen;
5089
+ const currentDistance = currentPrice - effectivePriceOpen;
4976
5090
  if (currentDistance > 0) {
4977
5091
  // Check if breakeven should be triggered
4978
5092
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
@@ -4980,7 +5094,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4980
5094
  if (currentDistance > 0) {
4981
5095
  // Moving towards TP (use trailing TP if set)
4982
5096
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
4983
- const tpDistance = effectiveTakeProfit - signal.priceOpen;
5097
+ const tpDistance = effectiveTakeProfit - effectivePriceOpen;
4984
5098
  const progressPercent = (currentDistance / tpDistance) * 100;
4985
5099
  percentTp = Math.min(progressPercent, 100);
4986
5100
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
@@ -4988,7 +5102,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4988
5102
  else if (currentDistance < 0) {
4989
5103
  // Moving towards SL (use trailing SL if set)
4990
5104
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
4991
- const slDistance = signal.priceOpen - effectiveStopLoss;
5105
+ const slDistance = effectivePriceOpen - effectiveStopLoss;
4992
5106
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
4993
5107
  percentSl = Math.min(progressPercent, 100);
4994
5108
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
@@ -4996,7 +5110,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4996
5110
  }
4997
5111
  else if (signal.position === "short") {
4998
5112
  // For short: calculate progress towards TP or SL
4999
- const currentDistance = signal.priceOpen - currentPrice;
5113
+ const currentDistance = effectivePriceOpen - currentPrice;
5000
5114
  if (currentDistance > 0) {
5001
5115
  // Check if breakeven should be triggered
5002
5116
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
@@ -5004,7 +5118,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5004
5118
  if (currentDistance > 0) {
5005
5119
  // Moving towards TP (use trailing TP if set)
5006
5120
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5007
- const tpDistance = signal.priceOpen - effectiveTakeProfit;
5121
+ const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5008
5122
  const progressPercent = (currentDistance / tpDistance) * 100;
5009
5123
  percentTp = Math.min(progressPercent, 100);
5010
5124
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
@@ -5012,7 +5126,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5012
5126
  if (currentDistance < 0) {
5013
5127
  // Moving towards SL (use trailing SL if set)
5014
5128
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5015
- const slDistance = effectiveStopLoss - signal.priceOpen;
5129
+ const slDistance = effectiveStopLoss - effectivePriceOpen;
5016
5130
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5017
5131
  percentSl = Math.min(progressPercent, 100);
5018
5132
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
@@ -5232,8 +5346,10 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5232
5346
  priceStopLoss: publicSignalForCommit.priceStopLoss,
5233
5347
  originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5234
5348
  originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5349
+ originalPriceOpen: publicSignalForCommit.originalPriceOpen,
5235
5350
  scheduledAt: publicSignalForCommit.scheduledAt,
5236
5351
  pendingAt: publicSignalForCommit.pendingAt,
5352
+ totalEntries: publicSignalForCommit.totalEntries,
5237
5353
  });
5238
5354
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
5239
5355
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -5380,9 +5496,10 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5380
5496
  // Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
5381
5497
  // Calculate percentage of path to TP/SL
5382
5498
  {
5499
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
5383
5500
  if (signal.position === "long") {
5384
5501
  // For long: calculate progress towards TP or SL
5385
- const currentDistance = averagePrice - signal.priceOpen;
5502
+ const currentDistance = averagePrice - effectivePriceOpen;
5386
5503
  if (currentDistance > 0) {
5387
5504
  // Check if breakeven should be triggered
5388
5505
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
@@ -5390,21 +5507,21 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5390
5507
  if (currentDistance > 0) {
5391
5508
  // Moving towards TP (use trailing TP if set)
5392
5509
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5393
- const tpDistance = effectiveTakeProfit - signal.priceOpen;
5510
+ const tpDistance = effectiveTakeProfit - effectivePriceOpen;
5394
5511
  const progressPercent = (currentDistance / tpDistance) * 100;
5395
5512
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5396
5513
  }
5397
5514
  else if (currentDistance < 0) {
5398
5515
  // Moving towards SL (use trailing SL if set)
5399
5516
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5400
- const slDistance = signal.priceOpen - effectiveStopLoss;
5517
+ const slDistance = effectivePriceOpen - effectiveStopLoss;
5401
5518
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5402
5519
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5403
5520
  }
5404
5521
  }
5405
5522
  else if (signal.position === "short") {
5406
5523
  // For short: calculate progress towards TP or SL
5407
- const currentDistance = signal.priceOpen - averagePrice;
5524
+ const currentDistance = effectivePriceOpen - averagePrice;
5408
5525
  if (currentDistance > 0) {
5409
5526
  // Check if breakeven should be triggered
5410
5527
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
@@ -5412,14 +5529,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5412
5529
  if (currentDistance > 0) {
5413
5530
  // Moving towards TP (use trailing TP if set)
5414
5531
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5415
- const tpDistance = signal.priceOpen - effectiveTakeProfit;
5532
+ const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5416
5533
  const progressPercent = (currentDistance / tpDistance) * 100;
5417
5534
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5418
5535
  }
5419
5536
  if (currentDistance < 0) {
5420
5537
  // Moving towards SL (use trailing SL if set)
5421
5538
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5422
- const slDistance = effectiveStopLoss - signal.priceOpen;
5539
+ const slDistance = effectiveStopLoss - effectivePriceOpen;
5423
5540
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5424
5541
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5425
5542
  }
@@ -5617,6 +5734,7 @@ class ClientStrategy {
5617
5734
  return false;
5618
5735
  }
5619
5736
  const signal = this._pendingSignal;
5737
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
5620
5738
  // Calculate breakeven threshold based on slippage and fees
5621
5739
  // Need to cover: entry slippage + entry fee + exit slippage + exit fee
5622
5740
  // Total: (slippage + fee) * 2 transactions
@@ -5626,52 +5744,52 @@ class ClientStrategy {
5626
5744
  const trailingStopLoss = signal._trailingPriceStopLoss;
5627
5745
  if (signal.position === "long") {
5628
5746
  // LONG: trailing SL is positive if it's above entry (in profit zone)
5629
- const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
5747
+ const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
5630
5748
  if (isPositiveTrailing) {
5631
5749
  // Trailing stop is already protecting profit - breakeven achieved
5632
5750
  return true;
5633
5751
  }
5634
5752
  // Trailing stop is negative (below entry)
5635
5753
  // Check if we can upgrade it to breakeven
5636
- const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
5754
+ const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
5637
5755
  const isThresholdReached = currentPrice >= thresholdPrice;
5638
- const breakevenPrice = signal.priceOpen;
5756
+ const breakevenPrice = effectivePriceOpen;
5639
5757
  // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
5640
5758
  return isThresholdReached && breakevenPrice > trailingStopLoss;
5641
5759
  }
5642
5760
  else {
5643
5761
  // SHORT: trailing SL is positive if it's below entry (in profit zone)
5644
- const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
5762
+ const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
5645
5763
  if (isPositiveTrailing) {
5646
5764
  // Trailing stop is already protecting profit - breakeven achieved
5647
5765
  return true;
5648
5766
  }
5649
5767
  // Trailing stop is negative (above entry)
5650
5768
  // Check if we can upgrade it to breakeven
5651
- const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
5769
+ const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
5652
5770
  const isThresholdReached = currentPrice <= thresholdPrice;
5653
- const breakevenPrice = signal.priceOpen;
5771
+ const breakevenPrice = effectivePriceOpen;
5654
5772
  // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
5655
5773
  return isThresholdReached && breakevenPrice < trailingStopLoss;
5656
5774
  }
5657
5775
  }
5658
5776
  // No trailing stop set - proceed with normal breakeven logic
5659
5777
  const currentStopLoss = signal.priceStopLoss;
5660
- const breakevenPrice = signal.priceOpen;
5778
+ const breakevenPrice = effectivePriceOpen;
5661
5779
  // Calculate threshold price
5662
5780
  let thresholdPrice;
5663
5781
  let isThresholdReached;
5664
5782
  let canMoveToBreakeven;
5665
5783
  if (signal.position === "long") {
5666
5784
  // LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
5667
- thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
5785
+ thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
5668
5786
  isThresholdReached = currentPrice >= thresholdPrice;
5669
5787
  // Can move to breakeven only if threshold reached and SL is below entry
5670
5788
  canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
5671
5789
  }
5672
5790
  else {
5673
5791
  // SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
5674
- thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
5792
+ thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
5675
5793
  isThresholdReached = currentPrice <= thresholdPrice;
5676
5794
  // Can move to breakeven only if threshold reached and SL is above entry
5677
5795
  canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
@@ -5754,6 +5872,8 @@ class ClientStrategy {
5754
5872
  backtest: this.params.execution.context.backtest,
5755
5873
  cancelId: cancelledSignal.cancelId,
5756
5874
  timestamp: currentTime,
5875
+ totalEntries: cancelledSignal._entry?.length ?? 1,
5876
+ originalPriceOpen: cancelledSignal.priceOpen,
5757
5877
  });
5758
5878
  // Call onCancel callback
5759
5879
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -5794,6 +5914,8 @@ class ClientStrategy {
5794
5914
  backtest: this.params.execution.context.backtest,
5795
5915
  closeId: closedSignal.closeId,
5796
5916
  timestamp: currentTime,
5917
+ totalEntries: closedSignal._entry?.length ?? 1,
5918
+ originalPriceOpen: closedSignal.priceOpen,
5797
5919
  });
5798
5920
  // Call onClose callback
5799
5921
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -5874,8 +5996,10 @@ class ClientStrategy {
5874
5996
  priceStopLoss: publicSignalForCommit.priceStopLoss,
5875
5997
  originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5876
5998
  originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5999
+ originalPriceOpen: publicSignalForCommit.originalPriceOpen,
5877
6000
  scheduledAt: publicSignalForCommit.scheduledAt,
5878
6001
  pendingAt: publicSignalForCommit.pendingAt,
6002
+ totalEntries: publicSignalForCommit.totalEntries,
5879
6003
  });
5880
6004
  // Call onOpen callback
5881
6005
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -6006,6 +6130,8 @@ class ClientStrategy {
6006
6130
  backtest: true,
6007
6131
  cancelId: cancelledSignal.cancelId,
6008
6132
  timestamp: closeTimestamp,
6133
+ totalEntries: cancelledSignal._entry?.length ?? 1,
6134
+ originalPriceOpen: cancelledSignal.priceOpen,
6009
6135
  });
6010
6136
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
6011
6137
  const cancelledResult = {
@@ -6043,6 +6169,8 @@ class ClientStrategy {
6043
6169
  backtest: true,
6044
6170
  closeId: closedSignal.closeId,
6045
6171
  timestamp: closeTimestamp,
6172
+ totalEntries: closedSignal._entry?.length ?? 1,
6173
+ originalPriceOpen: closedSignal.priceOpen,
6046
6174
  });
6047
6175
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
6048
6176
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -6430,16 +6558,19 @@ class ClientStrategy {
6430
6558
  throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
6431
6559
  }
6432
6560
  // Validation: currentPrice must be moving toward TP (profit direction)
6433
- if (this._pendingSignal.position === "long") {
6434
- // For LONG: currentPrice must be higher than priceOpen (moving toward TP)
6435
- if (currentPrice <= this._pendingSignal.priceOpen) {
6436
- throw new Error(`ClientStrategy partialProfit: For LONG position, currentPrice (${currentPrice}) must be > priceOpen (${this._pendingSignal.priceOpen})`);
6561
+ {
6562
+ const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
6563
+ if (this._pendingSignal.position === "long") {
6564
+ // For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
6565
+ if (currentPrice <= effectivePriceOpen) {
6566
+ throw new Error(`ClientStrategy partialProfit: For LONG position, currentPrice (${currentPrice}) must be > effectivePriceOpen (${effectivePriceOpen})`);
6567
+ }
6437
6568
  }
6438
- }
6439
- else {
6440
- // For SHORT: currentPrice must be lower than priceOpen (moving toward TP)
6441
- if (currentPrice >= this._pendingSignal.priceOpen) {
6442
- throw new Error(`ClientStrategy partialProfit: For SHORT position, currentPrice (${currentPrice}) must be < priceOpen (${this._pendingSignal.priceOpen})`);
6569
+ else {
6570
+ // For SHORT: currentPrice must be lower than effectivePriceOpen (moving toward TP)
6571
+ if (currentPrice >= effectivePriceOpen) {
6572
+ throw new Error(`ClientStrategy partialProfit: For SHORT position, currentPrice (${currentPrice}) must be < effectivePriceOpen (${effectivePriceOpen})`);
6573
+ }
6443
6574
  }
6444
6575
  }
6445
6576
  // Check if currentPrice already crossed take profit level
@@ -6562,16 +6693,19 @@ class ClientStrategy {
6562
6693
  throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
6563
6694
  }
6564
6695
  // Validation: currentPrice must be moving toward SL (loss direction)
6565
- if (this._pendingSignal.position === "long") {
6566
- // For LONG: currentPrice must be lower than priceOpen (moving toward SL)
6567
- if (currentPrice >= this._pendingSignal.priceOpen) {
6568
- throw new Error(`ClientStrategy partialLoss: For LONG position, currentPrice (${currentPrice}) must be < priceOpen (${this._pendingSignal.priceOpen})`);
6696
+ {
6697
+ const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
6698
+ if (this._pendingSignal.position === "long") {
6699
+ // For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
6700
+ if (currentPrice >= effectivePriceOpen) {
6701
+ throw new Error(`ClientStrategy partialLoss: For LONG position, currentPrice (${currentPrice}) must be < effectivePriceOpen (${effectivePriceOpen})`);
6702
+ }
6569
6703
  }
6570
- }
6571
- else {
6572
- // For SHORT: currentPrice must be higher than priceOpen (moving toward SL)
6573
- if (currentPrice <= this._pendingSignal.priceOpen) {
6574
- throw new Error(`ClientStrategy partialLoss: For SHORT position, currentPrice (${currentPrice}) must be > priceOpen (${this._pendingSignal.priceOpen})`);
6704
+ else {
6705
+ // For SHORT: currentPrice must be higher than effectivePriceOpen (moving toward SL)
6706
+ if (currentPrice <= effectivePriceOpen) {
6707
+ throw new Error(`ClientStrategy partialLoss: For SHORT position, currentPrice (${currentPrice}) must be > effectivePriceOpen (${effectivePriceOpen})`);
6708
+ }
6575
6709
  }
6576
6710
  }
6577
6711
  // Check if currentPrice already crossed stop loss level
@@ -6692,7 +6826,7 @@ class ClientStrategy {
6692
6826
  }
6693
6827
  // Check for conflict with existing trailing take profit
6694
6828
  const signal = this._pendingSignal;
6695
- const breakevenPrice = signal.priceOpen;
6829
+ const breakevenPrice = getEffectivePriceOpen(signal);
6696
6830
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6697
6831
  if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit) {
6698
6832
  // LONG: Breakeven SL would be at or above current TP - invalid configuration
@@ -6844,14 +6978,15 @@ class ClientStrategy {
6844
6978
  }
6845
6979
  // Calculate what the new stop loss would be
6846
6980
  const signal = this._pendingSignal;
6847
- const slDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
6981
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
6982
+ const slDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
6848
6983
  const newSlDistancePercent = slDistancePercent + percentShift;
6849
6984
  let newStopLoss;
6850
6985
  if (signal.position === "long") {
6851
- newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
6986
+ newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
6852
6987
  }
6853
6988
  else {
6854
- newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
6989
+ newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
6855
6990
  }
6856
6991
  // Check for price intrusion before executing trailing logic
6857
6992
  if (signal.position === "long" && currentPrice < newStopLoss) {
@@ -7017,14 +7152,15 @@ class ClientStrategy {
7017
7152
  }
7018
7153
  // Calculate what the new take profit would be
7019
7154
  const signal = this._pendingSignal;
7020
- const tpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
7155
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
7156
+ const tpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
7021
7157
  const newTpDistancePercent = tpDistancePercent + percentShift;
7022
7158
  let newTakeProfit;
7023
7159
  if (signal.position === "long") {
7024
- newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
7160
+ newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
7025
7161
  }
7026
7162
  else {
7027
- newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
7163
+ newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
7028
7164
  }
7029
7165
  // Check for price intrusion before executing trailing logic
7030
7166
  if (signal.position === "long" && currentPrice > newTakeProfit) {
@@ -7106,6 +7242,70 @@ class ClientStrategy {
7106
7242
  });
7107
7243
  return true;
7108
7244
  }
7245
+ /**
7246
+ * Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
7247
+ *
7248
+ * Appends currentPrice to the _entry array. The effective entry price used in all
7249
+ * distance and PNL calculations becomes the simple arithmetic mean of all _entry prices.
7250
+ * Original priceOpen is preserved unchanged for identity/audit purposes.
7251
+ *
7252
+ * Rejection rules (returns false without throwing):
7253
+ * - LONG: currentPrice >= last entry price (must average down, not up or equal)
7254
+ * - SHORT: currentPrice <= last entry price (must average down, not up or equal)
7255
+ *
7256
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
7257
+ * @param currentPrice - New entry price to add to the averaging history
7258
+ * @param backtest - Whether running in backtest mode
7259
+ * @returns Promise<boolean> - true if entry added, false if rejected by direction check
7260
+ */
7261
+ async averageBuy(symbol, currentPrice, backtest) {
7262
+ this.params.logger.debug("ClientStrategy averageBuy", {
7263
+ symbol,
7264
+ currentPrice,
7265
+ hasPendingSignal: this._pendingSignal !== null,
7266
+ });
7267
+ // Validation: must have pending signal
7268
+ if (!this._pendingSignal) {
7269
+ throw new Error(`ClientStrategy averageBuy: No pending signal exists for symbol=${symbol}`);
7270
+ }
7271
+ // Validation: currentPrice must be valid
7272
+ if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
7273
+ throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
7274
+ }
7275
+ // Reject if any partial closes have already been executed
7276
+ if (this._pendingSignal._partial && this._pendingSignal._partial.length > 0) {
7277
+ this.params.logger.debug("ClientStrategy averageBuy: rejected — partial closes already executed", {
7278
+ symbol,
7279
+ partialCount: this._pendingSignal._partial.length,
7280
+ });
7281
+ return false;
7282
+ }
7283
+ // Execute averaging logic
7284
+ const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice);
7285
+ if (!result) {
7286
+ return false;
7287
+ }
7288
+ // Persist updated signal state
7289
+ this.params.logger.debug("ClientStrategy setPendingSignal (inline)", {
7290
+ pendingSignal: this._pendingSignal,
7291
+ });
7292
+ // Call onWrite callback for testing persist storage
7293
+ if (this.params.callbacks?.onWrite) {
7294
+ this.params.callbacks.onWrite(this.params.execution.context.symbol, TO_PUBLIC_SIGNAL(this._pendingSignal), backtest);
7295
+ }
7296
+ if (!backtest) {
7297
+ await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
7298
+ }
7299
+ // Queue commit event for processing in tick()/backtest() with proper timestamp
7300
+ this._commitQueue.push({
7301
+ action: "average-buy",
7302
+ symbol,
7303
+ backtest,
7304
+ currentPrice,
7305
+ totalEntries: this._pendingSignal._entry?.length ?? 1,
7306
+ });
7307
+ return true;
7308
+ }
7109
7309
  }
7110
7310
 
7111
7311
  const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
@@ -8230,6 +8430,27 @@ class StrategyConnectionService {
8230
8430
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8231
8431
  return await strategy.activateScheduled(symbol, backtest, activateId);
8232
8432
  };
8433
+ /**
8434
+ * Adds a new DCA entry to the active pending signal.
8435
+ *
8436
+ * Delegates to ClientStrategy.averageBuy() with current execution context.
8437
+ *
8438
+ * @param backtest - Whether running in backtest mode
8439
+ * @param symbol - Trading pair symbol
8440
+ * @param currentPrice - New entry price to add to the averaging history
8441
+ * @param context - Execution context with strategyName, exchangeName, frameName
8442
+ * @returns Promise<boolean> - true if entry added, false if rejected
8443
+ */
8444
+ this.averageBuy = async (backtest, symbol, currentPrice, context) => {
8445
+ this.loggerService.log("strategyConnectionService averageBuy", {
8446
+ symbol,
8447
+ context,
8448
+ currentPrice,
8449
+ backtest,
8450
+ });
8451
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8452
+ return await strategy.averageBuy(symbol, currentPrice, backtest);
8453
+ };
8233
8454
  }
8234
8455
  }
8235
8456
 
@@ -8703,11 +8924,13 @@ const TO_RISK_SIGNAL = (signal, currentPrice) => {
8703
8924
  : 0;
8704
8925
  return {
8705
8926
  ...structuredClone(signal),
8927
+ totalEntries: 1,
8706
8928
  priceOpen: signal.priceOpen ?? currentPrice,
8707
8929
  priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
8708
8930
  priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
8709
8931
  originalPriceStopLoss: signal.priceStopLoss,
8710
8932
  originalPriceTakeProfit: signal.priceTakeProfit,
8933
+ originalPriceOpen: signal.priceOpen ?? currentPrice,
8711
8934
  partialExecuted,
8712
8935
  };
8713
8936
  };
@@ -11575,6 +11798,28 @@ class StrategyCoreService {
11575
11798
  await this.validate(context);
11576
11799
  return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
11577
11800
  };
11801
+ /**
11802
+ * Adds a new DCA entry to the active pending signal.
11803
+ *
11804
+ * Validates strategy existence and delegates to connection service
11805
+ * to add a new averaging entry to the position.
11806
+ *
11807
+ * @param backtest - Whether running in backtest mode
11808
+ * @param symbol - Trading pair symbol
11809
+ * @param currentPrice - New entry price to add to the averaging history
11810
+ * @param context - Execution context with strategyName, exchangeName, frameName
11811
+ * @returns Promise<boolean> - true if entry added, false if rejected
11812
+ */
11813
+ this.averageBuy = async (backtest, symbol, currentPrice, context) => {
11814
+ this.loggerService.log("strategyCoreService averageBuy", {
11815
+ symbol,
11816
+ currentPrice,
11817
+ context,
11818
+ backtest,
11819
+ });
11820
+ await this.validate(context);
11821
+ return await this.strategyConnectionService.averageBuy(backtest, symbol, currentPrice, context);
11822
+ };
11578
11823
  }
11579
11824
  }
11580
11825
 
@@ -14122,6 +14367,18 @@ const backtest_columns = [
14122
14367
  format: (data) => `${data.signal.originalPriceStopLoss.toFixed(8)} USD`,
14123
14368
  isVisible: () => true,
14124
14369
  },
14370
+ {
14371
+ key: "originalPriceOpen",
14372
+ label: "Original Entry",
14373
+ format: (data) => `${data.signal.originalPriceOpen.toFixed(8)} USD`,
14374
+ isVisible: () => true,
14375
+ },
14376
+ {
14377
+ key: "totalEntries",
14378
+ label: "DCA Entries",
14379
+ format: (data) => String(data.signal.totalEntries),
14380
+ isVisible: () => true,
14381
+ },
14125
14382
  {
14126
14383
  key: "pnl",
14127
14384
  label: "PNL (net)",
@@ -14409,6 +14666,20 @@ const live_columns = [
14409
14666
  : "N/A",
14410
14667
  isVisible: () => true,
14411
14668
  },
14669
+ {
14670
+ key: "originalPriceOpen",
14671
+ label: "Original Entry",
14672
+ format: (data) => data.originalPriceOpen !== undefined
14673
+ ? `${data.originalPriceOpen.toFixed(8)} USD`
14674
+ : "N/A",
14675
+ isVisible: () => true,
14676
+ },
14677
+ {
14678
+ key: "totalEntries",
14679
+ label: "DCA Entries",
14680
+ format: (data) => data.totalEntries !== undefined ? String(data.totalEntries) : "N/A",
14681
+ isVisible: () => true,
14682
+ },
14412
14683
  {
14413
14684
  key: "partialExecuted",
14414
14685
  label: "Partial Executed %",
@@ -14573,6 +14844,18 @@ const partial_columns = [
14573
14844
  format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
14574
14845
  isVisible: () => true,
14575
14846
  },
14847
+ {
14848
+ key: "originalPriceOpen",
14849
+ label: "Original Entry",
14850
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
14851
+ isVisible: () => true,
14852
+ },
14853
+ {
14854
+ key: "totalEntries",
14855
+ label: "DCA Entries",
14856
+ format: (data) => (data.totalEntries !== undefined ? String(data.totalEntries) : "N/A"),
14857
+ isVisible: () => true,
14858
+ },
14576
14859
  {
14577
14860
  key: "partialExecuted",
14578
14861
  label: "Partial Executed %",
@@ -14707,6 +14990,18 @@ const breakeven_columns = [
14707
14990
  format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
14708
14991
  isVisible: () => true,
14709
14992
  },
14993
+ {
14994
+ key: "originalPriceOpen",
14995
+ label: "Original Entry",
14996
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
14997
+ isVisible: () => true,
14998
+ },
14999
+ {
15000
+ key: "totalEntries",
15001
+ label: "DCA Entries",
15002
+ format: (data) => (data.totalEntries !== undefined ? String(data.totalEntries) : "N/A"),
15003
+ isVisible: () => true,
15004
+ },
14710
15005
  {
14711
15006
  key: "partialExecuted",
14712
15007
  label: "Partial Executed %",
@@ -14977,6 +15272,22 @@ const risk_columns = [
14977
15272
  : "N/A",
14978
15273
  isVisible: () => true,
14979
15274
  },
15275
+ {
15276
+ key: "originalPriceOpen",
15277
+ label: "Original Entry",
15278
+ format: (data) => data.currentSignal.originalPriceOpen !== undefined
15279
+ ? `${data.currentSignal.originalPriceOpen.toFixed(8)} USD`
15280
+ : "N/A",
15281
+ isVisible: () => true,
15282
+ },
15283
+ {
15284
+ key: "totalEntries",
15285
+ label: "DCA Entries",
15286
+ format: (data) => data.currentSignal.totalEntries !== undefined
15287
+ ? String(data.currentSignal.totalEntries)
15288
+ : "N/A",
15289
+ isVisible: () => true,
15290
+ },
14980
15291
  {
14981
15292
  key: "partialExecuted",
14982
15293
  label: "Partial Executed %",
@@ -15151,6 +15462,20 @@ const schedule_columns = [
15151
15462
  : "N/A",
15152
15463
  isVisible: () => true,
15153
15464
  },
15465
+ {
15466
+ key: "originalPriceOpen",
15467
+ label: "Original Entry",
15468
+ format: (data) => data.originalPriceOpen !== undefined
15469
+ ? `${data.originalPriceOpen.toFixed(8)} USD`
15470
+ : "N/A",
15471
+ isVisible: () => true,
15472
+ },
15473
+ {
15474
+ key: "totalEntries",
15475
+ label: "DCA Entries",
15476
+ format: (data) => data.totalEntries !== undefined ? String(data.totalEntries) : "N/A",
15477
+ isVisible: () => true,
15478
+ },
15154
15479
  {
15155
15480
  key: "partialExecuted",
15156
15481
  label: "Partial Executed %",
@@ -17240,6 +17565,8 @@ let ReportStorage$5 = class ReportStorage {
17240
17565
  priceStopLoss: data.signal.priceStopLoss,
17241
17566
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17242
17567
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17568
+ totalEntries: data.signal.totalEntries,
17569
+ originalPriceOpen: data.signal.originalPriceOpen,
17243
17570
  partialExecuted: data.signal.partialExecuted,
17244
17571
  scheduledAt: data.signal.scheduledAt,
17245
17572
  });
@@ -17269,6 +17596,8 @@ let ReportStorage$5 = class ReportStorage {
17269
17596
  priceStopLoss: data.signal.priceStopLoss,
17270
17597
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17271
17598
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17599
+ totalEntries: data.signal.totalEntries,
17600
+ originalPriceOpen: data.signal.originalPriceOpen,
17272
17601
  partialExecuted: data.signal.partialExecuted,
17273
17602
  duration: durationMin,
17274
17603
  pendingAt: data.signal.pendingAt,
@@ -17301,6 +17630,8 @@ let ReportStorage$5 = class ReportStorage {
17301
17630
  priceStopLoss: data.signal.priceStopLoss,
17302
17631
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17303
17632
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17633
+ totalEntries: data.signal.totalEntries,
17634
+ originalPriceOpen: data.signal.originalPriceOpen,
17304
17635
  partialExecuted: data.signal.partialExecuted,
17305
17636
  closeTimestamp: data.closeTimestamp,
17306
17637
  duration: durationMin,
@@ -20446,6 +20777,8 @@ let ReportStorage$3 = class ReportStorage {
20446
20777
  priceStopLoss: data.priceStopLoss,
20447
20778
  originalPriceTakeProfit: data.originalPriceTakeProfit,
20448
20779
  originalPriceStopLoss: data.originalPriceStopLoss,
20780
+ totalEntries: data.totalEntries,
20781
+ originalPriceOpen: data.originalPriceOpen,
20449
20782
  partialExecuted: data.partialExecuted,
20450
20783
  note: data.note,
20451
20784
  pendingAt: data.pendingAt,
@@ -20480,6 +20813,8 @@ let ReportStorage$3 = class ReportStorage {
20480
20813
  priceStopLoss: data.priceStopLoss,
20481
20814
  originalPriceTakeProfit: data.originalPriceTakeProfit,
20482
20815
  originalPriceStopLoss: data.originalPriceStopLoss,
20816
+ totalEntries: data.totalEntries,
20817
+ originalPriceOpen: data.originalPriceOpen,
20483
20818
  partialExecuted: data.partialExecuted,
20484
20819
  note: data.note,
20485
20820
  pendingAt: data.pendingAt,
@@ -21579,6 +21914,8 @@ let ReportStorage$2 = class ReportStorage {
21579
21914
  priceStopLoss: data.priceStopLoss,
21580
21915
  originalPriceTakeProfit: data.originalPriceTakeProfit,
21581
21916
  originalPriceStopLoss: data.originalPriceStopLoss,
21917
+ totalEntries: data.totalEntries,
21918
+ originalPriceOpen: data.originalPriceOpen,
21582
21919
  partialExecuted: data.partialExecuted,
21583
21920
  note: data.note,
21584
21921
  pendingAt: data.pendingAt,
@@ -23582,6 +23919,8 @@ class ScheduleReportService {
23582
23919
  priceStopLoss: data.signal?.priceStopLoss,
23583
23920
  originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
23584
23921
  originalPriceStopLoss: data.signal?.originalPriceStopLoss,
23922
+ totalEntries: data.signal?.totalEntries,
23923
+ originalPriceOpen: data.signal?.originalPriceOpen,
23585
23924
  partialExecuted: data.signal?.partialExecuted,
23586
23925
  pendingAt: data.signal?.pendingAt,
23587
23926
  minuteEstimatedTime: data.signal?.minuteEstimatedTime,
@@ -23626,6 +23965,8 @@ class ScheduleReportService {
23626
23965
  priceStopLoss: data.signal?.priceStopLoss,
23627
23966
  originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
23628
23967
  originalPriceStopLoss: data.signal?.originalPriceStopLoss,
23968
+ totalEntries: data.signal?.totalEntries,
23969
+ originalPriceOpen: data.signal?.originalPriceOpen,
23629
23970
  partialExecuted: data.signal?.partialExecuted,
23630
23971
  scheduledAt: data.signal?.scheduledAt,
23631
23972
  pendingAt: data.signal?.pendingAt,
@@ -24103,6 +24444,8 @@ class PartialReportService {
24103
24444
  priceStopLoss: data.data.priceStopLoss,
24104
24445
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24105
24446
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24447
+ totalEntries: data.data.totalEntries,
24448
+ originalPriceOpen: data.data.originalPriceOpen,
24106
24449
  partialExecuted: data.data.partialExecuted,
24107
24450
  _partial: data.data._partial,
24108
24451
  note: data.data.note,
@@ -24144,6 +24487,8 @@ class PartialReportService {
24144
24487
  priceStopLoss: data.data.priceStopLoss,
24145
24488
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24146
24489
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24490
+ totalEntries: data.data.totalEntries,
24491
+ originalPriceOpen: data.data.originalPriceOpen,
24147
24492
  partialExecuted: data.data.partialExecuted,
24148
24493
  _partial: data.data._partial,
24149
24494
  note: data.data.note,
@@ -24266,6 +24611,8 @@ class BreakevenReportService {
24266
24611
  priceStopLoss: data.data.priceStopLoss,
24267
24612
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24268
24613
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24614
+ totalEntries: data.data.totalEntries,
24615
+ originalPriceOpen: data.data.originalPriceOpen,
24269
24616
  partialExecuted: data.data.partialExecuted,
24270
24617
  _partial: data.data._partial,
24271
24618
  note: data.data.note,
@@ -24574,7 +24921,7 @@ class StrategyReportService {
24574
24921
  * @param scheduledAt - Signal creation timestamp in milliseconds
24575
24922
  * @param pendingAt - Pending timestamp in milliseconds
24576
24923
  */
24577
- this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
24924
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24578
24925
  this.loggerService.log("strategyReportService partialProfit", {
24579
24926
  symbol,
24580
24927
  percentToClose,
@@ -24606,6 +24953,8 @@ class StrategyReportService {
24606
24953
  priceStopLoss,
24607
24954
  originalPriceTakeProfit,
24608
24955
  originalPriceStopLoss,
24956
+ originalPriceOpen,
24957
+ totalEntries,
24609
24958
  scheduledAt,
24610
24959
  pendingAt,
24611
24960
  }, {
@@ -24635,7 +24984,7 @@ class StrategyReportService {
24635
24984
  * @param scheduledAt - Signal creation timestamp in milliseconds
24636
24985
  * @param pendingAt - Pending timestamp in milliseconds
24637
24986
  */
24638
- this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
24987
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24639
24988
  this.loggerService.log("strategyReportService partialLoss", {
24640
24989
  symbol,
24641
24990
  percentToClose,
@@ -24667,6 +25016,8 @@ class StrategyReportService {
24667
25016
  priceStopLoss,
24668
25017
  originalPriceTakeProfit,
24669
25018
  originalPriceStopLoss,
25019
+ originalPriceOpen,
25020
+ totalEntries,
24670
25021
  scheduledAt,
24671
25022
  pendingAt,
24672
25023
  }, {
@@ -24696,7 +25047,7 @@ class StrategyReportService {
24696
25047
  * @param scheduledAt - Signal creation timestamp in milliseconds
24697
25048
  * @param pendingAt - Pending timestamp in milliseconds
24698
25049
  */
24699
- this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25050
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24700
25051
  this.loggerService.log("strategyReportService trailingStop", {
24701
25052
  symbol,
24702
25053
  percentShift,
@@ -24728,6 +25079,8 @@ class StrategyReportService {
24728
25079
  priceStopLoss,
24729
25080
  originalPriceTakeProfit,
24730
25081
  originalPriceStopLoss,
25082
+ originalPriceOpen,
25083
+ totalEntries,
24731
25084
  scheduledAt,
24732
25085
  pendingAt,
24733
25086
  }, {
@@ -24757,7 +25110,7 @@ class StrategyReportService {
24757
25110
  * @param scheduledAt - Signal creation timestamp in milliseconds
24758
25111
  * @param pendingAt - Pending timestamp in milliseconds
24759
25112
  */
24760
- this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25113
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24761
25114
  this.loggerService.log("strategyReportService trailingTake", {
24762
25115
  symbol,
24763
25116
  percentShift,
@@ -24789,6 +25142,8 @@ class StrategyReportService {
24789
25142
  priceStopLoss,
24790
25143
  originalPriceTakeProfit,
24791
25144
  originalPriceStopLoss,
25145
+ originalPriceOpen,
25146
+ totalEntries,
24792
25147
  scheduledAt,
24793
25148
  pendingAt,
24794
25149
  }, {
@@ -24817,7 +25172,7 @@ class StrategyReportService {
24817
25172
  * @param scheduledAt - Signal creation timestamp in milliseconds
24818
25173
  * @param pendingAt - Pending timestamp in milliseconds
24819
25174
  */
24820
- this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25175
+ this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24821
25176
  this.loggerService.log("strategyReportService breakeven", {
24822
25177
  symbol,
24823
25178
  currentPrice,
@@ -24847,6 +25202,8 @@ class StrategyReportService {
24847
25202
  priceStopLoss,
24848
25203
  originalPriceTakeProfit,
24849
25204
  originalPriceStopLoss,
25205
+ originalPriceOpen,
25206
+ totalEntries,
24850
25207
  scheduledAt,
24851
25208
  pendingAt,
24852
25209
  }, {
@@ -24876,7 +25233,7 @@ class StrategyReportService {
24876
25233
  * @param pendingAt - Pending timestamp in milliseconds
24877
25234
  * @param activateId - Optional identifier for the activation reason
24878
25235
  */
24879
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
25236
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
24880
25237
  this.loggerService.log("strategyReportService activateScheduled", {
24881
25238
  symbol,
24882
25239
  currentPrice,
@@ -24908,6 +25265,8 @@ class StrategyReportService {
24908
25265
  priceStopLoss,
24909
25266
  originalPriceTakeProfit,
24910
25267
  originalPriceStopLoss,
25268
+ originalPriceOpen,
25269
+ totalEntries,
24911
25270
  scheduledAt,
24912
25271
  pendingAt,
24913
25272
  }, {
@@ -24919,6 +25278,71 @@ class StrategyReportService {
24919
25278
  walkerName: "",
24920
25279
  });
24921
25280
  };
25281
+ /**
25282
+ * Logs an average-buy (DCA) event when a new averaging entry is added to an open position.
25283
+ *
25284
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
25285
+ * @param currentPrice - Price at which the new averaging entry was executed
25286
+ * @param effectivePriceOpen - Averaged entry price after this addition
25287
+ * @param totalEntries - Total number of DCA entries after this addition
25288
+ * @param isBacktest - Whether this is a backtest or live trading event
25289
+ * @param context - Strategy context with strategyName, exchangeName, frameName
25290
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
25291
+ * @param position - Trade direction: "long" or "short"
25292
+ * @param priceOpen - Original entry price (unchanged by averaging)
25293
+ * @param priceTakeProfit - Effective take profit price
25294
+ * @param priceStopLoss - Effective stop loss price
25295
+ * @param originalPriceTakeProfit - Original take profit before trailing
25296
+ * @param originalPriceStopLoss - Original stop loss before trailing
25297
+ * @param scheduledAt - Signal creation timestamp in milliseconds
25298
+ * @param pendingAt - Pending timestamp in milliseconds
25299
+ */
25300
+ this.averageBuy = async (symbol, currentPrice, effectivePriceOpen, totalEntries, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, originalPriceOpen) => {
25301
+ this.loggerService.log("strategyReportService averageBuy", {
25302
+ symbol,
25303
+ currentPrice,
25304
+ effectivePriceOpen,
25305
+ totalEntries,
25306
+ isBacktest,
25307
+ });
25308
+ if (!this.subscribe.hasValue()) {
25309
+ return;
25310
+ }
25311
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
25312
+ exchangeName: context.exchangeName,
25313
+ strategyName: context.strategyName,
25314
+ frameName: context.frameName,
25315
+ });
25316
+ if (!pendingRow) {
25317
+ return;
25318
+ }
25319
+ const createdAt = new Date(timestamp).toISOString();
25320
+ await Report.writeData("strategy", {
25321
+ action: "average-buy",
25322
+ currentPrice,
25323
+ effectivePriceOpen,
25324
+ totalEntries,
25325
+ symbol,
25326
+ timestamp,
25327
+ createdAt,
25328
+ position,
25329
+ priceOpen,
25330
+ priceTakeProfit,
25331
+ priceStopLoss,
25332
+ originalPriceTakeProfit,
25333
+ originalPriceStopLoss,
25334
+ originalPriceOpen,
25335
+ scheduledAt,
25336
+ pendingAt,
25337
+ }, {
25338
+ signalId: pendingRow.id,
25339
+ exchangeName: context.exchangeName,
25340
+ frameName: context.frameName,
25341
+ strategyName: context.strategyName,
25342
+ symbol,
25343
+ walkerName: "",
25344
+ });
25345
+ };
24922
25346
  /**
24923
25347
  * Initializes the service for event logging.
24924
25348
  *
@@ -24949,43 +25373,50 @@ class StrategyReportService {
24949
25373
  exchangeName: event.exchangeName,
24950
25374
  frameName: event.frameName,
24951
25375
  strategyName: event.strategyName,
24952
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25376
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24953
25377
  const unPartialLoss = strategyCommitSubject
24954
25378
  .filter(({ action }) => action === "partial-loss")
24955
25379
  .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24956
25380
  exchangeName: event.exchangeName,
24957
25381
  frameName: event.frameName,
24958
25382
  strategyName: event.strategyName,
24959
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25383
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24960
25384
  const unTrailingStop = strategyCommitSubject
24961
25385
  .filter(({ action }) => action === "trailing-stop")
24962
25386
  .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24963
25387
  exchangeName: event.exchangeName,
24964
25388
  frameName: event.frameName,
24965
25389
  strategyName: event.strategyName,
24966
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25390
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24967
25391
  const unTrailingTake = strategyCommitSubject
24968
25392
  .filter(({ action }) => action === "trailing-take")
24969
25393
  .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24970
25394
  exchangeName: event.exchangeName,
24971
25395
  frameName: event.frameName,
24972
25396
  strategyName: event.strategyName,
24973
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25397
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24974
25398
  const unBreakeven = strategyCommitSubject
24975
25399
  .filter(({ action }) => action === "breakeven")
24976
25400
  .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24977
25401
  exchangeName: event.exchangeName,
24978
25402
  frameName: event.frameName,
24979
25403
  strategyName: event.strategyName,
24980
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25404
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24981
25405
  const unActivateScheduled = strategyCommitSubject
24982
25406
  .filter(({ action }) => action === "activate-scheduled")
24983
25407
  .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
24984
25408
  exchangeName: event.exchangeName,
24985
25409
  frameName: event.frameName,
24986
25410
  strategyName: event.strategyName,
24987
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
24988
- const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
25411
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
25412
+ const unAverageBuy = strategyCommitSubject
25413
+ .filter(({ action }) => action === "average-buy")
25414
+ .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
25415
+ exchangeName: event.exchangeName,
25416
+ frameName: event.frameName,
25417
+ strategyName: event.strategyName,
25418
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.originalPriceOpen));
25419
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled(), () => unAverageBuy());
24989
25420
  return () => {
24990
25421
  disposeFn();
24991
25422
  this.subscribe.clear();
@@ -25117,6 +25548,7 @@ class ReportStorage {
25117
25548
  trailingTakeCount: 0,
25118
25549
  breakevenCount: 0,
25119
25550
  activateScheduledCount: 0,
25551
+ averageBuyCount: 0,
25120
25552
  };
25121
25553
  }
25122
25554
  return {
@@ -25130,6 +25562,7 @@ class ReportStorage {
25130
25562
  trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
25131
25563
  breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
25132
25564
  activateScheduledCount: this._eventList.filter(e => e.action === "activate-scheduled").length,
25565
+ averageBuyCount: this._eventList.filter(e => e.action === "average-buy").length,
25133
25566
  };
25134
25567
  }
25135
25568
  /**
@@ -25179,6 +25612,7 @@ class ReportStorage {
25179
25612
  `- Trailing take: ${stats.trailingTakeCount}`,
25180
25613
  `- Breakeven: ${stats.breakevenCount}`,
25181
25614
  `- Activate scheduled: ${stats.activateScheduledCount}`,
25615
+ `- Average buy: ${stats.averageBuyCount}`,
25182
25616
  ].join("\n");
25183
25617
  }
25184
25618
  /**
@@ -25366,7 +25800,7 @@ class StrategyMarkdownService {
25366
25800
  * @param scheduledAt - Signal creation timestamp in milliseconds
25367
25801
  * @param pendingAt - Pending timestamp in milliseconds
25368
25802
  */
25369
- this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25803
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25370
25804
  this.loggerService.log("strategyMarkdownService partialProfit", {
25371
25805
  symbol,
25372
25806
  percentToClose,
@@ -25404,6 +25838,8 @@ class StrategyMarkdownService {
25404
25838
  priceStopLoss,
25405
25839
  originalPriceTakeProfit,
25406
25840
  originalPriceStopLoss,
25841
+ originalPriceOpen,
25842
+ totalEntries,
25407
25843
  scheduledAt,
25408
25844
  pendingAt,
25409
25845
  });
@@ -25426,7 +25862,7 @@ class StrategyMarkdownService {
25426
25862
  * @param scheduledAt - Signal creation timestamp in milliseconds
25427
25863
  * @param pendingAt - Pending timestamp in milliseconds
25428
25864
  */
25429
- this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25865
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25430
25866
  this.loggerService.log("strategyMarkdownService partialLoss", {
25431
25867
  symbol,
25432
25868
  percentToClose,
@@ -25464,6 +25900,8 @@ class StrategyMarkdownService {
25464
25900
  priceStopLoss,
25465
25901
  originalPriceTakeProfit,
25466
25902
  originalPriceStopLoss,
25903
+ originalPriceOpen,
25904
+ totalEntries,
25467
25905
  scheduledAt,
25468
25906
  pendingAt,
25469
25907
  });
@@ -25486,7 +25924,7 @@ class StrategyMarkdownService {
25486
25924
  * @param scheduledAt - Signal creation timestamp in milliseconds
25487
25925
  * @param pendingAt - Pending timestamp in milliseconds
25488
25926
  */
25489
- this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25927
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25490
25928
  this.loggerService.log("strategyMarkdownService trailingStop", {
25491
25929
  symbol,
25492
25930
  percentShift,
@@ -25524,6 +25962,8 @@ class StrategyMarkdownService {
25524
25962
  priceStopLoss,
25525
25963
  originalPriceTakeProfit,
25526
25964
  originalPriceStopLoss,
25965
+ originalPriceOpen,
25966
+ totalEntries,
25527
25967
  scheduledAt,
25528
25968
  pendingAt,
25529
25969
  });
@@ -25546,7 +25986,7 @@ class StrategyMarkdownService {
25546
25986
  * @param scheduledAt - Signal creation timestamp in milliseconds
25547
25987
  * @param pendingAt - Pending timestamp in milliseconds
25548
25988
  */
25549
- this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25989
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25550
25990
  this.loggerService.log("strategyMarkdownService trailingTake", {
25551
25991
  symbol,
25552
25992
  percentShift,
@@ -25584,6 +26024,8 @@ class StrategyMarkdownService {
25584
26024
  priceStopLoss,
25585
26025
  originalPriceTakeProfit,
25586
26026
  originalPriceStopLoss,
26027
+ originalPriceOpen,
26028
+ totalEntries,
25587
26029
  scheduledAt,
25588
26030
  pendingAt,
25589
26031
  });
@@ -25605,7 +26047,7 @@ class StrategyMarkdownService {
25605
26047
  * @param scheduledAt - Signal creation timestamp in milliseconds
25606
26048
  * @param pendingAt - Pending timestamp in milliseconds
25607
26049
  */
25608
- this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
26050
+ this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25609
26051
  this.loggerService.log("strategyMarkdownService breakeven", {
25610
26052
  symbol,
25611
26053
  currentPrice,
@@ -25641,6 +26083,8 @@ class StrategyMarkdownService {
25641
26083
  priceStopLoss,
25642
26084
  originalPriceTakeProfit,
25643
26085
  originalPriceStopLoss,
26086
+ originalPriceOpen,
26087
+ totalEntries,
25644
26088
  scheduledAt,
25645
26089
  pendingAt,
25646
26090
  });
@@ -25663,7 +26107,7 @@ class StrategyMarkdownService {
25663
26107
  * @param pendingAt - Pending timestamp in milliseconds
25664
26108
  * @param activateId - Optional identifier for the activation reason
25665
26109
  */
25666
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
26110
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
25667
26111
  this.loggerService.log("strategyMarkdownService activateScheduled", {
25668
26112
  symbol,
25669
26113
  currentPrice,
@@ -25701,6 +26145,72 @@ class StrategyMarkdownService {
25701
26145
  priceStopLoss,
25702
26146
  originalPriceTakeProfit,
25703
26147
  originalPriceStopLoss,
26148
+ originalPriceOpen,
26149
+ totalEntries,
26150
+ scheduledAt,
26151
+ pendingAt,
26152
+ });
26153
+ };
26154
+ /**
26155
+ * Records an average-buy (DCA) event when a new averaging entry is added to an open position.
26156
+ *
26157
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
26158
+ * @param currentPrice - Price at which the new averaging entry was executed
26159
+ * @param effectivePriceOpen - Averaged entry price after this addition
26160
+ * @param totalEntries - Total number of DCA entries after this addition
26161
+ * @param isBacktest - Whether this is a backtest or live trading event
26162
+ * @param context - Strategy context with strategyName, exchangeName, frameName
26163
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
26164
+ * @param position - Trade direction: "long" or "short"
26165
+ * @param priceOpen - Original entry price (unchanged by averaging)
26166
+ * @param priceTakeProfit - Effective take profit price
26167
+ * @param priceStopLoss - Effective stop loss price
26168
+ * @param originalPriceTakeProfit - Original take profit before trailing
26169
+ * @param originalPriceStopLoss - Original stop loss before trailing
26170
+ * @param scheduledAt - Signal creation timestamp in milliseconds
26171
+ * @param pendingAt - Pending timestamp in milliseconds
26172
+ */
26173
+ this.averageBuy = async (symbol, currentPrice, effectivePriceOpen, totalEntries, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, originalPriceOpen) => {
26174
+ this.loggerService.log("strategyMarkdownService averageBuy", {
26175
+ symbol,
26176
+ currentPrice,
26177
+ effectivePriceOpen,
26178
+ totalEntries,
26179
+ isBacktest,
26180
+ });
26181
+ if (!this.subscribe.hasValue()) {
26182
+ return;
26183
+ }
26184
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
26185
+ exchangeName: context.exchangeName,
26186
+ strategyName: context.strategyName,
26187
+ frameName: context.frameName,
26188
+ });
26189
+ if (!pendingRow) {
26190
+ return;
26191
+ }
26192
+ const createdAt = new Date(timestamp).toISOString();
26193
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
26194
+ storage.addEvent({
26195
+ timestamp,
26196
+ symbol,
26197
+ strategyName: context.strategyName,
26198
+ exchangeName: context.exchangeName,
26199
+ frameName: context.frameName,
26200
+ signalId: pendingRow.id,
26201
+ action: "average-buy",
26202
+ currentPrice,
26203
+ effectivePriceOpen,
26204
+ totalEntries,
26205
+ createdAt,
26206
+ backtest: isBacktest,
26207
+ position,
26208
+ priceOpen,
26209
+ priceTakeProfit,
26210
+ priceStopLoss,
26211
+ originalPriceTakeProfit,
26212
+ originalPriceStopLoss,
26213
+ originalPriceOpen,
25704
26214
  scheduledAt,
25705
26215
  pendingAt,
25706
26216
  });
@@ -25849,43 +26359,50 @@ class StrategyMarkdownService {
25849
26359
  exchangeName: event.exchangeName,
25850
26360
  frameName: event.frameName,
25851
26361
  strategyName: event.strategyName,
25852
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26362
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25853
26363
  const unPartialLoss = strategyCommitSubject
25854
26364
  .filter(({ action }) => action === "partial-loss")
25855
26365
  .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
25856
26366
  exchangeName: event.exchangeName,
25857
26367
  frameName: event.frameName,
25858
26368
  strategyName: event.strategyName,
25859
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26369
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25860
26370
  const unTrailingStop = strategyCommitSubject
25861
26371
  .filter(({ action }) => action === "trailing-stop")
25862
26372
  .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
25863
26373
  exchangeName: event.exchangeName,
25864
26374
  frameName: event.frameName,
25865
26375
  strategyName: event.strategyName,
25866
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26376
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25867
26377
  const unTrailingTake = strategyCommitSubject
25868
26378
  .filter(({ action }) => action === "trailing-take")
25869
26379
  .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
25870
26380
  exchangeName: event.exchangeName,
25871
26381
  frameName: event.frameName,
25872
26382
  strategyName: event.strategyName,
25873
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26383
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25874
26384
  const unBreakeven = strategyCommitSubject
25875
26385
  .filter(({ action }) => action === "breakeven")
25876
26386
  .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
25877
26387
  exchangeName: event.exchangeName,
25878
26388
  frameName: event.frameName,
25879
26389
  strategyName: event.strategyName,
25880
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26390
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25881
26391
  const unActivateScheduled = strategyCommitSubject
25882
26392
  .filter(({ action }) => action === "activate-scheduled")
25883
26393
  .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
25884
26394
  exchangeName: event.exchangeName,
25885
26395
  frameName: event.frameName,
25886
26396
  strategyName: event.strategyName,
25887
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
25888
- const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
26397
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
26398
+ const unAverageBuy = strategyCommitSubject
26399
+ .filter(({ action }) => action === "average-buy")
26400
+ .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
26401
+ exchangeName: event.exchangeName,
26402
+ frameName: event.frameName,
26403
+ strategyName: event.strategyName,
26404
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.originalPriceOpen));
26405
+ const disposeFn = compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled(), () => unAverageBuy());
25889
26406
  return () => {
25890
26407
  disposeFn();
25891
26408
  this.subscribe.clear();
@@ -27691,6 +28208,7 @@ const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
27691
28208
  const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
27692
28209
  const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
27693
28210
  const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
28211
+ const AVERAGE_BUY_METHOD_NAME = "strategy.commitAverageBuy";
27694
28212
  /**
27695
28213
  * Cancels the scheduled signal without stopping the strategy.
27696
28214
  *
@@ -28043,6 +28561,45 @@ async function commitActivateScheduled(symbol, activateId) {
28043
28561
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
28044
28562
  await bt.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
28045
28563
  }
28564
+ /**
28565
+ * Adds a new DCA entry to the active pending signal.
28566
+ *
28567
+ * Adds a new averaging entry at the current market price to the position's
28568
+ * entry history. Updates effectivePriceOpen (mean of all entries) and emits
28569
+ * an average-buy commit event.
28570
+ *
28571
+ * Automatically detects backtest/live mode from execution context.
28572
+ * Automatically fetches current price via getAveragePrice.
28573
+ *
28574
+ * @param symbol - Trading pair symbol
28575
+ * @returns Promise<boolean> - true if entry added, false if rejected
28576
+ *
28577
+ * @example
28578
+ * ```typescript
28579
+ * import { commitAverageBuy } from "backtest-kit";
28580
+ *
28581
+ * // Add DCA entry at current market price
28582
+ * const success = await commitAverageBuy("BTCUSDT");
28583
+ * if (success) {
28584
+ * console.log("DCA entry added");
28585
+ * }
28586
+ * ```
28587
+ */
28588
+ async function commitAverageBuy(symbol) {
28589
+ bt.loggerService.info(AVERAGE_BUY_METHOD_NAME, {
28590
+ symbol,
28591
+ });
28592
+ if (!ExecutionContextService.hasContext()) {
28593
+ throw new Error("commitAverageBuy requires an execution context");
28594
+ }
28595
+ if (!MethodContextService.hasContext()) {
28596
+ throw new Error("commitAverageBuy requires a method context");
28597
+ }
28598
+ const currentPrice = await getAveragePrice(symbol);
28599
+ const { backtest: isBacktest } = bt.executionContextService.context;
28600
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
28601
+ return await bt.strategyCoreService.averageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
28602
+ }
28046
28603
 
28047
28604
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
28048
28605
  /**
@@ -30323,6 +30880,7 @@ const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
30323
30880
  const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
30324
30881
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
30325
30882
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
30883
+ const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
30326
30884
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
30327
30885
  /**
30328
30886
  * Internal task function that runs backtest and handles completion.
@@ -31219,6 +31777,49 @@ class BacktestUtils {
31219
31777
  }
31220
31778
  await bt.strategyCoreService.activateScheduled(true, symbol, context, activateId);
31221
31779
  };
31780
+ /**
31781
+ * Adds a new DCA entry to the active pending signal.
31782
+ *
31783
+ * Adds a new averaging entry at currentPrice to the position's entry history.
31784
+ * Updates effectivePriceOpen (mean of all entries) and emits average-buy commit event.
31785
+ *
31786
+ * @param symbol - Trading pair symbol
31787
+ * @param currentPrice - New entry price to add to the averaging history
31788
+ * @param context - Execution context with strategyName, exchangeName, frameName
31789
+ * @returns Promise<boolean> - true if entry added, false if rejected
31790
+ *
31791
+ * @example
31792
+ * ```typescript
31793
+ * // Add DCA entry at current price
31794
+ * const success = await Backtest.commitAverageBuy("BTCUSDT", 42000, {
31795
+ * strategyName: "my-strategy",
31796
+ * exchangeName: "binance",
31797
+ * frameName: "1h"
31798
+ * });
31799
+ * if (success) {
31800
+ * console.log('DCA entry added');
31801
+ * }
31802
+ * ```
31803
+ */
31804
+ this.commitAverageBuy = async (symbol, currentPrice, context) => {
31805
+ bt.loggerService.info(BACKTEST_METHOD_NAME_AVERAGE_BUY, {
31806
+ symbol,
31807
+ currentPrice,
31808
+ context,
31809
+ });
31810
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31811
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31812
+ {
31813
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
31814
+ riskName &&
31815
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31816
+ riskList &&
31817
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
31818
+ actions &&
31819
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
31820
+ }
31821
+ return await bt.strategyCoreService.averageBuy(true, symbol, currentPrice, context);
31822
+ };
31222
31823
  /**
31223
31824
  * Gets statistical data from all closed signals for a symbol-strategy pair.
31224
31825
  *
@@ -31395,6 +31996,7 @@ const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
31395
31996
  const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
31396
31997
  const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
31397
31998
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
31999
+ const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
31398
32000
  /**
31399
32001
  * Internal task function that runs live trading and handles completion.
31400
32002
  * Consumes live trading results and updates instance state flags.
@@ -32259,6 +32861,49 @@ class LiveUtils {
32259
32861
  frameName: "",
32260
32862
  }, activateId);
32261
32863
  };
32864
+ /**
32865
+ * Adds a new DCA entry to the active pending signal.
32866
+ *
32867
+ * Adds a new averaging entry at currentPrice to the position's entry history.
32868
+ * Updates effectivePriceOpen (mean of all entries) and emits average-buy commit event.
32869
+ *
32870
+ * @param symbol - Trading pair symbol
32871
+ * @param currentPrice - New entry price to add to the averaging history
32872
+ * @param context - Execution context with strategyName and exchangeName
32873
+ * @returns Promise<boolean> - true if entry added, false if rejected
32874
+ *
32875
+ * @example
32876
+ * ```typescript
32877
+ * // Add DCA entry at current price
32878
+ * const success = await Live.commitAverageBuy("BTCUSDT", 42000, {
32879
+ * strategyName: "my-strategy",
32880
+ * exchangeName: "binance"
32881
+ * });
32882
+ * if (success) {
32883
+ * console.log('DCA entry added');
32884
+ * }
32885
+ * ```
32886
+ */
32887
+ this.commitAverageBuy = async (symbol, currentPrice, context) => {
32888
+ bt.loggerService.info(LIVE_METHOD_NAME_AVERAGE_BUY, {
32889
+ symbol,
32890
+ currentPrice,
32891
+ context,
32892
+ });
32893
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_AVERAGE_BUY);
32894
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_AVERAGE_BUY);
32895
+ {
32896
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
32897
+ riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY);
32898
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY));
32899
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_AVERAGE_BUY));
32900
+ }
32901
+ return await bt.strategyCoreService.averageBuy(false, symbol, currentPrice, {
32902
+ strategyName: context.strategyName,
32903
+ exchangeName: context.exchangeName,
32904
+ frameName: "",
32905
+ });
32906
+ };
32262
32907
  /**
32263
32908
  * Gets statistical data from all live trading events for a symbol-strategy pair.
32264
32909
  *
@@ -34922,6 +35567,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34922
35567
  priceStopLoss: data.signal.priceStopLoss,
34923
35568
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34924
35569
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35570
+ originalPriceOpen: data.signal.originalPriceOpen,
35571
+ totalEntries: data.signal.totalEntries,
34925
35572
  note: data.signal.note,
34926
35573
  scheduledAt: data.signal.scheduledAt,
34927
35574
  pendingAt: data.signal.pendingAt,
@@ -34947,6 +35594,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34947
35594
  priceStopLoss: data.signal.priceStopLoss,
34948
35595
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34949
35596
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35597
+ originalPriceOpen: data.signal.originalPriceOpen,
35598
+ totalEntries: data.signal.totalEntries,
34950
35599
  pnlPercentage: data.pnl.pnlPercentage,
34951
35600
  closeReason: data.closeReason,
34952
35601
  duration: durationMin,
@@ -34972,6 +35621,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34972
35621
  priceStopLoss: data.signal.priceStopLoss,
34973
35622
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34974
35623
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35624
+ originalPriceOpen: data.signal.originalPriceOpen,
35625
+ totalEntries: data.signal.totalEntries,
34975
35626
  scheduledAt: data.signal.scheduledAt,
34976
35627
  currentPrice: data.currentPrice,
34977
35628
  createdAt: data.createdAt,
@@ -34995,6 +35646,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34995
35646
  priceStopLoss: data.signal.priceStopLoss,
34996
35647
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34997
35648
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35649
+ originalPriceOpen: data.signal.originalPriceOpen,
35650
+ totalEntries: data.signal.totalEntries,
34998
35651
  cancelReason: data.reason,
34999
35652
  cancelId: data.cancelId,
35000
35653
  duration: durationMin,
@@ -35027,6 +35680,8 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
35027
35680
  priceStopLoss: data.data.priceStopLoss,
35028
35681
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35029
35682
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35683
+ originalPriceOpen: data.data.originalPriceOpen,
35684
+ totalEntries: data.data.totalEntries,
35030
35685
  scheduledAt: data.data.scheduledAt,
35031
35686
  pendingAt: data.data.pendingAt,
35032
35687
  createdAt: data.timestamp,
@@ -35053,6 +35708,8 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
35053
35708
  priceStopLoss: data.data.priceStopLoss,
35054
35709
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35055
35710
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35711
+ originalPriceOpen: data.data.originalPriceOpen,
35712
+ totalEntries: data.data.totalEntries,
35056
35713
  scheduledAt: data.data.scheduledAt,
35057
35714
  pendingAt: data.data.pendingAt,
35058
35715
  createdAt: data.timestamp,
@@ -35078,6 +35735,8 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
35078
35735
  priceStopLoss: data.data.priceStopLoss,
35079
35736
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35080
35737
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35738
+ originalPriceOpen: data.data.originalPriceOpen,
35739
+ totalEntries: data.data.totalEntries,
35081
35740
  scheduledAt: data.data.scheduledAt,
35082
35741
  pendingAt: data.data.pendingAt,
35083
35742
  createdAt: data.timestamp,
@@ -35108,6 +35767,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35108
35767
  priceStopLoss: data.priceStopLoss,
35109
35768
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35110
35769
  originalPriceStopLoss: data.originalPriceStopLoss,
35770
+ originalPriceOpen: data.originalPriceOpen,
35771
+ totalEntries: data.totalEntries,
35111
35772
  scheduledAt: data.scheduledAt,
35112
35773
  pendingAt: data.pendingAt,
35113
35774
  createdAt: data.timestamp,
@@ -35131,6 +35792,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35131
35792
  priceStopLoss: data.priceStopLoss,
35132
35793
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35133
35794
  originalPriceStopLoss: data.originalPriceStopLoss,
35795
+ originalPriceOpen: data.originalPriceOpen,
35796
+ totalEntries: data.totalEntries,
35134
35797
  scheduledAt: data.scheduledAt,
35135
35798
  pendingAt: data.pendingAt,
35136
35799
  createdAt: data.timestamp,
@@ -35153,6 +35816,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35153
35816
  priceStopLoss: data.priceStopLoss,
35154
35817
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35155
35818
  originalPriceStopLoss: data.originalPriceStopLoss,
35819
+ originalPriceOpen: data.originalPriceOpen,
35820
+ totalEntries: data.totalEntries,
35156
35821
  scheduledAt: data.scheduledAt,
35157
35822
  pendingAt: data.pendingAt,
35158
35823
  createdAt: data.timestamp,
@@ -35176,6 +35841,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35176
35841
  priceStopLoss: data.priceStopLoss,
35177
35842
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35178
35843
  originalPriceStopLoss: data.originalPriceStopLoss,
35844
+ originalPriceOpen: data.originalPriceOpen,
35845
+ totalEntries: data.totalEntries,
35179
35846
  scheduledAt: data.scheduledAt,
35180
35847
  pendingAt: data.pendingAt,
35181
35848
  createdAt: data.timestamp,
@@ -35199,6 +35866,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35199
35866
  priceStopLoss: data.priceStopLoss,
35200
35867
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35201
35868
  originalPriceStopLoss: data.originalPriceStopLoss,
35869
+ originalPriceOpen: data.originalPriceOpen,
35870
+ totalEntries: data.totalEntries,
35202
35871
  scheduledAt: data.scheduledAt,
35203
35872
  pendingAt: data.pendingAt,
35204
35873
  createdAt: data.timestamp,
@@ -35222,6 +35891,33 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35222
35891
  priceStopLoss: data.priceStopLoss,
35223
35892
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35224
35893
  originalPriceStopLoss: data.originalPriceStopLoss,
35894
+ originalPriceOpen: data.originalPriceOpen,
35895
+ totalEntries: data.totalEntries,
35896
+ scheduledAt: data.scheduledAt,
35897
+ pendingAt: data.pendingAt,
35898
+ createdAt: data.timestamp,
35899
+ };
35900
+ }
35901
+ if (data.action === "average-buy") {
35902
+ return {
35903
+ type: "average_buy.commit",
35904
+ id: CREATE_KEY_FN$1(),
35905
+ timestamp: data.timestamp,
35906
+ backtest: data.backtest,
35907
+ symbol: data.symbol,
35908
+ strategyName: data.strategyName,
35909
+ exchangeName: data.exchangeName,
35910
+ signalId: data.signalId,
35911
+ currentPrice: data.currentPrice,
35912
+ effectivePriceOpen: data.effectivePriceOpen,
35913
+ totalEntries: data.totalEntries,
35914
+ position: data.position,
35915
+ priceOpen: data.priceOpen,
35916
+ priceTakeProfit: data.priceTakeProfit,
35917
+ priceStopLoss: data.priceStopLoss,
35918
+ originalPriceTakeProfit: data.originalPriceTakeProfit,
35919
+ originalPriceStopLoss: data.originalPriceStopLoss,
35920
+ originalPriceOpen: data.originalPriceOpen,
35225
35921
  scheduledAt: data.scheduledAt,
35226
35922
  pendingAt: data.pendingAt,
35227
35923
  createdAt: data.timestamp,
@@ -37564,4 +38260,4 @@ const set = (object, path, value) => {
37564
38260
  }
37565
38261
  };
37566
38262
 
37567
- export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };
38263
+ export { ActionBase, Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, Markdown, MarkdownFileBase, MarkdownFolderBase, MethodContextService, Notification, NotificationBacktest, NotificationLive, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistCandleAdapter, PersistNotificationAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PersistStorageAdapter, PositionSize, Report, ReportBase, Risk, Schedule, Storage, StorageBacktest, StorageLive, Strategy, Walker, addActionSchema, addExchangeSchema, addFrameSchema, addRiskSchema, addSizingSchema, addStrategySchema, addWalkerSchema, alignToInterval, checkCandles, commitActivateScheduled, commitAverageBuy, commitBreakeven, commitCancelScheduled, commitClosePending, commitPartialLoss, commitPartialProfit, commitTrailingStop, commitTrailingTake, dumpMessages, emitters, formatPrice, formatQuantity, get, getActionSchema, getAveragePrice, getBacktestTimeframe, getCandles, getColumns, getConfig, getContext, getDate, getDefaultColumns, getDefaultConfig, getExchangeSchema, getFrameSchema, getMode, getNextCandles, getOrderBook, getRawCandles, getRiskSchema, getSizingSchema, getStrategySchema, getSymbol, getWalkerSchema, hasTradeContext, backtest as lib, listExchangeSchema, listFrameSchema, listRiskSchema, listSizingSchema, listStrategySchema, listWalkerSchema, listenActivePing, listenActivePingOnce, listenBacktestProgress, listenBreakevenAvailable, listenBreakevenAvailableOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenPartialLossAvailable, listenPartialLossAvailableOnce, listenPartialProfitAvailable, listenPartialProfitAvailableOnce, listenPerformance, listenRisk, listenRiskOnce, listenSchedulePing, listenSchedulePingOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenStrategyCommit, listenStrategyCommitOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, overrideActionSchema, overrideExchangeSchema, overrideFrameSchema, overrideRiskSchema, overrideSizingSchema, overrideStrategySchema, overrideWalkerSchema, parseArgs, roundTicks, set, setColumns, setConfig, setLogger, stopStrategy, validate, waitForCandle, warmCandles };