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.cjs CHANGED
@@ -2900,6 +2900,27 @@ class ExchangeConnectionService {
2900
2900
  }
2901
2901
  }
2902
2902
 
2903
+ /**
2904
+ * Returns the effective entry price for price calculations.
2905
+ *
2906
+ * When the _entry array exists and has at least one element, returns
2907
+ * the simple arithmetic mean of all entry prices (DCA average).
2908
+ * Otherwise returns the original signal.priceOpen.
2909
+ *
2910
+ * This mirrors the _trailingPriceStopLoss pattern: original price is preserved
2911
+ * in signal.priceOpen (for identity/tracking), while calculations use the
2912
+ * effective averaged price returned by this function.
2913
+ *
2914
+ * @param signal - Signal row (ISignalRow or IScheduledSignalRow)
2915
+ * @returns Effective entry price for distance and PNL calculations
2916
+ */
2917
+ const getEffectivePriceOpen = (signal) => {
2918
+ if (signal._entry && signal._entry.length > 0) {
2919
+ return signal._entry.reduce((sum, e) => sum + e.price, 0) / signal._entry.length;
2920
+ }
2921
+ return signal.priceOpen;
2922
+ };
2923
+
2903
2924
  /**
2904
2925
  * Calculates profit/loss for a closed signal with slippage and fees.
2905
2926
  *
@@ -2950,7 +2971,7 @@ class ExchangeConnectionService {
2950
2971
  * ```
2951
2972
  */
2952
2973
  const toProfitLossDto = (signal, priceClose) => {
2953
- const priceOpen = signal.priceOpen;
2974
+ const priceOpen = getEffectivePriceOpen(signal);
2954
2975
  // Calculate weighted PNL with partial closes
2955
2976
  if (signal._partial && signal._partial.length > 0) {
2956
2977
  let totalWeightedPnl = 0;
@@ -3204,6 +3225,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3204
3225
  percentToClose: commit.percentToClose,
3205
3226
  currentPrice: commit.currentPrice,
3206
3227
  timestamp,
3228
+ totalEntries: publicSignal.totalEntries,
3207
3229
  position: publicSignal.position,
3208
3230
  priceOpen: publicSignal.priceOpen,
3209
3231
  signalId: publicSignal.id,
@@ -3211,6 +3233,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3211
3233
  priceStopLoss: publicSignal.priceStopLoss,
3212
3234
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3213
3235
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3236
+ originalPriceOpen: publicSignal.originalPriceOpen,
3214
3237
  scheduledAt: publicSignal.scheduledAt,
3215
3238
  pendingAt: publicSignal.pendingAt,
3216
3239
  });
@@ -3227,6 +3250,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3227
3250
  percentToClose: commit.percentToClose,
3228
3251
  currentPrice: commit.currentPrice,
3229
3252
  timestamp,
3253
+ totalEntries: publicSignal.totalEntries,
3230
3254
  position: publicSignal.position,
3231
3255
  priceOpen: publicSignal.priceOpen,
3232
3256
  signalId: publicSignal.id,
@@ -3234,6 +3258,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3234
3258
  priceStopLoss: publicSignal.priceStopLoss,
3235
3259
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3236
3260
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3261
+ originalPriceOpen: publicSignal.originalPriceOpen,
3237
3262
  scheduledAt: publicSignal.scheduledAt,
3238
3263
  pendingAt: publicSignal.pendingAt,
3239
3264
  });
@@ -3249,6 +3274,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3249
3274
  backtest: commit.backtest,
3250
3275
  currentPrice: commit.currentPrice,
3251
3276
  timestamp,
3277
+ totalEntries: publicSignal.totalEntries,
3252
3278
  signalId: publicSignal.id,
3253
3279
  position: publicSignal.position,
3254
3280
  priceOpen: publicSignal.priceOpen,
@@ -3256,6 +3282,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3256
3282
  priceStopLoss: publicSignal.priceStopLoss,
3257
3283
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3258
3284
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3285
+ originalPriceOpen: publicSignal.originalPriceOpen,
3259
3286
  scheduledAt: publicSignal.scheduledAt,
3260
3287
  pendingAt: publicSignal.pendingAt,
3261
3288
  });
@@ -3272,6 +3299,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3272
3299
  percentShift: commit.percentShift,
3273
3300
  currentPrice: commit.currentPrice,
3274
3301
  timestamp,
3302
+ totalEntries: publicSignal.totalEntries,
3275
3303
  signalId: publicSignal.id,
3276
3304
  position: publicSignal.position,
3277
3305
  priceOpen: publicSignal.priceOpen,
@@ -3279,6 +3307,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3279
3307
  priceStopLoss: publicSignal.priceStopLoss,
3280
3308
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3281
3309
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3310
+ originalPriceOpen: publicSignal.originalPriceOpen,
3282
3311
  scheduledAt: publicSignal.scheduledAt,
3283
3312
  pendingAt: publicSignal.pendingAt,
3284
3313
  });
@@ -3295,6 +3324,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3295
3324
  percentShift: commit.percentShift,
3296
3325
  currentPrice: commit.currentPrice,
3297
3326
  timestamp,
3327
+ totalEntries: publicSignal.totalEntries,
3298
3328
  signalId: publicSignal.id,
3299
3329
  position: publicSignal.position,
3300
3330
  priceOpen: publicSignal.priceOpen,
@@ -3302,6 +3332,33 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
3302
3332
  priceStopLoss: publicSignal.priceStopLoss,
3303
3333
  originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3304
3334
  originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3335
+ originalPriceOpen: publicSignal.originalPriceOpen,
3336
+ scheduledAt: publicSignal.scheduledAt,
3337
+ pendingAt: publicSignal.pendingAt,
3338
+ });
3339
+ continue;
3340
+ }
3341
+ if (commit.action === "average-buy") {
3342
+ const effectivePriceOpen = getEffectivePriceOpen(self._pendingSignal);
3343
+ await CALL_COMMIT_FN(self, {
3344
+ action: "average-buy",
3345
+ symbol: commit.symbol,
3346
+ strategyName: self.params.strategyName,
3347
+ exchangeName: self.params.exchangeName,
3348
+ frameName: self.params.frameName,
3349
+ backtest: commit.backtest,
3350
+ currentPrice: commit.currentPrice,
3351
+ effectivePriceOpen,
3352
+ timestamp,
3353
+ totalEntries: publicSignal.totalEntries,
3354
+ signalId: publicSignal.id,
3355
+ position: publicSignal.position,
3356
+ priceOpen: publicSignal.originalPriceOpen,
3357
+ priceTakeProfit: publicSignal.priceTakeProfit,
3358
+ priceStopLoss: publicSignal.priceStopLoss,
3359
+ originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
3360
+ originalPriceStopLoss: publicSignal.originalPriceStopLoss,
3361
+ originalPriceOpen: publicSignal.originalPriceOpen,
3305
3362
  scheduledAt: publicSignal.scheduledAt,
3306
3363
  pendingAt: publicSignal.pendingAt,
3307
3364
  });
@@ -3362,13 +3419,20 @@ const TO_PUBLIC_SIGNAL = (signal) => {
3362
3419
  const partialExecuted = ("_partial" in signal && Array.isArray(signal._partial))
3363
3420
  ? signal._partial.reduce((sum, partial) => sum + partial.percent, 0)
3364
3421
  : 0;
3422
+ const totalEntries = ("_entry" in signal && Array.isArray(signal._entry))
3423
+ ? signal._entry.length
3424
+ : 1;
3425
+ const effectivePriceOpen = "_entry" in signal ? getEffectivePriceOpen(signal) : signal.priceOpen;
3365
3426
  return {
3366
3427
  ...structuredClone(signal),
3428
+ priceOpen: effectivePriceOpen,
3367
3429
  priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
3368
3430
  priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
3431
+ originalPriceOpen: signal.priceOpen,
3369
3432
  originalPriceStopLoss: signal.priceStopLoss,
3370
3433
  originalPriceTakeProfit: signal.priceTakeProfit,
3371
3434
  partialExecuted,
3435
+ totalEntries,
3372
3436
  };
3373
3437
  };
3374
3438
  const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
@@ -3698,6 +3762,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
3698
3762
  scheduledAt: currentTime,
3699
3763
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
3700
3764
  _isScheduled: false,
3765
+ _entry: [{ price: signal.priceOpen }],
3701
3766
  };
3702
3767
  // Валидируем сигнал перед возвратом
3703
3768
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -3719,6 +3784,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
3719
3784
  scheduledAt: currentTime,
3720
3785
  pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
3721
3786
  _isScheduled: true,
3787
+ _entry: [{ price: signal.priceOpen }],
3722
3788
  };
3723
3789
  // Валидируем сигнал перед возвратом
3724
3790
  VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
@@ -3736,6 +3802,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
3736
3802
  scheduledAt: currentTime,
3737
3803
  pendingAt: currentTime, // Для immediate signal оба времени одинаковые
3738
3804
  _isScheduled: false,
3805
+ _entry: [{ price: currentPrice }],
3739
3806
  };
3740
3807
  // Валидируем сигнал перед возвратом
3741
3808
  VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
@@ -3882,9 +3949,10 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
3882
3949
  return true;
3883
3950
  };
3884
3951
  const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3952
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
3885
3953
  // CRITICAL: Always calculate from ORIGINAL SL, not from current trailing SL
3886
3954
  // This prevents error accumulation on repeated calls
3887
- const originalSlDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
3955
+ const originalSlDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
3888
3956
  // Calculate new stop-loss distance percentage by adding shift to ORIGINAL distance
3889
3957
  // Negative percentShift: reduces distance % (tightens stop, moves SL toward entry or beyond)
3890
3958
  // Positive percentShift: increases distance % (loosens stop, moves SL away from entry)
@@ -3896,13 +3964,13 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3896
3964
  // LONG: SL is below entry (or above entry if in profit zone)
3897
3965
  // Formula: entry * (1 - newDistance%)
3898
3966
  // Example: entry=100, originalSL=90 (10%), shift=-5% → newDistance=5% → 100 * 0.95 = 95 (tighter)
3899
- newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
3967
+ newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
3900
3968
  }
3901
3969
  else {
3902
3970
  // SHORT: SL is above entry (or below entry if in profit zone)
3903
3971
  // Formula: entry * (1 + newDistance%)
3904
3972
  // Example: entry=100, originalSL=110 (10%), shift=-5% → newDistance=5% → 100 * 1.05 = 105 (tighter)
3905
- newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
3973
+ newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
3906
3974
  }
3907
3975
  const currentTrailingSL = signal._trailingPriceStopLoss;
3908
3976
  const isFirstCall = currentTrailingSL === undefined;
@@ -3965,9 +4033,10 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
3965
4033
  }
3966
4034
  };
3967
4035
  const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
4036
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
3968
4037
  // CRITICAL: Always calculate from ORIGINAL TP, not from current trailing TP
3969
4038
  // This prevents error accumulation on repeated calls
3970
- const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
4039
+ const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
3971
4040
  // Calculate new take-profit distance percentage by adding shift to ORIGINAL distance
3972
4041
  // Negative percentShift: reduces distance % (brings TP closer to entry)
3973
4042
  // Positive percentShift: increases distance % (moves TP further from entry)
@@ -3978,13 +4047,13 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
3978
4047
  // LONG: TP is above entry
3979
4048
  // Formula: entry * (1 + newDistance%)
3980
4049
  // Example: entry=100, originalTP=110 (10%), shift=-3% → newDistance=7% → 100 * 1.07 = 107 (closer)
3981
- newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
4050
+ newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
3982
4051
  }
3983
4052
  else {
3984
4053
  // SHORT: TP is below entry
3985
4054
  // Formula: entry * (1 - newDistance%)
3986
4055
  // Example: entry=100, originalTP=90 (10%), shift=-3% → newDistance=7% → 100 * 0.93 = 93 (closer)
3987
- newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
4056
+ newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
3988
4057
  }
3989
4058
  const currentTrailingTP = signal._trailingPriceTakeProfit;
3990
4059
  const isFirstCall = currentTrailingTP === undefined;
@@ -4045,6 +4114,7 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
4045
4114
  }
4046
4115
  };
4047
4116
  const BREAKEVEN_FN = (self, signal, currentPrice) => {
4117
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
4048
4118
  // Calculate breakeven threshold based on slippage and fees
4049
4119
  // Need to cover: entry slippage + entry fee + exit slippage + exit fee
4050
4120
  // Total: (slippage + fee) * 2 transactions
@@ -4052,10 +4122,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4052
4122
  // Check if trailing stop is already set
4053
4123
  if (signal._trailingPriceStopLoss !== undefined) {
4054
4124
  const trailingStopLoss = signal._trailingPriceStopLoss;
4055
- const breakevenPrice = signal.priceOpen;
4125
+ const breakevenPrice = effectivePriceOpen;
4056
4126
  if (signal.position === "long") {
4057
4127
  // LONG: trailing SL is positive if it's above entry (in profit zone)
4058
- const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
4128
+ const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
4059
4129
  if (isPositiveTrailing) {
4060
4130
  // Trailing stop is already protecting profit - consider breakeven achieved
4061
4131
  self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
@@ -4071,7 +4141,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4071
4141
  else {
4072
4142
  // Trailing stop is negative (below entry)
4073
4143
  // Check if we can upgrade it to breakeven
4074
- const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4144
+ const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
4075
4145
  const isThresholdReached = currentPrice >= thresholdPrice;
4076
4146
  if (isThresholdReached && breakevenPrice > trailingStopLoss) {
4077
4147
  // Check for price intrusion before setting new SL
@@ -4120,7 +4190,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4120
4190
  }
4121
4191
  else {
4122
4192
  // SHORT: trailing SL is positive if it's below entry (in profit zone)
4123
- const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
4193
+ const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
4124
4194
  if (isPositiveTrailing) {
4125
4195
  // Trailing stop is already protecting profit - consider breakeven achieved
4126
4196
  self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
@@ -4136,7 +4206,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4136
4206
  else {
4137
4207
  // Trailing stop is negative (above entry)
4138
4208
  // Check if we can upgrade it to breakeven
4139
- const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4209
+ const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
4140
4210
  const isThresholdReached = currentPrice <= thresholdPrice;
4141
4211
  if (isThresholdReached && breakevenPrice < trailingStopLoss) {
4142
4212
  // Check for price intrusion before setting new SL
@@ -4186,21 +4256,21 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4186
4256
  }
4187
4257
  // No trailing stop set - proceed with normal breakeven logic
4188
4258
  const currentStopLoss = signal.priceStopLoss;
4189
- const breakevenPrice = signal.priceOpen;
4259
+ const breakevenPrice = effectivePriceOpen;
4190
4260
  // Calculate threshold price
4191
4261
  let thresholdPrice;
4192
4262
  let isThresholdReached;
4193
4263
  let canMoveToBreakeven;
4194
4264
  if (signal.position === "long") {
4195
4265
  // LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
4196
- thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
4266
+ thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
4197
4267
  isThresholdReached = currentPrice >= thresholdPrice;
4198
4268
  // Can move to breakeven only if threshold reached and SL is below entry
4199
4269
  canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
4200
4270
  }
4201
4271
  else {
4202
4272
  // SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
4203
- thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
4273
+ thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
4204
4274
  isThresholdReached = currentPrice <= thresholdPrice;
4205
4275
  // Can move to breakeven only if threshold reached and SL is above entry
4206
4276
  canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
@@ -4260,8 +4330,51 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
4260
4330
  thresholdPrice,
4261
4331
  breakevenThresholdPercent,
4262
4332
  profitDistancePercent: signal.position === "long"
4263
- ? ((currentPrice - signal.priceOpen) / signal.priceOpen * 100)
4264
- : ((signal.priceOpen - currentPrice) / signal.priceOpen * 100),
4333
+ ? ((currentPrice - effectivePriceOpen) / effectivePriceOpen * 100)
4334
+ : ((effectivePriceOpen - currentPrice) / effectivePriceOpen * 100),
4335
+ });
4336
+ return true;
4337
+ };
4338
+ const AVERAGE_BUY_FN = (self, signal, currentPrice) => {
4339
+ // Ensure _entry is initialized (handles signals loaded from disk without _entry)
4340
+ if (!signal._entry || signal._entry.length === 0) {
4341
+ signal._entry = [{ price: signal.priceOpen }];
4342
+ }
4343
+ const lastEntry = signal._entry[signal._entry.length - 1];
4344
+ if (signal.position === "long") {
4345
+ // LONG: averaging down = currentPrice must be strictly lower than last entry
4346
+ if (currentPrice >= lastEntry.price) {
4347
+ self.params.logger.debug("AVERAGE_BUY_FN: rejected — currentPrice >= last entry (LONG)", {
4348
+ signalId: signal.id,
4349
+ position: signal.position,
4350
+ currentPrice,
4351
+ lastEntryPrice: lastEntry.price,
4352
+ reason: "must average down for LONG",
4353
+ });
4354
+ return false;
4355
+ }
4356
+ }
4357
+ else {
4358
+ // SHORT: averaging down = currentPrice must be strictly higher than last entry
4359
+ if (currentPrice <= lastEntry.price) {
4360
+ self.params.logger.debug("AVERAGE_BUY_FN: rejected — currentPrice <= last entry (SHORT)", {
4361
+ signalId: signal.id,
4362
+ position: signal.position,
4363
+ currentPrice,
4364
+ lastEntryPrice: lastEntry.price,
4365
+ reason: "must average down for SHORT",
4366
+ });
4367
+ return false;
4368
+ }
4369
+ }
4370
+ signal._entry.push({ price: currentPrice });
4371
+ self.params.logger.info("AVERAGE_BUY_FN executed", {
4372
+ signalId: signal.id,
4373
+ position: signal.position,
4374
+ originalPriceOpen: signal.priceOpen,
4375
+ newEntryPrice: currentPrice,
4376
+ newEffectivePrice: getEffectivePriceOpen(signal),
4377
+ totalEntries: signal._entry.length,
4265
4378
  });
4266
4379
  return true;
4267
4380
  };
@@ -4990,9 +5103,10 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
4990
5103
  await CALL_ACTIVE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentTime, self.params.execution.context.backtest);
4991
5104
  // Calculate percentage of path to TP/SL for partial fill/loss callbacks
4992
5105
  {
5106
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
4993
5107
  if (signal.position === "long") {
4994
5108
  // For long: calculate progress towards TP or SL
4995
- const currentDistance = currentPrice - signal.priceOpen;
5109
+ const currentDistance = currentPrice - effectivePriceOpen;
4996
5110
  if (currentDistance > 0) {
4997
5111
  // Check if breakeven should be triggered
4998
5112
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
@@ -5000,7 +5114,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5000
5114
  if (currentDistance > 0) {
5001
5115
  // Moving towards TP (use trailing TP if set)
5002
5116
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5003
- const tpDistance = effectiveTakeProfit - signal.priceOpen;
5117
+ const tpDistance = effectiveTakeProfit - effectivePriceOpen;
5004
5118
  const progressPercent = (currentDistance / tpDistance) * 100;
5005
5119
  percentTp = Math.min(progressPercent, 100);
5006
5120
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
@@ -5008,7 +5122,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5008
5122
  else if (currentDistance < 0) {
5009
5123
  // Moving towards SL (use trailing SL if set)
5010
5124
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5011
- const slDistance = signal.priceOpen - effectiveStopLoss;
5125
+ const slDistance = effectivePriceOpen - effectiveStopLoss;
5012
5126
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5013
5127
  percentSl = Math.min(progressPercent, 100);
5014
5128
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
@@ -5016,7 +5130,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5016
5130
  }
5017
5131
  else if (signal.position === "short") {
5018
5132
  // For short: calculate progress towards TP or SL
5019
- const currentDistance = signal.priceOpen - currentPrice;
5133
+ const currentDistance = effectivePriceOpen - currentPrice;
5020
5134
  if (currentDistance > 0) {
5021
5135
  // Check if breakeven should be triggered
5022
5136
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
@@ -5024,7 +5138,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5024
5138
  if (currentDistance > 0) {
5025
5139
  // Moving towards TP (use trailing TP if set)
5026
5140
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5027
- const tpDistance = signal.priceOpen - effectiveTakeProfit;
5141
+ const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5028
5142
  const progressPercent = (currentDistance / tpDistance) * 100;
5029
5143
  percentTp = Math.min(progressPercent, 100);
5030
5144
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
@@ -5032,7 +5146,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
5032
5146
  if (currentDistance < 0) {
5033
5147
  // Moving towards SL (use trailing SL if set)
5034
5148
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5035
- const slDistance = effectiveStopLoss - signal.priceOpen;
5149
+ const slDistance = effectiveStopLoss - effectivePriceOpen;
5036
5150
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5037
5151
  percentSl = Math.min(progressPercent, 100);
5038
5152
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
@@ -5252,8 +5366,10 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
5252
5366
  priceStopLoss: publicSignalForCommit.priceStopLoss,
5253
5367
  originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5254
5368
  originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
5369
+ originalPriceOpen: publicSignalForCommit.originalPriceOpen,
5255
5370
  scheduledAt: publicSignalForCommit.scheduledAt,
5256
5371
  pendingAt: publicSignalForCommit.pendingAt,
5372
+ totalEntries: publicSignalForCommit.totalEntries,
5257
5373
  });
5258
5374
  await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
5259
5375
  await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
@@ -5400,9 +5516,10 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5400
5516
  // Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
5401
5517
  // Calculate percentage of path to TP/SL
5402
5518
  {
5519
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
5403
5520
  if (signal.position === "long") {
5404
5521
  // For long: calculate progress towards TP or SL
5405
- const currentDistance = averagePrice - signal.priceOpen;
5522
+ const currentDistance = averagePrice - effectivePriceOpen;
5406
5523
  if (currentDistance > 0) {
5407
5524
  // Check if breakeven should be triggered
5408
5525
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
@@ -5410,21 +5527,21 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5410
5527
  if (currentDistance > 0) {
5411
5528
  // Moving towards TP (use trailing TP if set)
5412
5529
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5413
- const tpDistance = effectiveTakeProfit - signal.priceOpen;
5530
+ const tpDistance = effectiveTakeProfit - effectivePriceOpen;
5414
5531
  const progressPercent = (currentDistance / tpDistance) * 100;
5415
5532
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5416
5533
  }
5417
5534
  else if (currentDistance < 0) {
5418
5535
  // Moving towards SL (use trailing SL if set)
5419
5536
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5420
- const slDistance = signal.priceOpen - effectiveStopLoss;
5537
+ const slDistance = effectivePriceOpen - effectiveStopLoss;
5421
5538
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5422
5539
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5423
5540
  }
5424
5541
  }
5425
5542
  else if (signal.position === "short") {
5426
5543
  // For short: calculate progress towards TP or SL
5427
- const currentDistance = signal.priceOpen - averagePrice;
5544
+ const currentDistance = effectivePriceOpen - averagePrice;
5428
5545
  if (currentDistance > 0) {
5429
5546
  // Check if breakeven should be triggered
5430
5547
  await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
@@ -5432,14 +5549,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
5432
5549
  if (currentDistance > 0) {
5433
5550
  // Moving towards TP (use trailing TP if set)
5434
5551
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
5435
- const tpDistance = signal.priceOpen - effectiveTakeProfit;
5552
+ const tpDistance = effectivePriceOpen - effectiveTakeProfit;
5436
5553
  const progressPercent = (currentDistance / tpDistance) * 100;
5437
5554
  await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5438
5555
  }
5439
5556
  if (currentDistance < 0) {
5440
5557
  // Moving towards SL (use trailing SL if set)
5441
5558
  const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
5442
- const slDistance = effectiveStopLoss - signal.priceOpen;
5559
+ const slDistance = effectiveStopLoss - effectivePriceOpen;
5443
5560
  const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
5444
5561
  await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
5445
5562
  }
@@ -5637,6 +5754,7 @@ class ClientStrategy {
5637
5754
  return false;
5638
5755
  }
5639
5756
  const signal = this._pendingSignal;
5757
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
5640
5758
  // Calculate breakeven threshold based on slippage and fees
5641
5759
  // Need to cover: entry slippage + entry fee + exit slippage + exit fee
5642
5760
  // Total: (slippage + fee) * 2 transactions
@@ -5646,52 +5764,52 @@ class ClientStrategy {
5646
5764
  const trailingStopLoss = signal._trailingPriceStopLoss;
5647
5765
  if (signal.position === "long") {
5648
5766
  // LONG: trailing SL is positive if it's above entry (in profit zone)
5649
- const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
5767
+ const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
5650
5768
  if (isPositiveTrailing) {
5651
5769
  // Trailing stop is already protecting profit - breakeven achieved
5652
5770
  return true;
5653
5771
  }
5654
5772
  // Trailing stop is negative (below entry)
5655
5773
  // Check if we can upgrade it to breakeven
5656
- const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
5774
+ const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
5657
5775
  const isThresholdReached = currentPrice >= thresholdPrice;
5658
- const breakevenPrice = signal.priceOpen;
5776
+ const breakevenPrice = effectivePriceOpen;
5659
5777
  // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
5660
5778
  return isThresholdReached && breakevenPrice > trailingStopLoss;
5661
5779
  }
5662
5780
  else {
5663
5781
  // SHORT: trailing SL is positive if it's below entry (in profit zone)
5664
- const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
5782
+ const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
5665
5783
  if (isPositiveTrailing) {
5666
5784
  // Trailing stop is already protecting profit - breakeven achieved
5667
5785
  return true;
5668
5786
  }
5669
5787
  // Trailing stop is negative (above entry)
5670
5788
  // Check if we can upgrade it to breakeven
5671
- const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
5789
+ const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
5672
5790
  const isThresholdReached = currentPrice <= thresholdPrice;
5673
- const breakevenPrice = signal.priceOpen;
5791
+ const breakevenPrice = effectivePriceOpen;
5674
5792
  // Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
5675
5793
  return isThresholdReached && breakevenPrice < trailingStopLoss;
5676
5794
  }
5677
5795
  }
5678
5796
  // No trailing stop set - proceed with normal breakeven logic
5679
5797
  const currentStopLoss = signal.priceStopLoss;
5680
- const breakevenPrice = signal.priceOpen;
5798
+ const breakevenPrice = effectivePriceOpen;
5681
5799
  // Calculate threshold price
5682
5800
  let thresholdPrice;
5683
5801
  let isThresholdReached;
5684
5802
  let canMoveToBreakeven;
5685
5803
  if (signal.position === "long") {
5686
5804
  // LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
5687
- thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
5805
+ thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
5688
5806
  isThresholdReached = currentPrice >= thresholdPrice;
5689
5807
  // Can move to breakeven only if threshold reached and SL is below entry
5690
5808
  canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
5691
5809
  }
5692
5810
  else {
5693
5811
  // SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
5694
- thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
5812
+ thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
5695
5813
  isThresholdReached = currentPrice <= thresholdPrice;
5696
5814
  // Can move to breakeven only if threshold reached and SL is above entry
5697
5815
  canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
@@ -5774,6 +5892,8 @@ class ClientStrategy {
5774
5892
  backtest: this.params.execution.context.backtest,
5775
5893
  cancelId: cancelledSignal.cancelId,
5776
5894
  timestamp: currentTime,
5895
+ totalEntries: cancelledSignal._entry?.length ?? 1,
5896
+ originalPriceOpen: cancelledSignal.priceOpen,
5777
5897
  });
5778
5898
  // Call onCancel callback
5779
5899
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -5814,6 +5934,8 @@ class ClientStrategy {
5814
5934
  backtest: this.params.execution.context.backtest,
5815
5935
  closeId: closedSignal.closeId,
5816
5936
  timestamp: currentTime,
5937
+ totalEntries: closedSignal._entry?.length ?? 1,
5938
+ originalPriceOpen: closedSignal.priceOpen,
5817
5939
  });
5818
5940
  // Call onClose callback
5819
5941
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -5894,8 +6016,10 @@ class ClientStrategy {
5894
6016
  priceStopLoss: publicSignalForCommit.priceStopLoss,
5895
6017
  originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
5896
6018
  originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
6019
+ originalPriceOpen: publicSignalForCommit.originalPriceOpen,
5897
6020
  scheduledAt: publicSignalForCommit.scheduledAt,
5898
6021
  pendingAt: publicSignalForCommit.pendingAt,
6022
+ totalEntries: publicSignalForCommit.totalEntries,
5899
6023
  });
5900
6024
  // Call onOpen callback
5901
6025
  await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
@@ -6026,6 +6150,8 @@ class ClientStrategy {
6026
6150
  backtest: true,
6027
6151
  cancelId: cancelledSignal.cancelId,
6028
6152
  timestamp: closeTimestamp,
6153
+ totalEntries: cancelledSignal._entry?.length ?? 1,
6154
+ originalPriceOpen: cancelledSignal.priceOpen,
6029
6155
  });
6030
6156
  await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
6031
6157
  const cancelledResult = {
@@ -6063,6 +6189,8 @@ class ClientStrategy {
6063
6189
  backtest: true,
6064
6190
  closeId: closedSignal.closeId,
6065
6191
  timestamp: closeTimestamp,
6192
+ totalEntries: closedSignal._entry?.length ?? 1,
6193
+ originalPriceOpen: closedSignal.priceOpen,
6066
6194
  });
6067
6195
  await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
6068
6196
  // КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
@@ -6450,16 +6578,19 @@ class ClientStrategy {
6450
6578
  throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
6451
6579
  }
6452
6580
  // Validation: currentPrice must be moving toward TP (profit direction)
6453
- if (this._pendingSignal.position === "long") {
6454
- // For LONG: currentPrice must be higher than priceOpen (moving toward TP)
6455
- if (currentPrice <= this._pendingSignal.priceOpen) {
6456
- throw new Error(`ClientStrategy partialProfit: For LONG position, currentPrice (${currentPrice}) must be > priceOpen (${this._pendingSignal.priceOpen})`);
6581
+ {
6582
+ const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
6583
+ if (this._pendingSignal.position === "long") {
6584
+ // For LONG: currentPrice must be higher than effectivePriceOpen (moving toward TP)
6585
+ if (currentPrice <= effectivePriceOpen) {
6586
+ throw new Error(`ClientStrategy partialProfit: For LONG position, currentPrice (${currentPrice}) must be > effectivePriceOpen (${effectivePriceOpen})`);
6587
+ }
6457
6588
  }
6458
- }
6459
- else {
6460
- // For SHORT: currentPrice must be lower than priceOpen (moving toward TP)
6461
- if (currentPrice >= this._pendingSignal.priceOpen) {
6462
- throw new Error(`ClientStrategy partialProfit: For SHORT position, currentPrice (${currentPrice}) must be < priceOpen (${this._pendingSignal.priceOpen})`);
6589
+ else {
6590
+ // For SHORT: currentPrice must be lower than effectivePriceOpen (moving toward TP)
6591
+ if (currentPrice >= effectivePriceOpen) {
6592
+ throw new Error(`ClientStrategy partialProfit: For SHORT position, currentPrice (${currentPrice}) must be < effectivePriceOpen (${effectivePriceOpen})`);
6593
+ }
6463
6594
  }
6464
6595
  }
6465
6596
  // Check if currentPrice already crossed take profit level
@@ -6582,16 +6713,19 @@ class ClientStrategy {
6582
6713
  throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
6583
6714
  }
6584
6715
  // Validation: currentPrice must be moving toward SL (loss direction)
6585
- if (this._pendingSignal.position === "long") {
6586
- // For LONG: currentPrice must be lower than priceOpen (moving toward SL)
6587
- if (currentPrice >= this._pendingSignal.priceOpen) {
6588
- throw new Error(`ClientStrategy partialLoss: For LONG position, currentPrice (${currentPrice}) must be < priceOpen (${this._pendingSignal.priceOpen})`);
6716
+ {
6717
+ const effectivePriceOpen = getEffectivePriceOpen(this._pendingSignal);
6718
+ if (this._pendingSignal.position === "long") {
6719
+ // For LONG: currentPrice must be lower than effectivePriceOpen (moving toward SL)
6720
+ if (currentPrice >= effectivePriceOpen) {
6721
+ throw new Error(`ClientStrategy partialLoss: For LONG position, currentPrice (${currentPrice}) must be < effectivePriceOpen (${effectivePriceOpen})`);
6722
+ }
6589
6723
  }
6590
- }
6591
- else {
6592
- // For SHORT: currentPrice must be higher than priceOpen (moving toward SL)
6593
- if (currentPrice <= this._pendingSignal.priceOpen) {
6594
- throw new Error(`ClientStrategy partialLoss: For SHORT position, currentPrice (${currentPrice}) must be > priceOpen (${this._pendingSignal.priceOpen})`);
6724
+ else {
6725
+ // For SHORT: currentPrice must be higher than effectivePriceOpen (moving toward SL)
6726
+ if (currentPrice <= effectivePriceOpen) {
6727
+ throw new Error(`ClientStrategy partialLoss: For SHORT position, currentPrice (${currentPrice}) must be > effectivePriceOpen (${effectivePriceOpen})`);
6728
+ }
6595
6729
  }
6596
6730
  }
6597
6731
  // Check if currentPrice already crossed stop loss level
@@ -6712,7 +6846,7 @@ class ClientStrategy {
6712
6846
  }
6713
6847
  // Check for conflict with existing trailing take profit
6714
6848
  const signal = this._pendingSignal;
6715
- const breakevenPrice = signal.priceOpen;
6849
+ const breakevenPrice = getEffectivePriceOpen(signal);
6716
6850
  const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
6717
6851
  if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit) {
6718
6852
  // LONG: Breakeven SL would be at or above current TP - invalid configuration
@@ -6864,14 +6998,15 @@ class ClientStrategy {
6864
6998
  }
6865
6999
  // Calculate what the new stop loss would be
6866
7000
  const signal = this._pendingSignal;
6867
- const slDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
7001
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
7002
+ const slDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
6868
7003
  const newSlDistancePercent = slDistancePercent + percentShift;
6869
7004
  let newStopLoss;
6870
7005
  if (signal.position === "long") {
6871
- newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
7006
+ newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
6872
7007
  }
6873
7008
  else {
6874
- newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
7009
+ newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
6875
7010
  }
6876
7011
  // Check for price intrusion before executing trailing logic
6877
7012
  if (signal.position === "long" && currentPrice < newStopLoss) {
@@ -7037,14 +7172,15 @@ class ClientStrategy {
7037
7172
  }
7038
7173
  // Calculate what the new take profit would be
7039
7174
  const signal = this._pendingSignal;
7040
- const tpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
7175
+ const effectivePriceOpen = getEffectivePriceOpen(signal);
7176
+ const tpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
7041
7177
  const newTpDistancePercent = tpDistancePercent + percentShift;
7042
7178
  let newTakeProfit;
7043
7179
  if (signal.position === "long") {
7044
- newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
7180
+ newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
7045
7181
  }
7046
7182
  else {
7047
- newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
7183
+ newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
7048
7184
  }
7049
7185
  // Check for price intrusion before executing trailing logic
7050
7186
  if (signal.position === "long" && currentPrice > newTakeProfit) {
@@ -7126,6 +7262,70 @@ class ClientStrategy {
7126
7262
  });
7127
7263
  return true;
7128
7264
  }
7265
+ /**
7266
+ * Adds a new averaging entry to an open position (DCA — Dollar Cost Averaging).
7267
+ *
7268
+ * Appends currentPrice to the _entry array. The effective entry price used in all
7269
+ * distance and PNL calculations becomes the simple arithmetic mean of all _entry prices.
7270
+ * Original priceOpen is preserved unchanged for identity/audit purposes.
7271
+ *
7272
+ * Rejection rules (returns false without throwing):
7273
+ * - LONG: currentPrice >= last entry price (must average down, not up or equal)
7274
+ * - SHORT: currentPrice <= last entry price (must average down, not up or equal)
7275
+ *
7276
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
7277
+ * @param currentPrice - New entry price to add to the averaging history
7278
+ * @param backtest - Whether running in backtest mode
7279
+ * @returns Promise<boolean> - true if entry added, false if rejected by direction check
7280
+ */
7281
+ async averageBuy(symbol, currentPrice, backtest) {
7282
+ this.params.logger.debug("ClientStrategy averageBuy", {
7283
+ symbol,
7284
+ currentPrice,
7285
+ hasPendingSignal: this._pendingSignal !== null,
7286
+ });
7287
+ // Validation: must have pending signal
7288
+ if (!this._pendingSignal) {
7289
+ throw new Error(`ClientStrategy averageBuy: No pending signal exists for symbol=${symbol}`);
7290
+ }
7291
+ // Validation: currentPrice must be valid
7292
+ if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
7293
+ throw new Error(`ClientStrategy averageBuy: currentPrice must be a positive finite number, got ${currentPrice}`);
7294
+ }
7295
+ // Reject if any partial closes have already been executed
7296
+ if (this._pendingSignal._partial && this._pendingSignal._partial.length > 0) {
7297
+ this.params.logger.debug("ClientStrategy averageBuy: rejected — partial closes already executed", {
7298
+ symbol,
7299
+ partialCount: this._pendingSignal._partial.length,
7300
+ });
7301
+ return false;
7302
+ }
7303
+ // Execute averaging logic
7304
+ const result = AVERAGE_BUY_FN(this, this._pendingSignal, currentPrice);
7305
+ if (!result) {
7306
+ return false;
7307
+ }
7308
+ // Persist updated signal state
7309
+ this.params.logger.debug("ClientStrategy setPendingSignal (inline)", {
7310
+ pendingSignal: this._pendingSignal,
7311
+ });
7312
+ // Call onWrite callback for testing persist storage
7313
+ if (this.params.callbacks?.onWrite) {
7314
+ this.params.callbacks.onWrite(this.params.execution.context.symbol, TO_PUBLIC_SIGNAL(this._pendingSignal), backtest);
7315
+ }
7316
+ if (!backtest) {
7317
+ await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
7318
+ }
7319
+ // Queue commit event for processing in tick()/backtest() with proper timestamp
7320
+ this._commitQueue.push({
7321
+ action: "average-buy",
7322
+ symbol,
7323
+ backtest,
7324
+ currentPrice,
7325
+ totalEntries: this._pendingSignal._entry?.length ?? 1,
7326
+ });
7327
+ return true;
7328
+ }
7129
7329
  }
7130
7330
 
7131
7331
  const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
@@ -8250,6 +8450,27 @@ class StrategyConnectionService {
8250
8450
  const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8251
8451
  return await strategy.activateScheduled(symbol, backtest, activateId);
8252
8452
  };
8453
+ /**
8454
+ * Adds a new DCA entry to the active pending signal.
8455
+ *
8456
+ * Delegates to ClientStrategy.averageBuy() with current execution context.
8457
+ *
8458
+ * @param backtest - Whether running in backtest mode
8459
+ * @param symbol - Trading pair symbol
8460
+ * @param currentPrice - New entry price to add to the averaging history
8461
+ * @param context - Execution context with strategyName, exchangeName, frameName
8462
+ * @returns Promise<boolean> - true if entry added, false if rejected
8463
+ */
8464
+ this.averageBuy = async (backtest, symbol, currentPrice, context) => {
8465
+ this.loggerService.log("strategyConnectionService averageBuy", {
8466
+ symbol,
8467
+ context,
8468
+ currentPrice,
8469
+ backtest,
8470
+ });
8471
+ const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
8472
+ return await strategy.averageBuy(symbol, currentPrice, backtest);
8473
+ };
8253
8474
  }
8254
8475
  }
8255
8476
 
@@ -8723,11 +8944,13 @@ const TO_RISK_SIGNAL = (signal, currentPrice) => {
8723
8944
  : 0;
8724
8945
  return {
8725
8946
  ...structuredClone(signal),
8947
+ totalEntries: 1,
8726
8948
  priceOpen: signal.priceOpen ?? currentPrice,
8727
8949
  priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
8728
8950
  priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
8729
8951
  originalPriceStopLoss: signal.priceStopLoss,
8730
8952
  originalPriceTakeProfit: signal.priceTakeProfit,
8953
+ originalPriceOpen: signal.priceOpen ?? currentPrice,
8731
8954
  partialExecuted,
8732
8955
  };
8733
8956
  };
@@ -11595,6 +11818,28 @@ class StrategyCoreService {
11595
11818
  await this.validate(context);
11596
11819
  return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
11597
11820
  };
11821
+ /**
11822
+ * Adds a new DCA entry to the active pending signal.
11823
+ *
11824
+ * Validates strategy existence and delegates to connection service
11825
+ * to add a new averaging entry to the position.
11826
+ *
11827
+ * @param backtest - Whether running in backtest mode
11828
+ * @param symbol - Trading pair symbol
11829
+ * @param currentPrice - New entry price to add to the averaging history
11830
+ * @param context - Execution context with strategyName, exchangeName, frameName
11831
+ * @returns Promise<boolean> - true if entry added, false if rejected
11832
+ */
11833
+ this.averageBuy = async (backtest, symbol, currentPrice, context) => {
11834
+ this.loggerService.log("strategyCoreService averageBuy", {
11835
+ symbol,
11836
+ currentPrice,
11837
+ context,
11838
+ backtest,
11839
+ });
11840
+ await this.validate(context);
11841
+ return await this.strategyConnectionService.averageBuy(backtest, symbol, currentPrice, context);
11842
+ };
11598
11843
  }
11599
11844
  }
11600
11845
 
@@ -14142,6 +14387,18 @@ const backtest_columns = [
14142
14387
  format: (data) => `${data.signal.originalPriceStopLoss.toFixed(8)} USD`,
14143
14388
  isVisible: () => true,
14144
14389
  },
14390
+ {
14391
+ key: "originalPriceOpen",
14392
+ label: "Original Entry",
14393
+ format: (data) => `${data.signal.originalPriceOpen.toFixed(8)} USD`,
14394
+ isVisible: () => true,
14395
+ },
14396
+ {
14397
+ key: "totalEntries",
14398
+ label: "DCA Entries",
14399
+ format: (data) => String(data.signal.totalEntries),
14400
+ isVisible: () => true,
14401
+ },
14145
14402
  {
14146
14403
  key: "pnl",
14147
14404
  label: "PNL (net)",
@@ -14429,6 +14686,20 @@ const live_columns = [
14429
14686
  : "N/A",
14430
14687
  isVisible: () => true,
14431
14688
  },
14689
+ {
14690
+ key: "originalPriceOpen",
14691
+ label: "Original Entry",
14692
+ format: (data) => data.originalPriceOpen !== undefined
14693
+ ? `${data.originalPriceOpen.toFixed(8)} USD`
14694
+ : "N/A",
14695
+ isVisible: () => true,
14696
+ },
14697
+ {
14698
+ key: "totalEntries",
14699
+ label: "DCA Entries",
14700
+ format: (data) => data.totalEntries !== undefined ? String(data.totalEntries) : "N/A",
14701
+ isVisible: () => true,
14702
+ },
14432
14703
  {
14433
14704
  key: "partialExecuted",
14434
14705
  label: "Partial Executed %",
@@ -14593,6 +14864,18 @@ const partial_columns = [
14593
14864
  format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
14594
14865
  isVisible: () => true,
14595
14866
  },
14867
+ {
14868
+ key: "originalPriceOpen",
14869
+ label: "Original Entry",
14870
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
14871
+ isVisible: () => true,
14872
+ },
14873
+ {
14874
+ key: "totalEntries",
14875
+ label: "DCA Entries",
14876
+ format: (data) => (data.totalEntries !== undefined ? String(data.totalEntries) : "N/A"),
14877
+ isVisible: () => true,
14878
+ },
14596
14879
  {
14597
14880
  key: "partialExecuted",
14598
14881
  label: "Partial Executed %",
@@ -14727,6 +15010,18 @@ const breakeven_columns = [
14727
15010
  format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
14728
15011
  isVisible: () => true,
14729
15012
  },
15013
+ {
15014
+ key: "originalPriceOpen",
15015
+ label: "Original Entry",
15016
+ format: (data) => (data.originalPriceOpen ? `${data.originalPriceOpen.toFixed(8)} USD` : "N/A"),
15017
+ isVisible: () => true,
15018
+ },
15019
+ {
15020
+ key: "totalEntries",
15021
+ label: "DCA Entries",
15022
+ format: (data) => (data.totalEntries !== undefined ? String(data.totalEntries) : "N/A"),
15023
+ isVisible: () => true,
15024
+ },
14730
15025
  {
14731
15026
  key: "partialExecuted",
14732
15027
  label: "Partial Executed %",
@@ -14997,6 +15292,22 @@ const risk_columns = [
14997
15292
  : "N/A",
14998
15293
  isVisible: () => true,
14999
15294
  },
15295
+ {
15296
+ key: "originalPriceOpen",
15297
+ label: "Original Entry",
15298
+ format: (data) => data.currentSignal.originalPriceOpen !== undefined
15299
+ ? `${data.currentSignal.originalPriceOpen.toFixed(8)} USD`
15300
+ : "N/A",
15301
+ isVisible: () => true,
15302
+ },
15303
+ {
15304
+ key: "totalEntries",
15305
+ label: "DCA Entries",
15306
+ format: (data) => data.currentSignal.totalEntries !== undefined
15307
+ ? String(data.currentSignal.totalEntries)
15308
+ : "N/A",
15309
+ isVisible: () => true,
15310
+ },
15000
15311
  {
15001
15312
  key: "partialExecuted",
15002
15313
  label: "Partial Executed %",
@@ -15171,6 +15482,20 @@ const schedule_columns = [
15171
15482
  : "N/A",
15172
15483
  isVisible: () => true,
15173
15484
  },
15485
+ {
15486
+ key: "originalPriceOpen",
15487
+ label: "Original Entry",
15488
+ format: (data) => data.originalPriceOpen !== undefined
15489
+ ? `${data.originalPriceOpen.toFixed(8)} USD`
15490
+ : "N/A",
15491
+ isVisible: () => true,
15492
+ },
15493
+ {
15494
+ key: "totalEntries",
15495
+ label: "DCA Entries",
15496
+ format: (data) => data.totalEntries !== undefined ? String(data.totalEntries) : "N/A",
15497
+ isVisible: () => true,
15498
+ },
15174
15499
  {
15175
15500
  key: "partialExecuted",
15176
15501
  label: "Partial Executed %",
@@ -17260,6 +17585,8 @@ let ReportStorage$5 = class ReportStorage {
17260
17585
  priceStopLoss: data.signal.priceStopLoss,
17261
17586
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17262
17587
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17588
+ totalEntries: data.signal.totalEntries,
17589
+ originalPriceOpen: data.signal.originalPriceOpen,
17263
17590
  partialExecuted: data.signal.partialExecuted,
17264
17591
  scheduledAt: data.signal.scheduledAt,
17265
17592
  });
@@ -17289,6 +17616,8 @@ let ReportStorage$5 = class ReportStorage {
17289
17616
  priceStopLoss: data.signal.priceStopLoss,
17290
17617
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17291
17618
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17619
+ totalEntries: data.signal.totalEntries,
17620
+ originalPriceOpen: data.signal.originalPriceOpen,
17292
17621
  partialExecuted: data.signal.partialExecuted,
17293
17622
  duration: durationMin,
17294
17623
  pendingAt: data.signal.pendingAt,
@@ -17321,6 +17650,8 @@ let ReportStorage$5 = class ReportStorage {
17321
17650
  priceStopLoss: data.signal.priceStopLoss,
17322
17651
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
17323
17652
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
17653
+ totalEntries: data.signal.totalEntries,
17654
+ originalPriceOpen: data.signal.originalPriceOpen,
17324
17655
  partialExecuted: data.signal.partialExecuted,
17325
17656
  closeTimestamp: data.closeTimestamp,
17326
17657
  duration: durationMin,
@@ -20466,6 +20797,8 @@ let ReportStorage$3 = class ReportStorage {
20466
20797
  priceStopLoss: data.priceStopLoss,
20467
20798
  originalPriceTakeProfit: data.originalPriceTakeProfit,
20468
20799
  originalPriceStopLoss: data.originalPriceStopLoss,
20800
+ totalEntries: data.totalEntries,
20801
+ originalPriceOpen: data.originalPriceOpen,
20469
20802
  partialExecuted: data.partialExecuted,
20470
20803
  note: data.note,
20471
20804
  pendingAt: data.pendingAt,
@@ -20500,6 +20833,8 @@ let ReportStorage$3 = class ReportStorage {
20500
20833
  priceStopLoss: data.priceStopLoss,
20501
20834
  originalPriceTakeProfit: data.originalPriceTakeProfit,
20502
20835
  originalPriceStopLoss: data.originalPriceStopLoss,
20836
+ totalEntries: data.totalEntries,
20837
+ originalPriceOpen: data.originalPriceOpen,
20503
20838
  partialExecuted: data.partialExecuted,
20504
20839
  note: data.note,
20505
20840
  pendingAt: data.pendingAt,
@@ -21599,6 +21934,8 @@ let ReportStorage$2 = class ReportStorage {
21599
21934
  priceStopLoss: data.priceStopLoss,
21600
21935
  originalPriceTakeProfit: data.originalPriceTakeProfit,
21601
21936
  originalPriceStopLoss: data.originalPriceStopLoss,
21937
+ totalEntries: data.totalEntries,
21938
+ originalPriceOpen: data.originalPriceOpen,
21602
21939
  partialExecuted: data.partialExecuted,
21603
21940
  note: data.note,
21604
21941
  pendingAt: data.pendingAt,
@@ -23602,6 +23939,8 @@ class ScheduleReportService {
23602
23939
  priceStopLoss: data.signal?.priceStopLoss,
23603
23940
  originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
23604
23941
  originalPriceStopLoss: data.signal?.originalPriceStopLoss,
23942
+ totalEntries: data.signal?.totalEntries,
23943
+ originalPriceOpen: data.signal?.originalPriceOpen,
23605
23944
  partialExecuted: data.signal?.partialExecuted,
23606
23945
  pendingAt: data.signal?.pendingAt,
23607
23946
  minuteEstimatedTime: data.signal?.minuteEstimatedTime,
@@ -23646,6 +23985,8 @@ class ScheduleReportService {
23646
23985
  priceStopLoss: data.signal?.priceStopLoss,
23647
23986
  originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
23648
23987
  originalPriceStopLoss: data.signal?.originalPriceStopLoss,
23988
+ totalEntries: data.signal?.totalEntries,
23989
+ originalPriceOpen: data.signal?.originalPriceOpen,
23649
23990
  partialExecuted: data.signal?.partialExecuted,
23650
23991
  scheduledAt: data.signal?.scheduledAt,
23651
23992
  pendingAt: data.signal?.pendingAt,
@@ -24123,6 +24464,8 @@ class PartialReportService {
24123
24464
  priceStopLoss: data.data.priceStopLoss,
24124
24465
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24125
24466
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24467
+ totalEntries: data.data.totalEntries,
24468
+ originalPriceOpen: data.data.originalPriceOpen,
24126
24469
  partialExecuted: data.data.partialExecuted,
24127
24470
  _partial: data.data._partial,
24128
24471
  note: data.data.note,
@@ -24164,6 +24507,8 @@ class PartialReportService {
24164
24507
  priceStopLoss: data.data.priceStopLoss,
24165
24508
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24166
24509
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24510
+ totalEntries: data.data.totalEntries,
24511
+ originalPriceOpen: data.data.originalPriceOpen,
24167
24512
  partialExecuted: data.data.partialExecuted,
24168
24513
  _partial: data.data._partial,
24169
24514
  note: data.data.note,
@@ -24286,6 +24631,8 @@ class BreakevenReportService {
24286
24631
  priceStopLoss: data.data.priceStopLoss,
24287
24632
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
24288
24633
  originalPriceStopLoss: data.data.originalPriceStopLoss,
24634
+ totalEntries: data.data.totalEntries,
24635
+ originalPriceOpen: data.data.originalPriceOpen,
24289
24636
  partialExecuted: data.data.partialExecuted,
24290
24637
  _partial: data.data._partial,
24291
24638
  note: data.data.note,
@@ -24594,7 +24941,7 @@ class StrategyReportService {
24594
24941
  * @param scheduledAt - Signal creation timestamp in milliseconds
24595
24942
  * @param pendingAt - Pending timestamp in milliseconds
24596
24943
  */
24597
- this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
24944
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24598
24945
  this.loggerService.log("strategyReportService partialProfit", {
24599
24946
  symbol,
24600
24947
  percentToClose,
@@ -24626,6 +24973,8 @@ class StrategyReportService {
24626
24973
  priceStopLoss,
24627
24974
  originalPriceTakeProfit,
24628
24975
  originalPriceStopLoss,
24976
+ originalPriceOpen,
24977
+ totalEntries,
24629
24978
  scheduledAt,
24630
24979
  pendingAt,
24631
24980
  }, {
@@ -24655,7 +25004,7 @@ class StrategyReportService {
24655
25004
  * @param scheduledAt - Signal creation timestamp in milliseconds
24656
25005
  * @param pendingAt - Pending timestamp in milliseconds
24657
25006
  */
24658
- this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25007
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24659
25008
  this.loggerService.log("strategyReportService partialLoss", {
24660
25009
  symbol,
24661
25010
  percentToClose,
@@ -24687,6 +25036,8 @@ class StrategyReportService {
24687
25036
  priceStopLoss,
24688
25037
  originalPriceTakeProfit,
24689
25038
  originalPriceStopLoss,
25039
+ originalPriceOpen,
25040
+ totalEntries,
24690
25041
  scheduledAt,
24691
25042
  pendingAt,
24692
25043
  }, {
@@ -24716,7 +25067,7 @@ class StrategyReportService {
24716
25067
  * @param scheduledAt - Signal creation timestamp in milliseconds
24717
25068
  * @param pendingAt - Pending timestamp in milliseconds
24718
25069
  */
24719
- this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25070
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24720
25071
  this.loggerService.log("strategyReportService trailingStop", {
24721
25072
  symbol,
24722
25073
  percentShift,
@@ -24748,6 +25099,8 @@ class StrategyReportService {
24748
25099
  priceStopLoss,
24749
25100
  originalPriceTakeProfit,
24750
25101
  originalPriceStopLoss,
25102
+ originalPriceOpen,
25103
+ totalEntries,
24751
25104
  scheduledAt,
24752
25105
  pendingAt,
24753
25106
  }, {
@@ -24777,7 +25130,7 @@ class StrategyReportService {
24777
25130
  * @param scheduledAt - Signal creation timestamp in milliseconds
24778
25131
  * @param pendingAt - Pending timestamp in milliseconds
24779
25132
  */
24780
- this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25133
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24781
25134
  this.loggerService.log("strategyReportService trailingTake", {
24782
25135
  symbol,
24783
25136
  percentShift,
@@ -24809,6 +25162,8 @@ class StrategyReportService {
24809
25162
  priceStopLoss,
24810
25163
  originalPriceTakeProfit,
24811
25164
  originalPriceStopLoss,
25165
+ originalPriceOpen,
25166
+ totalEntries,
24812
25167
  scheduledAt,
24813
25168
  pendingAt,
24814
25169
  }, {
@@ -24837,7 +25192,7 @@ class StrategyReportService {
24837
25192
  * @param scheduledAt - Signal creation timestamp in milliseconds
24838
25193
  * @param pendingAt - Pending timestamp in milliseconds
24839
25194
  */
24840
- this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25195
+ this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
24841
25196
  this.loggerService.log("strategyReportService breakeven", {
24842
25197
  symbol,
24843
25198
  currentPrice,
@@ -24867,6 +25222,8 @@ class StrategyReportService {
24867
25222
  priceStopLoss,
24868
25223
  originalPriceTakeProfit,
24869
25224
  originalPriceStopLoss,
25225
+ originalPriceOpen,
25226
+ totalEntries,
24870
25227
  scheduledAt,
24871
25228
  pendingAt,
24872
25229
  }, {
@@ -24896,7 +25253,7 @@ class StrategyReportService {
24896
25253
  * @param pendingAt - Pending timestamp in milliseconds
24897
25254
  * @param activateId - Optional identifier for the activation reason
24898
25255
  */
24899
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
25256
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
24900
25257
  this.loggerService.log("strategyReportService activateScheduled", {
24901
25258
  symbol,
24902
25259
  currentPrice,
@@ -24928,6 +25285,8 @@ class StrategyReportService {
24928
25285
  priceStopLoss,
24929
25286
  originalPriceTakeProfit,
24930
25287
  originalPriceStopLoss,
25288
+ originalPriceOpen,
25289
+ totalEntries,
24931
25290
  scheduledAt,
24932
25291
  pendingAt,
24933
25292
  }, {
@@ -24939,6 +25298,71 @@ class StrategyReportService {
24939
25298
  walkerName: "",
24940
25299
  });
24941
25300
  };
25301
+ /**
25302
+ * Logs an average-buy (DCA) event when a new averaging entry is added to an open position.
25303
+ *
25304
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
25305
+ * @param currentPrice - Price at which the new averaging entry was executed
25306
+ * @param effectivePriceOpen - Averaged entry price after this addition
25307
+ * @param totalEntries - Total number of DCA entries after this addition
25308
+ * @param isBacktest - Whether this is a backtest or live trading event
25309
+ * @param context - Strategy context with strategyName, exchangeName, frameName
25310
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
25311
+ * @param position - Trade direction: "long" or "short"
25312
+ * @param priceOpen - Original entry price (unchanged by averaging)
25313
+ * @param priceTakeProfit - Effective take profit price
25314
+ * @param priceStopLoss - Effective stop loss price
25315
+ * @param originalPriceTakeProfit - Original take profit before trailing
25316
+ * @param originalPriceStopLoss - Original stop loss before trailing
25317
+ * @param scheduledAt - Signal creation timestamp in milliseconds
25318
+ * @param pendingAt - Pending timestamp in milliseconds
25319
+ */
25320
+ this.averageBuy = async (symbol, currentPrice, effectivePriceOpen, totalEntries, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, originalPriceOpen) => {
25321
+ this.loggerService.log("strategyReportService averageBuy", {
25322
+ symbol,
25323
+ currentPrice,
25324
+ effectivePriceOpen,
25325
+ totalEntries,
25326
+ isBacktest,
25327
+ });
25328
+ if (!this.subscribe.hasValue()) {
25329
+ return;
25330
+ }
25331
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
25332
+ exchangeName: context.exchangeName,
25333
+ strategyName: context.strategyName,
25334
+ frameName: context.frameName,
25335
+ });
25336
+ if (!pendingRow) {
25337
+ return;
25338
+ }
25339
+ const createdAt = new Date(timestamp).toISOString();
25340
+ await Report.writeData("strategy", {
25341
+ action: "average-buy",
25342
+ currentPrice,
25343
+ effectivePriceOpen,
25344
+ totalEntries,
25345
+ symbol,
25346
+ timestamp,
25347
+ createdAt,
25348
+ position,
25349
+ priceOpen,
25350
+ priceTakeProfit,
25351
+ priceStopLoss,
25352
+ originalPriceTakeProfit,
25353
+ originalPriceStopLoss,
25354
+ originalPriceOpen,
25355
+ scheduledAt,
25356
+ pendingAt,
25357
+ }, {
25358
+ signalId: pendingRow.id,
25359
+ exchangeName: context.exchangeName,
25360
+ frameName: context.frameName,
25361
+ strategyName: context.strategyName,
25362
+ symbol,
25363
+ walkerName: "",
25364
+ });
25365
+ };
24942
25366
  /**
24943
25367
  * Initializes the service for event logging.
24944
25368
  *
@@ -24969,43 +25393,50 @@ class StrategyReportService {
24969
25393
  exchangeName: event.exchangeName,
24970
25394
  frameName: event.frameName,
24971
25395
  strategyName: event.strategyName,
24972
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25396
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24973
25397
  const unPartialLoss = strategyCommitSubject
24974
25398
  .filter(({ action }) => action === "partial-loss")
24975
25399
  .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
24976
25400
  exchangeName: event.exchangeName,
24977
25401
  frameName: event.frameName,
24978
25402
  strategyName: event.strategyName,
24979
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25403
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24980
25404
  const unTrailingStop = strategyCommitSubject
24981
25405
  .filter(({ action }) => action === "trailing-stop")
24982
25406
  .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24983
25407
  exchangeName: event.exchangeName,
24984
25408
  frameName: event.frameName,
24985
25409
  strategyName: event.strategyName,
24986
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25410
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24987
25411
  const unTrailingTake = strategyCommitSubject
24988
25412
  .filter(({ action }) => action === "trailing-take")
24989
25413
  .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
24990
25414
  exchangeName: event.exchangeName,
24991
25415
  frameName: event.frameName,
24992
25416
  strategyName: event.strategyName,
24993
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25417
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
24994
25418
  const unBreakeven = strategyCommitSubject
24995
25419
  .filter(({ action }) => action === "breakeven")
24996
25420
  .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
24997
25421
  exchangeName: event.exchangeName,
24998
25422
  frameName: event.frameName,
24999
25423
  strategyName: event.strategyName,
25000
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
25424
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25001
25425
  const unActivateScheduled = strategyCommitSubject
25002
25426
  .filter(({ action }) => action === "activate-scheduled")
25003
25427
  .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
25004
25428
  exchangeName: event.exchangeName,
25005
25429
  frameName: event.frameName,
25006
25430
  strategyName: event.strategyName,
25007
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
25008
- const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
25431
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
25432
+ const unAverageBuy = strategyCommitSubject
25433
+ .filter(({ action }) => action === "average-buy")
25434
+ .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
25435
+ exchangeName: event.exchangeName,
25436
+ frameName: event.frameName,
25437
+ strategyName: event.strategyName,
25438
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.originalPriceOpen));
25439
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled(), () => unAverageBuy());
25009
25440
  return () => {
25010
25441
  disposeFn();
25011
25442
  this.subscribe.clear();
@@ -25137,6 +25568,7 @@ class ReportStorage {
25137
25568
  trailingTakeCount: 0,
25138
25569
  breakevenCount: 0,
25139
25570
  activateScheduledCount: 0,
25571
+ averageBuyCount: 0,
25140
25572
  };
25141
25573
  }
25142
25574
  return {
@@ -25150,6 +25582,7 @@ class ReportStorage {
25150
25582
  trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
25151
25583
  breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
25152
25584
  activateScheduledCount: this._eventList.filter(e => e.action === "activate-scheduled").length,
25585
+ averageBuyCount: this._eventList.filter(e => e.action === "average-buy").length,
25153
25586
  };
25154
25587
  }
25155
25588
  /**
@@ -25199,6 +25632,7 @@ class ReportStorage {
25199
25632
  `- Trailing take: ${stats.trailingTakeCount}`,
25200
25633
  `- Breakeven: ${stats.breakevenCount}`,
25201
25634
  `- Activate scheduled: ${stats.activateScheduledCount}`,
25635
+ `- Average buy: ${stats.averageBuyCount}`,
25202
25636
  ].join("\n");
25203
25637
  }
25204
25638
  /**
@@ -25386,7 +25820,7 @@ class StrategyMarkdownService {
25386
25820
  * @param scheduledAt - Signal creation timestamp in milliseconds
25387
25821
  * @param pendingAt - Pending timestamp in milliseconds
25388
25822
  */
25389
- this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25823
+ this.partialProfit = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25390
25824
  this.loggerService.log("strategyMarkdownService partialProfit", {
25391
25825
  symbol,
25392
25826
  percentToClose,
@@ -25424,6 +25858,8 @@ class StrategyMarkdownService {
25424
25858
  priceStopLoss,
25425
25859
  originalPriceTakeProfit,
25426
25860
  originalPriceStopLoss,
25861
+ originalPriceOpen,
25862
+ totalEntries,
25427
25863
  scheduledAt,
25428
25864
  pendingAt,
25429
25865
  });
@@ -25446,7 +25882,7 @@ class StrategyMarkdownService {
25446
25882
  * @param scheduledAt - Signal creation timestamp in milliseconds
25447
25883
  * @param pendingAt - Pending timestamp in milliseconds
25448
25884
  */
25449
- this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25885
+ this.partialLoss = async (symbol, percentToClose, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25450
25886
  this.loggerService.log("strategyMarkdownService partialLoss", {
25451
25887
  symbol,
25452
25888
  percentToClose,
@@ -25484,6 +25920,8 @@ class StrategyMarkdownService {
25484
25920
  priceStopLoss,
25485
25921
  originalPriceTakeProfit,
25486
25922
  originalPriceStopLoss,
25923
+ originalPriceOpen,
25924
+ totalEntries,
25487
25925
  scheduledAt,
25488
25926
  pendingAt,
25489
25927
  });
@@ -25506,7 +25944,7 @@ class StrategyMarkdownService {
25506
25944
  * @param scheduledAt - Signal creation timestamp in milliseconds
25507
25945
  * @param pendingAt - Pending timestamp in milliseconds
25508
25946
  */
25509
- this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
25947
+ this.trailingStop = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25510
25948
  this.loggerService.log("strategyMarkdownService trailingStop", {
25511
25949
  symbol,
25512
25950
  percentShift,
@@ -25544,6 +25982,8 @@ class StrategyMarkdownService {
25544
25982
  priceStopLoss,
25545
25983
  originalPriceTakeProfit,
25546
25984
  originalPriceStopLoss,
25985
+ originalPriceOpen,
25986
+ totalEntries,
25547
25987
  scheduledAt,
25548
25988
  pendingAt,
25549
25989
  });
@@ -25566,7 +26006,7 @@ class StrategyMarkdownService {
25566
26006
  * @param scheduledAt - Signal creation timestamp in milliseconds
25567
26007
  * @param pendingAt - Pending timestamp in milliseconds
25568
26008
  */
25569
- this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
26009
+ this.trailingTake = async (symbol, percentShift, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25570
26010
  this.loggerService.log("strategyMarkdownService trailingTake", {
25571
26011
  symbol,
25572
26012
  percentShift,
@@ -25604,6 +26044,8 @@ class StrategyMarkdownService {
25604
26044
  priceStopLoss,
25605
26045
  originalPriceTakeProfit,
25606
26046
  originalPriceStopLoss,
26047
+ originalPriceOpen,
26048
+ totalEntries,
25607
26049
  scheduledAt,
25608
26050
  pendingAt,
25609
26051
  });
@@ -25625,7 +26067,7 @@ class StrategyMarkdownService {
25625
26067
  * @param scheduledAt - Signal creation timestamp in milliseconds
25626
26068
  * @param pendingAt - Pending timestamp in milliseconds
25627
26069
  */
25628
- this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt) => {
26070
+ this.breakeven = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen) => {
25629
26071
  this.loggerService.log("strategyMarkdownService breakeven", {
25630
26072
  symbol,
25631
26073
  currentPrice,
@@ -25661,6 +26103,8 @@ class StrategyMarkdownService {
25661
26103
  priceStopLoss,
25662
26104
  originalPriceTakeProfit,
25663
26105
  originalPriceStopLoss,
26106
+ originalPriceOpen,
26107
+ totalEntries,
25664
26108
  scheduledAt,
25665
26109
  pendingAt,
25666
26110
  });
@@ -25683,7 +26127,7 @@ class StrategyMarkdownService {
25683
26127
  * @param pendingAt - Pending timestamp in milliseconds
25684
26128
  * @param activateId - Optional identifier for the activation reason
25685
26129
  */
25686
- this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, activateId) => {
26130
+ this.activateScheduled = async (symbol, currentPrice, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, totalEntries, originalPriceOpen, activateId) => {
25687
26131
  this.loggerService.log("strategyMarkdownService activateScheduled", {
25688
26132
  symbol,
25689
26133
  currentPrice,
@@ -25721,6 +26165,72 @@ class StrategyMarkdownService {
25721
26165
  priceStopLoss,
25722
26166
  originalPriceTakeProfit,
25723
26167
  originalPriceStopLoss,
26168
+ originalPriceOpen,
26169
+ totalEntries,
26170
+ scheduledAt,
26171
+ pendingAt,
26172
+ });
26173
+ };
26174
+ /**
26175
+ * Records an average-buy (DCA) event when a new averaging entry is added to an open position.
26176
+ *
26177
+ * @param symbol - Trading pair symbol (e.g., "BTCUSDT")
26178
+ * @param currentPrice - Price at which the new averaging entry was executed
26179
+ * @param effectivePriceOpen - Averaged entry price after this addition
26180
+ * @param totalEntries - Total number of DCA entries after this addition
26181
+ * @param isBacktest - Whether this is a backtest or live trading event
26182
+ * @param context - Strategy context with strategyName, exchangeName, frameName
26183
+ * @param timestamp - Timestamp from StrategyCommitContract (execution context time)
26184
+ * @param position - Trade direction: "long" or "short"
26185
+ * @param priceOpen - Original entry price (unchanged by averaging)
26186
+ * @param priceTakeProfit - Effective take profit price
26187
+ * @param priceStopLoss - Effective stop loss price
26188
+ * @param originalPriceTakeProfit - Original take profit before trailing
26189
+ * @param originalPriceStopLoss - Original stop loss before trailing
26190
+ * @param scheduledAt - Signal creation timestamp in milliseconds
26191
+ * @param pendingAt - Pending timestamp in milliseconds
26192
+ */
26193
+ this.averageBuy = async (symbol, currentPrice, effectivePriceOpen, totalEntries, isBacktest, context, timestamp, position, priceOpen, priceTakeProfit, priceStopLoss, originalPriceTakeProfit, originalPriceStopLoss, scheduledAt, pendingAt, originalPriceOpen) => {
26194
+ this.loggerService.log("strategyMarkdownService averageBuy", {
26195
+ symbol,
26196
+ currentPrice,
26197
+ effectivePriceOpen,
26198
+ totalEntries,
26199
+ isBacktest,
26200
+ });
26201
+ if (!this.subscribe.hasValue()) {
26202
+ return;
26203
+ }
26204
+ const pendingRow = await this.strategyCoreService.getPendingSignal(isBacktest, symbol, {
26205
+ exchangeName: context.exchangeName,
26206
+ strategyName: context.strategyName,
26207
+ frameName: context.frameName,
26208
+ });
26209
+ if (!pendingRow) {
26210
+ return;
26211
+ }
26212
+ const createdAt = new Date(timestamp).toISOString();
26213
+ const storage = this.getStorage(symbol, context.strategyName, context.exchangeName, context.frameName, isBacktest);
26214
+ storage.addEvent({
26215
+ timestamp,
26216
+ symbol,
26217
+ strategyName: context.strategyName,
26218
+ exchangeName: context.exchangeName,
26219
+ frameName: context.frameName,
26220
+ signalId: pendingRow.id,
26221
+ action: "average-buy",
26222
+ currentPrice,
26223
+ effectivePriceOpen,
26224
+ totalEntries,
26225
+ createdAt,
26226
+ backtest: isBacktest,
26227
+ position,
26228
+ priceOpen,
26229
+ priceTakeProfit,
26230
+ priceStopLoss,
26231
+ originalPriceTakeProfit,
26232
+ originalPriceStopLoss,
26233
+ originalPriceOpen,
25724
26234
  scheduledAt,
25725
26235
  pendingAt,
25726
26236
  });
@@ -25869,43 +26379,50 @@ class StrategyMarkdownService {
25869
26379
  exchangeName: event.exchangeName,
25870
26380
  frameName: event.frameName,
25871
26381
  strategyName: event.strategyName,
25872
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26382
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25873
26383
  const unPartialLoss = strategyCommitSubject
25874
26384
  .filter(({ action }) => action === "partial-loss")
25875
26385
  .connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
25876
26386
  exchangeName: event.exchangeName,
25877
26387
  frameName: event.frameName,
25878
26388
  strategyName: event.strategyName,
25879
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26389
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25880
26390
  const unTrailingStop = strategyCommitSubject
25881
26391
  .filter(({ action }) => action === "trailing-stop")
25882
26392
  .connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
25883
26393
  exchangeName: event.exchangeName,
25884
26394
  frameName: event.frameName,
25885
26395
  strategyName: event.strategyName,
25886
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26396
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25887
26397
  const unTrailingTake = strategyCommitSubject
25888
26398
  .filter(({ action }) => action === "trailing-take")
25889
26399
  .connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
25890
26400
  exchangeName: event.exchangeName,
25891
26401
  frameName: event.frameName,
25892
26402
  strategyName: event.strategyName,
25893
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26403
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25894
26404
  const unBreakeven = strategyCommitSubject
25895
26405
  .filter(({ action }) => action === "breakeven")
25896
26406
  .connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
25897
26407
  exchangeName: event.exchangeName,
25898
26408
  frameName: event.frameName,
25899
26409
  strategyName: event.strategyName,
25900
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt));
26410
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen));
25901
26411
  const unActivateScheduled = strategyCommitSubject
25902
26412
  .filter(({ action }) => action === "activate-scheduled")
25903
26413
  .connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
25904
26414
  exchangeName: event.exchangeName,
25905
26415
  frameName: event.frameName,
25906
26416
  strategyName: event.strategyName,
25907
- }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
25908
- const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled());
26417
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.totalEntries, event.originalPriceOpen, event.activateId));
26418
+ const unAverageBuy = strategyCommitSubject
26419
+ .filter(({ action }) => action === "average-buy")
26420
+ .connect(async (event) => await this.averageBuy(event.symbol, event.currentPrice, event.effectivePriceOpen, event.totalEntries, event.backtest, {
26421
+ exchangeName: event.exchangeName,
26422
+ frameName: event.frameName,
26423
+ strategyName: event.strategyName,
26424
+ }, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.originalPriceOpen));
26425
+ const disposeFn = functoolsKit.compose(() => unCancelSchedule(), () => unClosePending(), () => unPartialProfit(), () => unPartialLoss(), () => unTrailingStop(), () => unTrailingTake(), () => unBreakeven(), () => unActivateScheduled(), () => unAverageBuy());
25909
26426
  return () => {
25910
26427
  disposeFn();
25911
26428
  this.subscribe.clear();
@@ -27711,6 +28228,7 @@ const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
27711
28228
  const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
27712
28229
  const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
27713
28230
  const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
28231
+ const AVERAGE_BUY_METHOD_NAME = "strategy.commitAverageBuy";
27714
28232
  /**
27715
28233
  * Cancels the scheduled signal without stopping the strategy.
27716
28234
  *
@@ -28063,6 +28581,45 @@ async function commitActivateScheduled(symbol, activateId) {
28063
28581
  const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
28064
28582
  await bt.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
28065
28583
  }
28584
+ /**
28585
+ * Adds a new DCA entry to the active pending signal.
28586
+ *
28587
+ * Adds a new averaging entry at the current market price to the position's
28588
+ * entry history. Updates effectivePriceOpen (mean of all entries) and emits
28589
+ * an average-buy commit event.
28590
+ *
28591
+ * Automatically detects backtest/live mode from execution context.
28592
+ * Automatically fetches current price via getAveragePrice.
28593
+ *
28594
+ * @param symbol - Trading pair symbol
28595
+ * @returns Promise<boolean> - true if entry added, false if rejected
28596
+ *
28597
+ * @example
28598
+ * ```typescript
28599
+ * import { commitAverageBuy } from "backtest-kit";
28600
+ *
28601
+ * // Add DCA entry at current market price
28602
+ * const success = await commitAverageBuy("BTCUSDT");
28603
+ * if (success) {
28604
+ * console.log("DCA entry added");
28605
+ * }
28606
+ * ```
28607
+ */
28608
+ async function commitAverageBuy(symbol) {
28609
+ bt.loggerService.info(AVERAGE_BUY_METHOD_NAME, {
28610
+ symbol,
28611
+ });
28612
+ if (!ExecutionContextService.hasContext()) {
28613
+ throw new Error("commitAverageBuy requires an execution context");
28614
+ }
28615
+ if (!MethodContextService.hasContext()) {
28616
+ throw new Error("commitAverageBuy requires a method context");
28617
+ }
28618
+ const currentPrice = await getAveragePrice(symbol);
28619
+ const { backtest: isBacktest } = bt.executionContextService.context;
28620
+ const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
28621
+ return await bt.strategyCoreService.averageBuy(isBacktest, symbol, currentPrice, { exchangeName, frameName, strategyName });
28622
+ }
28066
28623
 
28067
28624
  const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
28068
28625
  /**
@@ -30343,6 +30900,7 @@ const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
30343
30900
  const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
30344
30901
  const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
30345
30902
  const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
30903
+ const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
30346
30904
  const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
30347
30905
  /**
30348
30906
  * Internal task function that runs backtest and handles completion.
@@ -31239,6 +31797,49 @@ class BacktestUtils {
31239
31797
  }
31240
31798
  await bt.strategyCoreService.activateScheduled(true, symbol, context, activateId);
31241
31799
  };
31800
+ /**
31801
+ * Adds a new DCA entry to the active pending signal.
31802
+ *
31803
+ * Adds a new averaging entry at currentPrice to the position's entry history.
31804
+ * Updates effectivePriceOpen (mean of all entries) and emits average-buy commit event.
31805
+ *
31806
+ * @param symbol - Trading pair symbol
31807
+ * @param currentPrice - New entry price to add to the averaging history
31808
+ * @param context - Execution context with strategyName, exchangeName, frameName
31809
+ * @returns Promise<boolean> - true if entry added, false if rejected
31810
+ *
31811
+ * @example
31812
+ * ```typescript
31813
+ * // Add DCA entry at current price
31814
+ * const success = await Backtest.commitAverageBuy("BTCUSDT", 42000, {
31815
+ * strategyName: "my-strategy",
31816
+ * exchangeName: "binance",
31817
+ * frameName: "1h"
31818
+ * });
31819
+ * if (success) {
31820
+ * console.log('DCA entry added');
31821
+ * }
31822
+ * ```
31823
+ */
31824
+ this.commitAverageBuy = async (symbol, currentPrice, context) => {
31825
+ bt.loggerService.info(BACKTEST_METHOD_NAME_AVERAGE_BUY, {
31826
+ symbol,
31827
+ currentPrice,
31828
+ context,
31829
+ });
31830
+ bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31831
+ bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31832
+ {
31833
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
31834
+ riskName &&
31835
+ bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_AVERAGE_BUY);
31836
+ riskList &&
31837
+ riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
31838
+ actions &&
31839
+ actions.forEach((actionName) => bt.actionValidationService.validate(actionName, BACKTEST_METHOD_NAME_AVERAGE_BUY));
31840
+ }
31841
+ return await bt.strategyCoreService.averageBuy(true, symbol, currentPrice, context);
31842
+ };
31242
31843
  /**
31243
31844
  * Gets statistical data from all closed signals for a symbol-strategy pair.
31244
31845
  *
@@ -31415,6 +32016,7 @@ const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
31415
32016
  const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
31416
32017
  const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
31417
32018
  const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
32019
+ const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
31418
32020
  /**
31419
32021
  * Internal task function that runs live trading and handles completion.
31420
32022
  * Consumes live trading results and updates instance state flags.
@@ -32279,6 +32881,49 @@ class LiveUtils {
32279
32881
  frameName: "",
32280
32882
  }, activateId);
32281
32883
  };
32884
+ /**
32885
+ * Adds a new DCA entry to the active pending signal.
32886
+ *
32887
+ * Adds a new averaging entry at currentPrice to the position's entry history.
32888
+ * Updates effectivePriceOpen (mean of all entries) and emits average-buy commit event.
32889
+ *
32890
+ * @param symbol - Trading pair symbol
32891
+ * @param currentPrice - New entry price to add to the averaging history
32892
+ * @param context - Execution context with strategyName and exchangeName
32893
+ * @returns Promise<boolean> - true if entry added, false if rejected
32894
+ *
32895
+ * @example
32896
+ * ```typescript
32897
+ * // Add DCA entry at current price
32898
+ * const success = await Live.commitAverageBuy("BTCUSDT", 42000, {
32899
+ * strategyName: "my-strategy",
32900
+ * exchangeName: "binance"
32901
+ * });
32902
+ * if (success) {
32903
+ * console.log('DCA entry added');
32904
+ * }
32905
+ * ```
32906
+ */
32907
+ this.commitAverageBuy = async (symbol, currentPrice, context) => {
32908
+ bt.loggerService.info(LIVE_METHOD_NAME_AVERAGE_BUY, {
32909
+ symbol,
32910
+ currentPrice,
32911
+ context,
32912
+ });
32913
+ bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_AVERAGE_BUY);
32914
+ bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_AVERAGE_BUY);
32915
+ {
32916
+ const { riskName, riskList, actions } = bt.strategySchemaService.get(context.strategyName);
32917
+ riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY);
32918
+ riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_AVERAGE_BUY));
32919
+ actions && actions.forEach((actionName) => bt.actionValidationService.validate(actionName, LIVE_METHOD_NAME_AVERAGE_BUY));
32920
+ }
32921
+ return await bt.strategyCoreService.averageBuy(false, symbol, currentPrice, {
32922
+ strategyName: context.strategyName,
32923
+ exchangeName: context.exchangeName,
32924
+ frameName: "",
32925
+ });
32926
+ };
32282
32927
  /**
32283
32928
  * Gets statistical data from all live trading events for a symbol-strategy pair.
32284
32929
  *
@@ -34942,6 +35587,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34942
35587
  priceStopLoss: data.signal.priceStopLoss,
34943
35588
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34944
35589
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35590
+ originalPriceOpen: data.signal.originalPriceOpen,
35591
+ totalEntries: data.signal.totalEntries,
34945
35592
  note: data.signal.note,
34946
35593
  scheduledAt: data.signal.scheduledAt,
34947
35594
  pendingAt: data.signal.pendingAt,
@@ -34967,6 +35614,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34967
35614
  priceStopLoss: data.signal.priceStopLoss,
34968
35615
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34969
35616
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35617
+ originalPriceOpen: data.signal.originalPriceOpen,
35618
+ totalEntries: data.signal.totalEntries,
34970
35619
  pnlPercentage: data.pnl.pnlPercentage,
34971
35620
  closeReason: data.closeReason,
34972
35621
  duration: durationMin,
@@ -34992,6 +35641,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
34992
35641
  priceStopLoss: data.signal.priceStopLoss,
34993
35642
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
34994
35643
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35644
+ originalPriceOpen: data.signal.originalPriceOpen,
35645
+ totalEntries: data.signal.totalEntries,
34995
35646
  scheduledAt: data.signal.scheduledAt,
34996
35647
  currentPrice: data.currentPrice,
34997
35648
  createdAt: data.createdAt,
@@ -35015,6 +35666,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
35015
35666
  priceStopLoss: data.signal.priceStopLoss,
35016
35667
  originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
35017
35668
  originalPriceStopLoss: data.signal.originalPriceStopLoss,
35669
+ originalPriceOpen: data.signal.originalPriceOpen,
35670
+ totalEntries: data.signal.totalEntries,
35018
35671
  cancelReason: data.reason,
35019
35672
  cancelId: data.cancelId,
35020
35673
  duration: durationMin,
@@ -35047,6 +35700,8 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
35047
35700
  priceStopLoss: data.data.priceStopLoss,
35048
35701
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35049
35702
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35703
+ originalPriceOpen: data.data.originalPriceOpen,
35704
+ totalEntries: data.data.totalEntries,
35050
35705
  scheduledAt: data.data.scheduledAt,
35051
35706
  pendingAt: data.data.pendingAt,
35052
35707
  createdAt: data.timestamp,
@@ -35073,6 +35728,8 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
35073
35728
  priceStopLoss: data.data.priceStopLoss,
35074
35729
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35075
35730
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35731
+ originalPriceOpen: data.data.originalPriceOpen,
35732
+ totalEntries: data.data.totalEntries,
35076
35733
  scheduledAt: data.data.scheduledAt,
35077
35734
  pendingAt: data.data.pendingAt,
35078
35735
  createdAt: data.timestamp,
@@ -35098,6 +35755,8 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
35098
35755
  priceStopLoss: data.data.priceStopLoss,
35099
35756
  originalPriceTakeProfit: data.data.originalPriceTakeProfit,
35100
35757
  originalPriceStopLoss: data.data.originalPriceStopLoss,
35758
+ originalPriceOpen: data.data.originalPriceOpen,
35759
+ totalEntries: data.data.totalEntries,
35101
35760
  scheduledAt: data.data.scheduledAt,
35102
35761
  pendingAt: data.data.pendingAt,
35103
35762
  createdAt: data.timestamp,
@@ -35128,6 +35787,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35128
35787
  priceStopLoss: data.priceStopLoss,
35129
35788
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35130
35789
  originalPriceStopLoss: data.originalPriceStopLoss,
35790
+ originalPriceOpen: data.originalPriceOpen,
35791
+ totalEntries: data.totalEntries,
35131
35792
  scheduledAt: data.scheduledAt,
35132
35793
  pendingAt: data.pendingAt,
35133
35794
  createdAt: data.timestamp,
@@ -35151,6 +35812,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35151
35812
  priceStopLoss: data.priceStopLoss,
35152
35813
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35153
35814
  originalPriceStopLoss: data.originalPriceStopLoss,
35815
+ originalPriceOpen: data.originalPriceOpen,
35816
+ totalEntries: data.totalEntries,
35154
35817
  scheduledAt: data.scheduledAt,
35155
35818
  pendingAt: data.pendingAt,
35156
35819
  createdAt: data.timestamp,
@@ -35173,6 +35836,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35173
35836
  priceStopLoss: data.priceStopLoss,
35174
35837
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35175
35838
  originalPriceStopLoss: data.originalPriceStopLoss,
35839
+ originalPriceOpen: data.originalPriceOpen,
35840
+ totalEntries: data.totalEntries,
35176
35841
  scheduledAt: data.scheduledAt,
35177
35842
  pendingAt: data.pendingAt,
35178
35843
  createdAt: data.timestamp,
@@ -35196,6 +35861,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35196
35861
  priceStopLoss: data.priceStopLoss,
35197
35862
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35198
35863
  originalPriceStopLoss: data.originalPriceStopLoss,
35864
+ originalPriceOpen: data.originalPriceOpen,
35865
+ totalEntries: data.totalEntries,
35199
35866
  scheduledAt: data.scheduledAt,
35200
35867
  pendingAt: data.pendingAt,
35201
35868
  createdAt: data.timestamp,
@@ -35219,6 +35886,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35219
35886
  priceStopLoss: data.priceStopLoss,
35220
35887
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35221
35888
  originalPriceStopLoss: data.originalPriceStopLoss,
35889
+ originalPriceOpen: data.originalPriceOpen,
35890
+ totalEntries: data.totalEntries,
35222
35891
  scheduledAt: data.scheduledAt,
35223
35892
  pendingAt: data.pendingAt,
35224
35893
  createdAt: data.timestamp,
@@ -35242,6 +35911,33 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
35242
35911
  priceStopLoss: data.priceStopLoss,
35243
35912
  originalPriceTakeProfit: data.originalPriceTakeProfit,
35244
35913
  originalPriceStopLoss: data.originalPriceStopLoss,
35914
+ originalPriceOpen: data.originalPriceOpen,
35915
+ totalEntries: data.totalEntries,
35916
+ scheduledAt: data.scheduledAt,
35917
+ pendingAt: data.pendingAt,
35918
+ createdAt: data.timestamp,
35919
+ };
35920
+ }
35921
+ if (data.action === "average-buy") {
35922
+ return {
35923
+ type: "average_buy.commit",
35924
+ id: CREATE_KEY_FN$1(),
35925
+ timestamp: data.timestamp,
35926
+ backtest: data.backtest,
35927
+ symbol: data.symbol,
35928
+ strategyName: data.strategyName,
35929
+ exchangeName: data.exchangeName,
35930
+ signalId: data.signalId,
35931
+ currentPrice: data.currentPrice,
35932
+ effectivePriceOpen: data.effectivePriceOpen,
35933
+ totalEntries: data.totalEntries,
35934
+ position: data.position,
35935
+ priceOpen: data.priceOpen,
35936
+ priceTakeProfit: data.priceTakeProfit,
35937
+ priceStopLoss: data.priceStopLoss,
35938
+ originalPriceTakeProfit: data.originalPriceTakeProfit,
35939
+ originalPriceStopLoss: data.originalPriceStopLoss,
35940
+ originalPriceOpen: data.originalPriceOpen,
35245
35941
  scheduledAt: data.scheduledAt,
35246
35942
  pendingAt: data.pendingAt,
35247
35943
  createdAt: data.timestamp,
@@ -37631,6 +38327,7 @@ exports.addWalkerSchema = addWalkerSchema;
37631
38327
  exports.alignToInterval = alignToInterval;
37632
38328
  exports.checkCandles = checkCandles;
37633
38329
  exports.commitActivateScheduled = commitActivateScheduled;
38330
+ exports.commitAverageBuy = commitAverageBuy;
37634
38331
  exports.commitBreakeven = commitBreakeven;
37635
38332
  exports.commitCancelScheduled = commitCancelScheduled;
37636
38333
  exports.commitClosePending = commitClosePending;