backtest-kit 3.2.0 → 3.3.1
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/README.md +1 -1
- package/build/index.cjs +786 -89
- package/build/index.mjs +786 -90
- package/package.json +1 -1
- package/types.d.ts +391 -17
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
|
|
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((
|
|
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 =
|
|
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 =
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 >
|
|
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 =
|
|
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 <
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 -
|
|
4264
|
-
: ((
|
|
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 -
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 -
|
|
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 -
|
|
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 -
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 -
|
|
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 >
|
|
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 =
|
|
5774
|
+
const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
5657
5775
|
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
5658
|
-
const breakevenPrice =
|
|
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 <
|
|
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 =
|
|
5789
|
+
const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
5672
5790
|
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
5673
|
-
const breakevenPrice =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
6454
|
-
|
|
6455
|
-
if (
|
|
6456
|
-
|
|
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
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
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
|
-
|
|
6586
|
-
|
|
6587
|
-
if (
|
|
6588
|
-
|
|
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
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
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
|
|
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
|
|
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 =
|
|
7006
|
+
newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
|
|
6872
7007
|
}
|
|
6873
7008
|
else {
|
|
6874
|
-
newStopLoss =
|
|
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
|
|
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 =
|
|
7180
|
+
newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
|
|
7045
7181
|
}
|
|
7046
7182
|
else {
|
|
7047
|
-
newTakeProfit =
|
|
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
|
|
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
|
|
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;
|