backtest-kit 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/build/index.cjs +816 -139
- package/build/index.mjs +816 -140
- 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
|
*
|
|
@@ -2907,7 +2928,7 @@ class ExchangeConnectionService {
|
|
|
2907
2928
|
* - Calculates weighted PNL: Σ(percent_i × pnl_i) for each partial + (remaining% × final_pnl)
|
|
2908
2929
|
* - Each partial close has its own slippage
|
|
2909
2930
|
* - Open fee is charged once; close fees are proportional to each partial's size
|
|
2910
|
-
* - Total fees = CC_PERCENT_FEE (open) + CC_PERCENT_FEE ×
|
|
2931
|
+
* - Total fees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partial% / 100) × (closeWithSlip / openWithSlip)
|
|
2911
2932
|
*
|
|
2912
2933
|
* Formula breakdown:
|
|
2913
2934
|
* 1. Apply slippage to open/close prices (worse execution)
|
|
@@ -2916,7 +2937,7 @@ class ExchangeConnectionService {
|
|
|
2916
2937
|
* 2. Calculate raw PNL percentage
|
|
2917
2938
|
* - LONG: ((closePrice - openPrice) / openPrice) * 100
|
|
2918
2939
|
* - SHORT: ((openPrice - closePrice) / openPrice) * 100
|
|
2919
|
-
* 3. Subtract total fees
|
|
2940
|
+
* 3. Subtract total fees: open fee + close fee adjusted for slippage-affected execution price
|
|
2920
2941
|
*
|
|
2921
2942
|
* @param signal - Closed signal with position details and optional partial history
|
|
2922
2943
|
* @param priceClose - Actual close price at final exit
|
|
@@ -2950,73 +2971,53 @@ 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;
|
|
2957
2978
|
// Open fee is paid once for the whole position
|
|
2958
2979
|
let totalFees = GLOBAL_CONFIG.CC_PERCENT_FEE;
|
|
2980
|
+
// priceOpenWithSlippage is the same for all partials — compute once
|
|
2981
|
+
const priceOpenWithSlippage = signal.position === "long"
|
|
2982
|
+
? priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2983
|
+
: priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2959
2984
|
// Calculate PNL for each partial close
|
|
2960
2985
|
for (const partial of signal._partial) {
|
|
2961
2986
|
const partialPercent = partial.percent;
|
|
2962
|
-
const
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
let priceCloseWithSlippage;
|
|
2966
|
-
if (signal.position === "long") {
|
|
2967
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2968
|
-
priceCloseWithSlippage = partialPrice * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2969
|
-
}
|
|
2970
|
-
else {
|
|
2971
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2972
|
-
priceCloseWithSlippage = partialPrice * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2973
|
-
}
|
|
2987
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
2988
|
+
? partial.price * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
2989
|
+
: partial.price * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2974
2990
|
// Calculate PNL for this partial
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
}
|
|
2979
|
-
else {
|
|
2980
|
-
partialPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2981
|
-
}
|
|
2991
|
+
const partialPnl = signal.position === "long"
|
|
2992
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
2993
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
2982
2994
|
// Weight by percentage of position closed
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (partialPercent / 100);
|
|
2995
|
+
totalWeightedPnl += (partialPercent / 100) * partialPnl;
|
|
2996
|
+
// Close fee is proportional to the size of this partial and adjusted for slippage
|
|
2997
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (partialPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
2987
2998
|
}
|
|
2988
2999
|
// Calculate PNL for remaining position (if any)
|
|
2989
3000
|
// Compute totalClosed from _partial array
|
|
2990
3001
|
const totalClosed = signal._partial.reduce((sum, p) => sum + p.percent, 0);
|
|
3002
|
+
if (totalClosed > 100) {
|
|
3003
|
+
throw new Error(`Partial closes exceed 100%: ${totalClosed}% (signal id: ${signal.id})`);
|
|
3004
|
+
}
|
|
2991
3005
|
const remainingPercent = 100 - totalClosed;
|
|
2992
3006
|
if (remainingPercent > 0) {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
if (signal.position === "long") {
|
|
2997
|
-
priceOpenWithSlippage = priceOpen * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2998
|
-
priceCloseWithSlippage = priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
2999
|
-
}
|
|
3000
|
-
else {
|
|
3001
|
-
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3002
|
-
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3003
|
-
}
|
|
3007
|
+
const priceCloseWithSlippage = signal.position === "long"
|
|
3008
|
+
? priceClose * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100)
|
|
3009
|
+
: priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3004
3010
|
// Calculate PNL for remaining
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
}
|
|
3009
|
-
else {
|
|
3010
|
-
remainingPnl = ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
3011
|
-
}
|
|
3011
|
+
const remainingPnl = signal.position === "long"
|
|
3012
|
+
? ((priceCloseWithSlippage - priceOpenWithSlippage) / priceOpenWithSlippage) * 100
|
|
3013
|
+
: ((priceOpenWithSlippage - priceCloseWithSlippage) / priceOpenWithSlippage) * 100;
|
|
3012
3014
|
// Weight by remaining percentage
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (remainingPercent / 100);
|
|
3015
|
+
totalWeightedPnl += (remainingPercent / 100) * remainingPnl;
|
|
3016
|
+
// Close fee is proportional to the remaining size and adjusted for slippage
|
|
3017
|
+
totalFees += GLOBAL_CONFIG.CC_PERCENT_FEE * (remainingPercent / 100) * (priceCloseWithSlippage / priceOpenWithSlippage);
|
|
3017
3018
|
}
|
|
3018
3019
|
// Subtract total fees from weighted PNL
|
|
3019
|
-
// totalFees = CC_PERCENT_FEE (open) + CC_PERCENT_FEE ×
|
|
3020
|
+
// totalFees = CC_PERCENT_FEE (open) + Σ CC_PERCENT_FEE × (partialPercent/100) × (closeWithSlip/openWithSlip)
|
|
3020
3021
|
const pnlPercentage = totalWeightedPnl - totalFees;
|
|
3021
3022
|
return {
|
|
3022
3023
|
pnlPercentage,
|
|
@@ -3037,8 +3038,8 @@ const toProfitLossDto = (signal, priceClose) => {
|
|
|
3037
3038
|
priceOpenWithSlippage = priceOpen * (1 - GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3038
3039
|
priceCloseWithSlippage = priceClose * (1 + GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE / 100);
|
|
3039
3040
|
}
|
|
3040
|
-
//
|
|
3041
|
-
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE *
|
|
3041
|
+
// Открытие: комиссия от цены входа; закрытие: комиссия от фактической цены выхода (с учётом slippage)
|
|
3042
|
+
const totalFee = GLOBAL_CONFIG.CC_PERCENT_FEE * (1 + priceCloseWithSlippage / priceOpenWithSlippage);
|
|
3042
3043
|
let pnlPercentage;
|
|
3043
3044
|
if (signal.position === "long") {
|
|
3044
3045
|
// LONG: прибыль при росте цены
|
|
@@ -3224,6 +3225,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3224
3225
|
percentToClose: commit.percentToClose,
|
|
3225
3226
|
currentPrice: commit.currentPrice,
|
|
3226
3227
|
timestamp,
|
|
3228
|
+
totalEntries: publicSignal.totalEntries,
|
|
3227
3229
|
position: publicSignal.position,
|
|
3228
3230
|
priceOpen: publicSignal.priceOpen,
|
|
3229
3231
|
signalId: publicSignal.id,
|
|
@@ -3231,6 +3233,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3231
3233
|
priceStopLoss: publicSignal.priceStopLoss,
|
|
3232
3234
|
originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
|
|
3233
3235
|
originalPriceStopLoss: publicSignal.originalPriceStopLoss,
|
|
3236
|
+
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
3234
3237
|
scheduledAt: publicSignal.scheduledAt,
|
|
3235
3238
|
pendingAt: publicSignal.pendingAt,
|
|
3236
3239
|
});
|
|
@@ -3247,6 +3250,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3247
3250
|
percentToClose: commit.percentToClose,
|
|
3248
3251
|
currentPrice: commit.currentPrice,
|
|
3249
3252
|
timestamp,
|
|
3253
|
+
totalEntries: publicSignal.totalEntries,
|
|
3250
3254
|
position: publicSignal.position,
|
|
3251
3255
|
priceOpen: publicSignal.priceOpen,
|
|
3252
3256
|
signalId: publicSignal.id,
|
|
@@ -3254,6 +3258,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3254
3258
|
priceStopLoss: publicSignal.priceStopLoss,
|
|
3255
3259
|
originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
|
|
3256
3260
|
originalPriceStopLoss: publicSignal.originalPriceStopLoss,
|
|
3261
|
+
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
3257
3262
|
scheduledAt: publicSignal.scheduledAt,
|
|
3258
3263
|
pendingAt: publicSignal.pendingAt,
|
|
3259
3264
|
});
|
|
@@ -3269,6 +3274,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3269
3274
|
backtest: commit.backtest,
|
|
3270
3275
|
currentPrice: commit.currentPrice,
|
|
3271
3276
|
timestamp,
|
|
3277
|
+
totalEntries: publicSignal.totalEntries,
|
|
3272
3278
|
signalId: publicSignal.id,
|
|
3273
3279
|
position: publicSignal.position,
|
|
3274
3280
|
priceOpen: publicSignal.priceOpen,
|
|
@@ -3276,6 +3282,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3276
3282
|
priceStopLoss: publicSignal.priceStopLoss,
|
|
3277
3283
|
originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
|
|
3278
3284
|
originalPriceStopLoss: publicSignal.originalPriceStopLoss,
|
|
3285
|
+
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
3279
3286
|
scheduledAt: publicSignal.scheduledAt,
|
|
3280
3287
|
pendingAt: publicSignal.pendingAt,
|
|
3281
3288
|
});
|
|
@@ -3292,6 +3299,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3292
3299
|
percentShift: commit.percentShift,
|
|
3293
3300
|
currentPrice: commit.currentPrice,
|
|
3294
3301
|
timestamp,
|
|
3302
|
+
totalEntries: publicSignal.totalEntries,
|
|
3295
3303
|
signalId: publicSignal.id,
|
|
3296
3304
|
position: publicSignal.position,
|
|
3297
3305
|
priceOpen: publicSignal.priceOpen,
|
|
@@ -3299,6 +3307,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3299
3307
|
priceStopLoss: publicSignal.priceStopLoss,
|
|
3300
3308
|
originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
|
|
3301
3309
|
originalPriceStopLoss: publicSignal.originalPriceStopLoss,
|
|
3310
|
+
originalPriceOpen: publicSignal.originalPriceOpen,
|
|
3302
3311
|
scheduledAt: publicSignal.scheduledAt,
|
|
3303
3312
|
pendingAt: publicSignal.pendingAt,
|
|
3304
3313
|
});
|
|
@@ -3315,6 +3324,7 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3315
3324
|
percentShift: commit.percentShift,
|
|
3316
3325
|
currentPrice: commit.currentPrice,
|
|
3317
3326
|
timestamp,
|
|
3327
|
+
totalEntries: publicSignal.totalEntries,
|
|
3318
3328
|
signalId: publicSignal.id,
|
|
3319
3329
|
position: publicSignal.position,
|
|
3320
3330
|
priceOpen: publicSignal.priceOpen,
|
|
@@ -3322,6 +3332,33 @@ const PROCESS_COMMIT_QUEUE_FN = async (self, timestamp) => {
|
|
|
3322
3332
|
priceStopLoss: publicSignal.priceStopLoss,
|
|
3323
3333
|
originalPriceTakeProfit: publicSignal.originalPriceTakeProfit,
|
|
3324
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,
|
|
3325
3362
|
scheduledAt: publicSignal.scheduledAt,
|
|
3326
3363
|
pendingAt: publicSignal.pendingAt,
|
|
3327
3364
|
});
|
|
@@ -3382,13 +3419,20 @@ const TO_PUBLIC_SIGNAL = (signal) => {
|
|
|
3382
3419
|
const partialExecuted = ("_partial" in signal && Array.isArray(signal._partial))
|
|
3383
3420
|
? signal._partial.reduce((sum, partial) => sum + partial.percent, 0)
|
|
3384
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;
|
|
3385
3426
|
return {
|
|
3386
3427
|
...structuredClone(signal),
|
|
3428
|
+
priceOpen: effectivePriceOpen,
|
|
3387
3429
|
priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
|
|
3388
3430
|
priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
|
|
3431
|
+
originalPriceOpen: signal.priceOpen,
|
|
3389
3432
|
originalPriceStopLoss: signal.priceStopLoss,
|
|
3390
3433
|
originalPriceTakeProfit: signal.priceTakeProfit,
|
|
3391
3434
|
partialExecuted,
|
|
3435
|
+
totalEntries,
|
|
3392
3436
|
};
|
|
3393
3437
|
};
|
|
3394
3438
|
const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
|
|
@@ -3718,6 +3762,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
3718
3762
|
scheduledAt: currentTime,
|
|
3719
3763
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
3720
3764
|
_isScheduled: false,
|
|
3765
|
+
_entry: [{ price: signal.priceOpen }],
|
|
3721
3766
|
};
|
|
3722
3767
|
// Валидируем сигнал перед возвратом
|
|
3723
3768
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -3739,6 +3784,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
3739
3784
|
scheduledAt: currentTime,
|
|
3740
3785
|
pendingAt: SCHEDULED_SIGNAL_PENDING_MOCK, // Временно, обновится при активации
|
|
3741
3786
|
_isScheduled: true,
|
|
3787
|
+
_entry: [{ price: signal.priceOpen }],
|
|
3742
3788
|
};
|
|
3743
3789
|
// Валидируем сигнал перед возвратом
|
|
3744
3790
|
VALIDATE_SIGNAL_FN(scheduledSignalRow, currentPrice, true);
|
|
@@ -3756,6 +3802,7 @@ const GET_SIGNAL_FN = functoolsKit.trycatch(async (self) => {
|
|
|
3756
3802
|
scheduledAt: currentTime,
|
|
3757
3803
|
pendingAt: currentTime, // Для immediate signal оба времени одинаковые
|
|
3758
3804
|
_isScheduled: false,
|
|
3805
|
+
_entry: [{ price: currentPrice }],
|
|
3759
3806
|
};
|
|
3760
3807
|
// Валидируем сигнал перед возвратом
|
|
3761
3808
|
VALIDATE_SIGNAL_FN(signalRow, currentPrice, false);
|
|
@@ -3902,9 +3949,10 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
3902
3949
|
return true;
|
|
3903
3950
|
};
|
|
3904
3951
|
const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
|
|
3952
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
3905
3953
|
// CRITICAL: Always calculate from ORIGINAL SL, not from current trailing SL
|
|
3906
3954
|
// This prevents error accumulation on repeated calls
|
|
3907
|
-
const originalSlDistancePercent = Math.abs((
|
|
3955
|
+
const originalSlDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
|
|
3908
3956
|
// Calculate new stop-loss distance percentage by adding shift to ORIGINAL distance
|
|
3909
3957
|
// Negative percentShift: reduces distance % (tightens stop, moves SL toward entry or beyond)
|
|
3910
3958
|
// Positive percentShift: increases distance % (loosens stop, moves SL away from entry)
|
|
@@ -3916,13 +3964,13 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
|
|
|
3916
3964
|
// LONG: SL is below entry (or above entry if in profit zone)
|
|
3917
3965
|
// Formula: entry * (1 - newDistance%)
|
|
3918
3966
|
// Example: entry=100, originalSL=90 (10%), shift=-5% → newDistance=5% → 100 * 0.95 = 95 (tighter)
|
|
3919
|
-
newStopLoss =
|
|
3967
|
+
newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
|
|
3920
3968
|
}
|
|
3921
3969
|
else {
|
|
3922
3970
|
// SHORT: SL is above entry (or below entry if in profit zone)
|
|
3923
3971
|
// Formula: entry * (1 + newDistance%)
|
|
3924
3972
|
// Example: entry=100, originalSL=110 (10%), shift=-5% → newDistance=5% → 100 * 1.05 = 105 (tighter)
|
|
3925
|
-
newStopLoss =
|
|
3973
|
+
newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
3926
3974
|
}
|
|
3927
3975
|
const currentTrailingSL = signal._trailingPriceStopLoss;
|
|
3928
3976
|
const isFirstCall = currentTrailingSL === undefined;
|
|
@@ -3985,9 +4033,10 @@ const TRAILING_STOP_LOSS_FN = (self, signal, percentShift) => {
|
|
|
3985
4033
|
}
|
|
3986
4034
|
};
|
|
3987
4035
|
const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
|
|
4036
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
3988
4037
|
// CRITICAL: Always calculate from ORIGINAL TP, not from current trailing TP
|
|
3989
4038
|
// This prevents error accumulation on repeated calls
|
|
3990
|
-
const originalTpDistancePercent = Math.abs((signal.priceTakeProfit -
|
|
4039
|
+
const originalTpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
3991
4040
|
// Calculate new take-profit distance percentage by adding shift to ORIGINAL distance
|
|
3992
4041
|
// Negative percentShift: reduces distance % (brings TP closer to entry)
|
|
3993
4042
|
// Positive percentShift: increases distance % (moves TP further from entry)
|
|
@@ -3998,13 +4047,13 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
|
|
|
3998
4047
|
// LONG: TP is above entry
|
|
3999
4048
|
// Formula: entry * (1 + newDistance%)
|
|
4000
4049
|
// Example: entry=100, originalTP=110 (10%), shift=-3% → newDistance=7% → 100 * 1.07 = 107 (closer)
|
|
4001
|
-
newTakeProfit =
|
|
4050
|
+
newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
|
|
4002
4051
|
}
|
|
4003
4052
|
else {
|
|
4004
4053
|
// SHORT: TP is below entry
|
|
4005
4054
|
// Formula: entry * (1 - newDistance%)
|
|
4006
4055
|
// Example: entry=100, originalTP=90 (10%), shift=-3% → newDistance=7% → 100 * 0.93 = 93 (closer)
|
|
4007
|
-
newTakeProfit =
|
|
4056
|
+
newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
4008
4057
|
}
|
|
4009
4058
|
const currentTrailingTP = signal._trailingPriceTakeProfit;
|
|
4010
4059
|
const isFirstCall = currentTrailingTP === undefined;
|
|
@@ -4065,6 +4114,7 @@ const TRAILING_TAKE_PROFIT_FN = (self, signal, percentShift) => {
|
|
|
4065
4114
|
}
|
|
4066
4115
|
};
|
|
4067
4116
|
const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
4117
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
4068
4118
|
// Calculate breakeven threshold based on slippage and fees
|
|
4069
4119
|
// Need to cover: entry slippage + entry fee + exit slippage + exit fee
|
|
4070
4120
|
// Total: (slippage + fee) * 2 transactions
|
|
@@ -4072,10 +4122,10 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4072
4122
|
// Check if trailing stop is already set
|
|
4073
4123
|
if (signal._trailingPriceStopLoss !== undefined) {
|
|
4074
4124
|
const trailingStopLoss = signal._trailingPriceStopLoss;
|
|
4075
|
-
const breakevenPrice =
|
|
4125
|
+
const breakevenPrice = effectivePriceOpen;
|
|
4076
4126
|
if (signal.position === "long") {
|
|
4077
4127
|
// LONG: trailing SL is positive if it's above entry (in profit zone)
|
|
4078
|
-
const isPositiveTrailing = trailingStopLoss >
|
|
4128
|
+
const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
|
|
4079
4129
|
if (isPositiveTrailing) {
|
|
4080
4130
|
// Trailing stop is already protecting profit - consider breakeven achieved
|
|
4081
4131
|
self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
|
|
@@ -4091,7 +4141,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4091
4141
|
else {
|
|
4092
4142
|
// Trailing stop is negative (below entry)
|
|
4093
4143
|
// Check if we can upgrade it to breakeven
|
|
4094
|
-
const thresholdPrice =
|
|
4144
|
+
const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
4095
4145
|
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
4096
4146
|
if (isThresholdReached && breakevenPrice > trailingStopLoss) {
|
|
4097
4147
|
// Check for price intrusion before setting new SL
|
|
@@ -4140,7 +4190,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4140
4190
|
}
|
|
4141
4191
|
else {
|
|
4142
4192
|
// SHORT: trailing SL is positive if it's below entry (in profit zone)
|
|
4143
|
-
const isPositiveTrailing = trailingStopLoss <
|
|
4193
|
+
const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
|
|
4144
4194
|
if (isPositiveTrailing) {
|
|
4145
4195
|
// Trailing stop is already protecting profit - consider breakeven achieved
|
|
4146
4196
|
self.params.logger.debug("BREAKEVEN_FN: positive trailing stop already set, returning true", {
|
|
@@ -4156,7 +4206,7 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4156
4206
|
else {
|
|
4157
4207
|
// Trailing stop is negative (above entry)
|
|
4158
4208
|
// Check if we can upgrade it to breakeven
|
|
4159
|
-
const thresholdPrice =
|
|
4209
|
+
const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
4160
4210
|
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
4161
4211
|
if (isThresholdReached && breakevenPrice < trailingStopLoss) {
|
|
4162
4212
|
// Check for price intrusion before setting new SL
|
|
@@ -4206,21 +4256,21 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4206
4256
|
}
|
|
4207
4257
|
// No trailing stop set - proceed with normal breakeven logic
|
|
4208
4258
|
const currentStopLoss = signal.priceStopLoss;
|
|
4209
|
-
const breakevenPrice =
|
|
4259
|
+
const breakevenPrice = effectivePriceOpen;
|
|
4210
4260
|
// Calculate threshold price
|
|
4211
4261
|
let thresholdPrice;
|
|
4212
4262
|
let isThresholdReached;
|
|
4213
4263
|
let canMoveToBreakeven;
|
|
4214
4264
|
if (signal.position === "long") {
|
|
4215
4265
|
// LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
|
|
4216
|
-
thresholdPrice =
|
|
4266
|
+
thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
4217
4267
|
isThresholdReached = currentPrice >= thresholdPrice;
|
|
4218
4268
|
// Can move to breakeven only if threshold reached and SL is below entry
|
|
4219
4269
|
canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
|
|
4220
4270
|
}
|
|
4221
4271
|
else {
|
|
4222
4272
|
// SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
|
|
4223
|
-
thresholdPrice =
|
|
4273
|
+
thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
4224
4274
|
isThresholdReached = currentPrice <= thresholdPrice;
|
|
4225
4275
|
// Can move to breakeven only if threshold reached and SL is above entry
|
|
4226
4276
|
canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
|
|
@@ -4280,8 +4330,51 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
4280
4330
|
thresholdPrice,
|
|
4281
4331
|
breakevenThresholdPercent,
|
|
4282
4332
|
profitDistancePercent: signal.position === "long"
|
|
4283
|
-
? ((currentPrice -
|
|
4284
|
-
: ((
|
|
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,
|
|
4285
4378
|
});
|
|
4286
4379
|
return true;
|
|
4287
4380
|
};
|
|
@@ -5010,9 +5103,10 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5010
5103
|
await CALL_ACTIVE_PING_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentTime, self.params.execution.context.backtest);
|
|
5011
5104
|
// Calculate percentage of path to TP/SL for partial fill/loss callbacks
|
|
5012
5105
|
{
|
|
5106
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
5013
5107
|
if (signal.position === "long") {
|
|
5014
5108
|
// For long: calculate progress towards TP or SL
|
|
5015
|
-
const currentDistance = currentPrice -
|
|
5109
|
+
const currentDistance = currentPrice - effectivePriceOpen;
|
|
5016
5110
|
if (currentDistance > 0) {
|
|
5017
5111
|
// Check if breakeven should be triggered
|
|
5018
5112
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
|
|
@@ -5020,7 +5114,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5020
5114
|
if (currentDistance > 0) {
|
|
5021
5115
|
// Moving towards TP (use trailing TP if set)
|
|
5022
5116
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
5023
|
-
const tpDistance = effectiveTakeProfit -
|
|
5117
|
+
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
5024
5118
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5025
5119
|
percentTp = Math.min(progressPercent, 100);
|
|
5026
5120
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
@@ -5028,7 +5122,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5028
5122
|
else if (currentDistance < 0) {
|
|
5029
5123
|
// Moving towards SL (use trailing SL if set)
|
|
5030
5124
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
5031
|
-
const slDistance =
|
|
5125
|
+
const slDistance = effectivePriceOpen - effectiveStopLoss;
|
|
5032
5126
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
5033
5127
|
percentSl = Math.min(progressPercent, 100);
|
|
5034
5128
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
|
|
@@ -5036,7 +5130,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5036
5130
|
}
|
|
5037
5131
|
else if (signal.position === "short") {
|
|
5038
5132
|
// For short: calculate progress towards TP or SL
|
|
5039
|
-
const currentDistance =
|
|
5133
|
+
const currentDistance = effectivePriceOpen - currentPrice;
|
|
5040
5134
|
if (currentDistance > 0) {
|
|
5041
5135
|
// Check if breakeven should be triggered
|
|
5042
5136
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
|
|
@@ -5044,7 +5138,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5044
5138
|
if (currentDistance > 0) {
|
|
5045
5139
|
// Moving towards TP (use trailing TP if set)
|
|
5046
5140
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
5047
|
-
const tpDistance =
|
|
5141
|
+
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
5048
5142
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5049
5143
|
percentTp = Math.min(progressPercent, 100);
|
|
5050
5144
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
@@ -5052,7 +5146,7 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
5052
5146
|
if (currentDistance < 0) {
|
|
5053
5147
|
// Moving towards SL (use trailing SL if set)
|
|
5054
5148
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
5055
|
-
const slDistance = effectiveStopLoss -
|
|
5149
|
+
const slDistance = effectiveStopLoss - effectivePriceOpen;
|
|
5056
5150
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
5057
5151
|
percentSl = Math.min(progressPercent, 100);
|
|
5058
5152
|
await CALL_PARTIAL_LOSS_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentSl, currentTime, self.params.execution.context.backtest);
|
|
@@ -5272,8 +5366,10 @@ const PROCESS_SCHEDULED_SIGNAL_CANDLES_FN = async (self, scheduled, candles) =>
|
|
|
5272
5366
|
priceStopLoss: publicSignalForCommit.priceStopLoss,
|
|
5273
5367
|
originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
|
|
5274
5368
|
originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
|
|
5369
|
+
originalPriceOpen: publicSignalForCommit.originalPriceOpen,
|
|
5275
5370
|
scheduledAt: publicSignalForCommit.scheduledAt,
|
|
5276
5371
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
5372
|
+
totalEntries: publicSignalForCommit.totalEntries,
|
|
5277
5373
|
});
|
|
5278
5374
|
await CALL_OPEN_CALLBACKS_FN(self, self.params.execution.context.symbol, pendingSignal, pendingSignal.priceOpen, candle.timestamp, self.params.execution.context.backtest);
|
|
5279
5375
|
await CALL_BACKTEST_SCHEDULE_OPEN_FN(self, self.params.execution.context.symbol, pendingSignal, candle.timestamp, self.params.execution.context.backtest);
|
|
@@ -5420,9 +5516,10 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
5420
5516
|
// Call onPartialProfit/onPartialLoss callbacks during backtest candle processing
|
|
5421
5517
|
// Calculate percentage of path to TP/SL
|
|
5422
5518
|
{
|
|
5519
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
5423
5520
|
if (signal.position === "long") {
|
|
5424
5521
|
// For long: calculate progress towards TP or SL
|
|
5425
|
-
const currentDistance = averagePrice -
|
|
5522
|
+
const currentDistance = averagePrice - effectivePriceOpen;
|
|
5426
5523
|
if (currentDistance > 0) {
|
|
5427
5524
|
// Check if breakeven should be triggered
|
|
5428
5525
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
|
|
@@ -5430,21 +5527,21 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
5430
5527
|
if (currentDistance > 0) {
|
|
5431
5528
|
// Moving towards TP (use trailing TP if set)
|
|
5432
5529
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
5433
|
-
const tpDistance = effectiveTakeProfit -
|
|
5530
|
+
const tpDistance = effectiveTakeProfit - effectivePriceOpen;
|
|
5434
5531
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5435
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);
|
|
5436
5533
|
}
|
|
5437
5534
|
else if (currentDistance < 0) {
|
|
5438
5535
|
// Moving towards SL (use trailing SL if set)
|
|
5439
5536
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
5440
|
-
const slDistance =
|
|
5537
|
+
const slDistance = effectivePriceOpen - effectiveStopLoss;
|
|
5441
5538
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
5442
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);
|
|
5443
5540
|
}
|
|
5444
5541
|
}
|
|
5445
5542
|
else if (signal.position === "short") {
|
|
5446
5543
|
// For short: calculate progress towards TP or SL
|
|
5447
|
-
const currentDistance =
|
|
5544
|
+
const currentDistance = effectivePriceOpen - averagePrice;
|
|
5448
5545
|
if (currentDistance > 0) {
|
|
5449
5546
|
// Check if breakeven should be triggered
|
|
5450
5547
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
|
|
@@ -5452,14 +5549,14 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
5452
5549
|
if (currentDistance > 0) {
|
|
5453
5550
|
// Moving towards TP (use trailing TP if set)
|
|
5454
5551
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
5455
|
-
const tpDistance =
|
|
5552
|
+
const tpDistance = effectivePriceOpen - effectiveTakeProfit;
|
|
5456
5553
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
5457
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);
|
|
5458
5555
|
}
|
|
5459
5556
|
if (currentDistance < 0) {
|
|
5460
5557
|
// Moving towards SL (use trailing SL if set)
|
|
5461
5558
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
5462
|
-
const slDistance = effectiveStopLoss -
|
|
5559
|
+
const slDistance = effectiveStopLoss - effectivePriceOpen;
|
|
5463
5560
|
const progressPercent = (Math.abs(currentDistance) / slDistance) * 100;
|
|
5464
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);
|
|
5465
5562
|
}
|
|
@@ -5657,6 +5754,7 @@ class ClientStrategy {
|
|
|
5657
5754
|
return false;
|
|
5658
5755
|
}
|
|
5659
5756
|
const signal = this._pendingSignal;
|
|
5757
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
5660
5758
|
// Calculate breakeven threshold based on slippage and fees
|
|
5661
5759
|
// Need to cover: entry slippage + entry fee + exit slippage + exit fee
|
|
5662
5760
|
// Total: (slippage + fee) * 2 transactions
|
|
@@ -5666,52 +5764,52 @@ class ClientStrategy {
|
|
|
5666
5764
|
const trailingStopLoss = signal._trailingPriceStopLoss;
|
|
5667
5765
|
if (signal.position === "long") {
|
|
5668
5766
|
// LONG: trailing SL is positive if it's above entry (in profit zone)
|
|
5669
|
-
const isPositiveTrailing = trailingStopLoss >
|
|
5767
|
+
const isPositiveTrailing = trailingStopLoss > effectivePriceOpen;
|
|
5670
5768
|
if (isPositiveTrailing) {
|
|
5671
5769
|
// Trailing stop is already protecting profit - breakeven achieved
|
|
5672
5770
|
return true;
|
|
5673
5771
|
}
|
|
5674
5772
|
// Trailing stop is negative (below entry)
|
|
5675
5773
|
// Check if we can upgrade it to breakeven
|
|
5676
|
-
const thresholdPrice =
|
|
5774
|
+
const thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
5677
5775
|
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
5678
|
-
const breakevenPrice =
|
|
5776
|
+
const breakevenPrice = effectivePriceOpen;
|
|
5679
5777
|
// Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
|
|
5680
5778
|
return isThresholdReached && breakevenPrice > trailingStopLoss;
|
|
5681
5779
|
}
|
|
5682
5780
|
else {
|
|
5683
5781
|
// SHORT: trailing SL is positive if it's below entry (in profit zone)
|
|
5684
|
-
const isPositiveTrailing = trailingStopLoss <
|
|
5782
|
+
const isPositiveTrailing = trailingStopLoss < effectivePriceOpen;
|
|
5685
5783
|
if (isPositiveTrailing) {
|
|
5686
5784
|
// Trailing stop is already protecting profit - breakeven achieved
|
|
5687
5785
|
return true;
|
|
5688
5786
|
}
|
|
5689
5787
|
// Trailing stop is negative (above entry)
|
|
5690
5788
|
// Check if we can upgrade it to breakeven
|
|
5691
|
-
const thresholdPrice =
|
|
5789
|
+
const thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
5692
5790
|
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
5693
|
-
const breakevenPrice =
|
|
5791
|
+
const breakevenPrice = effectivePriceOpen;
|
|
5694
5792
|
// Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
|
|
5695
5793
|
return isThresholdReached && breakevenPrice < trailingStopLoss;
|
|
5696
5794
|
}
|
|
5697
5795
|
}
|
|
5698
5796
|
// No trailing stop set - proceed with normal breakeven logic
|
|
5699
5797
|
const currentStopLoss = signal.priceStopLoss;
|
|
5700
|
-
const breakevenPrice =
|
|
5798
|
+
const breakevenPrice = effectivePriceOpen;
|
|
5701
5799
|
// Calculate threshold price
|
|
5702
5800
|
let thresholdPrice;
|
|
5703
5801
|
let isThresholdReached;
|
|
5704
5802
|
let canMoveToBreakeven;
|
|
5705
5803
|
if (signal.position === "long") {
|
|
5706
5804
|
// LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
|
|
5707
|
-
thresholdPrice =
|
|
5805
|
+
thresholdPrice = effectivePriceOpen * (1 + breakevenThresholdPercent / 100);
|
|
5708
5806
|
isThresholdReached = currentPrice >= thresholdPrice;
|
|
5709
5807
|
// Can move to breakeven only if threshold reached and SL is below entry
|
|
5710
5808
|
canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
|
|
5711
5809
|
}
|
|
5712
5810
|
else {
|
|
5713
5811
|
// SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
|
|
5714
|
-
thresholdPrice =
|
|
5812
|
+
thresholdPrice = effectivePriceOpen * (1 - breakevenThresholdPercent / 100);
|
|
5715
5813
|
isThresholdReached = currentPrice <= thresholdPrice;
|
|
5716
5814
|
// Can move to breakeven only if threshold reached and SL is above entry
|
|
5717
5815
|
canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
|
|
@@ -5794,6 +5892,8 @@ class ClientStrategy {
|
|
|
5794
5892
|
backtest: this.params.execution.context.backtest,
|
|
5795
5893
|
cancelId: cancelledSignal.cancelId,
|
|
5796
5894
|
timestamp: currentTime,
|
|
5895
|
+
totalEntries: cancelledSignal._entry?.length ?? 1,
|
|
5896
|
+
originalPriceOpen: cancelledSignal.priceOpen,
|
|
5797
5897
|
});
|
|
5798
5898
|
// Call onCancel callback
|
|
5799
5899
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -5834,6 +5934,8 @@ class ClientStrategy {
|
|
|
5834
5934
|
backtest: this.params.execution.context.backtest,
|
|
5835
5935
|
closeId: closedSignal.closeId,
|
|
5836
5936
|
timestamp: currentTime,
|
|
5937
|
+
totalEntries: closedSignal._entry?.length ?? 1,
|
|
5938
|
+
originalPriceOpen: closedSignal.priceOpen,
|
|
5837
5939
|
});
|
|
5838
5940
|
// Call onClose callback
|
|
5839
5941
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -5914,8 +6016,10 @@ class ClientStrategy {
|
|
|
5914
6016
|
priceStopLoss: publicSignalForCommit.priceStopLoss,
|
|
5915
6017
|
originalPriceTakeProfit: publicSignalForCommit.originalPriceTakeProfit,
|
|
5916
6018
|
originalPriceStopLoss: publicSignalForCommit.originalPriceStopLoss,
|
|
6019
|
+
originalPriceOpen: publicSignalForCommit.originalPriceOpen,
|
|
5917
6020
|
scheduledAt: publicSignalForCommit.scheduledAt,
|
|
5918
6021
|
pendingAt: publicSignalForCommit.pendingAt,
|
|
6022
|
+
totalEntries: publicSignalForCommit.totalEntries,
|
|
5919
6023
|
});
|
|
5920
6024
|
// Call onOpen callback
|
|
5921
6025
|
await CALL_OPEN_CALLBACKS_FN(this, this.params.execution.context.symbol, pendingSignal, currentPrice, currentTime, this.params.execution.context.backtest);
|
|
@@ -6046,6 +6150,8 @@ class ClientStrategy {
|
|
|
6046
6150
|
backtest: true,
|
|
6047
6151
|
cancelId: cancelledSignal.cancelId,
|
|
6048
6152
|
timestamp: closeTimestamp,
|
|
6153
|
+
totalEntries: cancelledSignal._entry?.length ?? 1,
|
|
6154
|
+
originalPriceOpen: cancelledSignal.priceOpen,
|
|
6049
6155
|
});
|
|
6050
6156
|
await CALL_CANCEL_CALLBACKS_FN(this, this.params.execution.context.symbol, cancelledSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
6051
6157
|
const cancelledResult = {
|
|
@@ -6083,6 +6189,8 @@ class ClientStrategy {
|
|
|
6083
6189
|
backtest: true,
|
|
6084
6190
|
closeId: closedSignal.closeId,
|
|
6085
6191
|
timestamp: closeTimestamp,
|
|
6192
|
+
totalEntries: closedSignal._entry?.length ?? 1,
|
|
6193
|
+
originalPriceOpen: closedSignal.priceOpen,
|
|
6086
6194
|
});
|
|
6087
6195
|
await CALL_CLOSE_CALLBACKS_FN(this, this.params.execution.context.symbol, closedSignal, currentPrice, closeTimestamp, this.params.execution.context.backtest);
|
|
6088
6196
|
// КРИТИЧНО: Очищаем состояние ClientPartial при закрытии позиции
|
|
@@ -6470,16 +6578,19 @@ class ClientStrategy {
|
|
|
6470
6578
|
throw new Error(`ClientStrategy partialProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
6471
6579
|
}
|
|
6472
6580
|
// Validation: currentPrice must be moving toward TP (profit direction)
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
if (
|
|
6476
|
-
|
|
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
|
+
}
|
|
6477
6588
|
}
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
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
|
+
}
|
|
6483
6594
|
}
|
|
6484
6595
|
}
|
|
6485
6596
|
// Check if currentPrice already crossed take profit level
|
|
@@ -6602,16 +6713,19 @@ class ClientStrategy {
|
|
|
6602
6713
|
throw new Error(`ClientStrategy partialLoss: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
6603
6714
|
}
|
|
6604
6715
|
// Validation: currentPrice must be moving toward SL (loss direction)
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
if (
|
|
6608
|
-
|
|
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
|
+
}
|
|
6609
6723
|
}
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
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
|
+
}
|
|
6615
6729
|
}
|
|
6616
6730
|
}
|
|
6617
6731
|
// Check if currentPrice already crossed stop loss level
|
|
@@ -6732,7 +6846,7 @@ class ClientStrategy {
|
|
|
6732
6846
|
}
|
|
6733
6847
|
// Check for conflict with existing trailing take profit
|
|
6734
6848
|
const signal = this._pendingSignal;
|
|
6735
|
-
const breakevenPrice = signal
|
|
6849
|
+
const breakevenPrice = getEffectivePriceOpen(signal);
|
|
6736
6850
|
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
6737
6851
|
if (signal.position === "long" && breakevenPrice >= effectiveTakeProfit) {
|
|
6738
6852
|
// LONG: Breakeven SL would be at or above current TP - invalid configuration
|
|
@@ -6884,14 +6998,15 @@ class ClientStrategy {
|
|
|
6884
6998
|
}
|
|
6885
6999
|
// Calculate what the new stop loss would be
|
|
6886
7000
|
const signal = this._pendingSignal;
|
|
6887
|
-
const
|
|
7001
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
7002
|
+
const slDistancePercent = Math.abs((effectivePriceOpen - signal.priceStopLoss) / effectivePriceOpen * 100);
|
|
6888
7003
|
const newSlDistancePercent = slDistancePercent + percentShift;
|
|
6889
7004
|
let newStopLoss;
|
|
6890
7005
|
if (signal.position === "long") {
|
|
6891
|
-
newStopLoss =
|
|
7006
|
+
newStopLoss = effectivePriceOpen * (1 - newSlDistancePercent / 100);
|
|
6892
7007
|
}
|
|
6893
7008
|
else {
|
|
6894
|
-
newStopLoss =
|
|
7009
|
+
newStopLoss = effectivePriceOpen * (1 + newSlDistancePercent / 100);
|
|
6895
7010
|
}
|
|
6896
7011
|
// Check for price intrusion before executing trailing logic
|
|
6897
7012
|
if (signal.position === "long" && currentPrice < newStopLoss) {
|
|
@@ -7057,14 +7172,15 @@ class ClientStrategy {
|
|
|
7057
7172
|
}
|
|
7058
7173
|
// Calculate what the new take profit would be
|
|
7059
7174
|
const signal = this._pendingSignal;
|
|
7060
|
-
const
|
|
7175
|
+
const effectivePriceOpen = getEffectivePriceOpen(signal);
|
|
7176
|
+
const tpDistancePercent = Math.abs((signal.priceTakeProfit - effectivePriceOpen) / effectivePriceOpen * 100);
|
|
7061
7177
|
const newTpDistancePercent = tpDistancePercent + percentShift;
|
|
7062
7178
|
let newTakeProfit;
|
|
7063
7179
|
if (signal.position === "long") {
|
|
7064
|
-
newTakeProfit =
|
|
7180
|
+
newTakeProfit = effectivePriceOpen * (1 + newTpDistancePercent / 100);
|
|
7065
7181
|
}
|
|
7066
7182
|
else {
|
|
7067
|
-
newTakeProfit =
|
|
7183
|
+
newTakeProfit = effectivePriceOpen * (1 - newTpDistancePercent / 100);
|
|
7068
7184
|
}
|
|
7069
7185
|
// Check for price intrusion before executing trailing logic
|
|
7070
7186
|
if (signal.position === "long" && currentPrice > newTakeProfit) {
|
|
@@ -7146,6 +7262,70 @@ class ClientStrategy {
|
|
|
7146
7262
|
});
|
|
7147
7263
|
return true;
|
|
7148
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
|
+
}
|
|
7149
7329
|
}
|
|
7150
7330
|
|
|
7151
7331
|
const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
|
|
@@ -8270,6 +8450,27 @@ class StrategyConnectionService {
|
|
|
8270
8450
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
8271
8451
|
return await strategy.activateScheduled(symbol, backtest, activateId);
|
|
8272
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
|
+
};
|
|
8273
8474
|
}
|
|
8274
8475
|
}
|
|
8275
8476
|
|
|
@@ -8743,11 +8944,13 @@ const TO_RISK_SIGNAL = (signal, currentPrice) => {
|
|
|
8743
8944
|
: 0;
|
|
8744
8945
|
return {
|
|
8745
8946
|
...structuredClone(signal),
|
|
8947
|
+
totalEntries: 1,
|
|
8746
8948
|
priceOpen: signal.priceOpen ?? currentPrice,
|
|
8747
8949
|
priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
|
|
8748
8950
|
priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
|
|
8749
8951
|
originalPriceStopLoss: signal.priceStopLoss,
|
|
8750
8952
|
originalPriceTakeProfit: signal.priceTakeProfit,
|
|
8953
|
+
originalPriceOpen: signal.priceOpen ?? currentPrice,
|
|
8751
8954
|
partialExecuted,
|
|
8752
8955
|
};
|
|
8753
8956
|
};
|
|
@@ -11615,6 +11818,28 @@ class StrategyCoreService {
|
|
|
11615
11818
|
await this.validate(context);
|
|
11616
11819
|
return await this.strategyConnectionService.activateScheduled(backtest, symbol, context, activateId);
|
|
11617
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
|
+
};
|
|
11618
11843
|
}
|
|
11619
11844
|
}
|
|
11620
11845
|
|
|
@@ -14162,6 +14387,18 @@ const backtest_columns = [
|
|
|
14162
14387
|
format: (data) => `${data.signal.originalPriceStopLoss.toFixed(8)} USD`,
|
|
14163
14388
|
isVisible: () => true,
|
|
14164
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
|
+
},
|
|
14165
14402
|
{
|
|
14166
14403
|
key: "pnl",
|
|
14167
14404
|
label: "PNL (net)",
|
|
@@ -14449,6 +14686,20 @@ const live_columns = [
|
|
|
14449
14686
|
: "N/A",
|
|
14450
14687
|
isVisible: () => true,
|
|
14451
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
|
+
},
|
|
14452
14703
|
{
|
|
14453
14704
|
key: "partialExecuted",
|
|
14454
14705
|
label: "Partial Executed %",
|
|
@@ -14613,6 +14864,18 @@ const partial_columns = [
|
|
|
14613
14864
|
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
14614
14865
|
isVisible: () => true,
|
|
14615
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
|
+
},
|
|
14616
14879
|
{
|
|
14617
14880
|
key: "partialExecuted",
|
|
14618
14881
|
label: "Partial Executed %",
|
|
@@ -14747,6 +15010,18 @@ const breakeven_columns = [
|
|
|
14747
15010
|
format: (data) => (data.originalPriceStopLoss ? `${data.originalPriceStopLoss.toFixed(8)} USD` : "N/A"),
|
|
14748
15011
|
isVisible: () => true,
|
|
14749
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
|
+
},
|
|
14750
15025
|
{
|
|
14751
15026
|
key: "partialExecuted",
|
|
14752
15027
|
label: "Partial Executed %",
|
|
@@ -15017,6 +15292,22 @@ const risk_columns = [
|
|
|
15017
15292
|
: "N/A",
|
|
15018
15293
|
isVisible: () => true,
|
|
15019
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
|
+
},
|
|
15020
15311
|
{
|
|
15021
15312
|
key: "partialExecuted",
|
|
15022
15313
|
label: "Partial Executed %",
|
|
@@ -15191,6 +15482,20 @@ const schedule_columns = [
|
|
|
15191
15482
|
: "N/A",
|
|
15192
15483
|
isVisible: () => true,
|
|
15193
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
|
+
},
|
|
15194
15499
|
{
|
|
15195
15500
|
key: "partialExecuted",
|
|
15196
15501
|
label: "Partial Executed %",
|
|
@@ -17280,6 +17585,8 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
17280
17585
|
priceStopLoss: data.signal.priceStopLoss,
|
|
17281
17586
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
17282
17587
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
17588
|
+
totalEntries: data.signal.totalEntries,
|
|
17589
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
17283
17590
|
partialExecuted: data.signal.partialExecuted,
|
|
17284
17591
|
scheduledAt: data.signal.scheduledAt,
|
|
17285
17592
|
});
|
|
@@ -17309,6 +17616,8 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
17309
17616
|
priceStopLoss: data.signal.priceStopLoss,
|
|
17310
17617
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
17311
17618
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
17619
|
+
totalEntries: data.signal.totalEntries,
|
|
17620
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
17312
17621
|
partialExecuted: data.signal.partialExecuted,
|
|
17313
17622
|
duration: durationMin,
|
|
17314
17623
|
pendingAt: data.signal.pendingAt,
|
|
@@ -17341,6 +17650,8 @@ let ReportStorage$5 = class ReportStorage {
|
|
|
17341
17650
|
priceStopLoss: data.signal.priceStopLoss,
|
|
17342
17651
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
17343
17652
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
17653
|
+
totalEntries: data.signal.totalEntries,
|
|
17654
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
17344
17655
|
partialExecuted: data.signal.partialExecuted,
|
|
17345
17656
|
closeTimestamp: data.closeTimestamp,
|
|
17346
17657
|
duration: durationMin,
|
|
@@ -20486,6 +20797,8 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
20486
20797
|
priceStopLoss: data.priceStopLoss,
|
|
20487
20798
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
20488
20799
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
20800
|
+
totalEntries: data.totalEntries,
|
|
20801
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
20489
20802
|
partialExecuted: data.partialExecuted,
|
|
20490
20803
|
note: data.note,
|
|
20491
20804
|
pendingAt: data.pendingAt,
|
|
@@ -20520,6 +20833,8 @@ let ReportStorage$3 = class ReportStorage {
|
|
|
20520
20833
|
priceStopLoss: data.priceStopLoss,
|
|
20521
20834
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
20522
20835
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
20836
|
+
totalEntries: data.totalEntries,
|
|
20837
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
20523
20838
|
partialExecuted: data.partialExecuted,
|
|
20524
20839
|
note: data.note,
|
|
20525
20840
|
pendingAt: data.pendingAt,
|
|
@@ -21619,6 +21934,8 @@ let ReportStorage$2 = class ReportStorage {
|
|
|
21619
21934
|
priceStopLoss: data.priceStopLoss,
|
|
21620
21935
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
21621
21936
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
21937
|
+
totalEntries: data.totalEntries,
|
|
21938
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
21622
21939
|
partialExecuted: data.partialExecuted,
|
|
21623
21940
|
note: data.note,
|
|
21624
21941
|
pendingAt: data.pendingAt,
|
|
@@ -23622,6 +23939,8 @@ class ScheduleReportService {
|
|
|
23622
23939
|
priceStopLoss: data.signal?.priceStopLoss,
|
|
23623
23940
|
originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
|
|
23624
23941
|
originalPriceStopLoss: data.signal?.originalPriceStopLoss,
|
|
23942
|
+
totalEntries: data.signal?.totalEntries,
|
|
23943
|
+
originalPriceOpen: data.signal?.originalPriceOpen,
|
|
23625
23944
|
partialExecuted: data.signal?.partialExecuted,
|
|
23626
23945
|
pendingAt: data.signal?.pendingAt,
|
|
23627
23946
|
minuteEstimatedTime: data.signal?.minuteEstimatedTime,
|
|
@@ -23666,6 +23985,8 @@ class ScheduleReportService {
|
|
|
23666
23985
|
priceStopLoss: data.signal?.priceStopLoss,
|
|
23667
23986
|
originalPriceTakeProfit: data.signal?.originalPriceTakeProfit,
|
|
23668
23987
|
originalPriceStopLoss: data.signal?.originalPriceStopLoss,
|
|
23988
|
+
totalEntries: data.signal?.totalEntries,
|
|
23989
|
+
originalPriceOpen: data.signal?.originalPriceOpen,
|
|
23669
23990
|
partialExecuted: data.signal?.partialExecuted,
|
|
23670
23991
|
scheduledAt: data.signal?.scheduledAt,
|
|
23671
23992
|
pendingAt: data.signal?.pendingAt,
|
|
@@ -24143,6 +24464,8 @@ class PartialReportService {
|
|
|
24143
24464
|
priceStopLoss: data.data.priceStopLoss,
|
|
24144
24465
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
24145
24466
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
24467
|
+
totalEntries: data.data.totalEntries,
|
|
24468
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
24146
24469
|
partialExecuted: data.data.partialExecuted,
|
|
24147
24470
|
_partial: data.data._partial,
|
|
24148
24471
|
note: data.data.note,
|
|
@@ -24184,6 +24507,8 @@ class PartialReportService {
|
|
|
24184
24507
|
priceStopLoss: data.data.priceStopLoss,
|
|
24185
24508
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
24186
24509
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
24510
|
+
totalEntries: data.data.totalEntries,
|
|
24511
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
24187
24512
|
partialExecuted: data.data.partialExecuted,
|
|
24188
24513
|
_partial: data.data._partial,
|
|
24189
24514
|
note: data.data.note,
|
|
@@ -24306,6 +24631,8 @@ class BreakevenReportService {
|
|
|
24306
24631
|
priceStopLoss: data.data.priceStopLoss,
|
|
24307
24632
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
24308
24633
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
24634
|
+
totalEntries: data.data.totalEntries,
|
|
24635
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
24309
24636
|
partialExecuted: data.data.partialExecuted,
|
|
24310
24637
|
_partial: data.data._partial,
|
|
24311
24638
|
note: data.data.note,
|
|
@@ -24614,7 +24941,7 @@ class StrategyReportService {
|
|
|
24614
24941
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
24615
24942
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24616
24943
|
*/
|
|
24617
|
-
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) => {
|
|
24618
24945
|
this.loggerService.log("strategyReportService partialProfit", {
|
|
24619
24946
|
symbol,
|
|
24620
24947
|
percentToClose,
|
|
@@ -24646,6 +24973,8 @@ class StrategyReportService {
|
|
|
24646
24973
|
priceStopLoss,
|
|
24647
24974
|
originalPriceTakeProfit,
|
|
24648
24975
|
originalPriceStopLoss,
|
|
24976
|
+
originalPriceOpen,
|
|
24977
|
+
totalEntries,
|
|
24649
24978
|
scheduledAt,
|
|
24650
24979
|
pendingAt,
|
|
24651
24980
|
}, {
|
|
@@ -24675,7 +25004,7 @@ class StrategyReportService {
|
|
|
24675
25004
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
24676
25005
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24677
25006
|
*/
|
|
24678
|
-
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) => {
|
|
24679
25008
|
this.loggerService.log("strategyReportService partialLoss", {
|
|
24680
25009
|
symbol,
|
|
24681
25010
|
percentToClose,
|
|
@@ -24707,6 +25036,8 @@ class StrategyReportService {
|
|
|
24707
25036
|
priceStopLoss,
|
|
24708
25037
|
originalPriceTakeProfit,
|
|
24709
25038
|
originalPriceStopLoss,
|
|
25039
|
+
originalPriceOpen,
|
|
25040
|
+
totalEntries,
|
|
24710
25041
|
scheduledAt,
|
|
24711
25042
|
pendingAt,
|
|
24712
25043
|
}, {
|
|
@@ -24736,7 +25067,7 @@ class StrategyReportService {
|
|
|
24736
25067
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
24737
25068
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24738
25069
|
*/
|
|
24739
|
-
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) => {
|
|
24740
25071
|
this.loggerService.log("strategyReportService trailingStop", {
|
|
24741
25072
|
symbol,
|
|
24742
25073
|
percentShift,
|
|
@@ -24768,6 +25099,8 @@ class StrategyReportService {
|
|
|
24768
25099
|
priceStopLoss,
|
|
24769
25100
|
originalPriceTakeProfit,
|
|
24770
25101
|
originalPriceStopLoss,
|
|
25102
|
+
originalPriceOpen,
|
|
25103
|
+
totalEntries,
|
|
24771
25104
|
scheduledAt,
|
|
24772
25105
|
pendingAt,
|
|
24773
25106
|
}, {
|
|
@@ -24797,7 +25130,7 @@ class StrategyReportService {
|
|
|
24797
25130
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
24798
25131
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24799
25132
|
*/
|
|
24800
|
-
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) => {
|
|
24801
25134
|
this.loggerService.log("strategyReportService trailingTake", {
|
|
24802
25135
|
symbol,
|
|
24803
25136
|
percentShift,
|
|
@@ -24829,6 +25162,8 @@ class StrategyReportService {
|
|
|
24829
25162
|
priceStopLoss,
|
|
24830
25163
|
originalPriceTakeProfit,
|
|
24831
25164
|
originalPriceStopLoss,
|
|
25165
|
+
originalPriceOpen,
|
|
25166
|
+
totalEntries,
|
|
24832
25167
|
scheduledAt,
|
|
24833
25168
|
pendingAt,
|
|
24834
25169
|
}, {
|
|
@@ -24857,7 +25192,7 @@ class StrategyReportService {
|
|
|
24857
25192
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
24858
25193
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24859
25194
|
*/
|
|
24860
|
-
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) => {
|
|
24861
25196
|
this.loggerService.log("strategyReportService breakeven", {
|
|
24862
25197
|
symbol,
|
|
24863
25198
|
currentPrice,
|
|
@@ -24887,6 +25222,8 @@ class StrategyReportService {
|
|
|
24887
25222
|
priceStopLoss,
|
|
24888
25223
|
originalPriceTakeProfit,
|
|
24889
25224
|
originalPriceStopLoss,
|
|
25225
|
+
originalPriceOpen,
|
|
25226
|
+
totalEntries,
|
|
24890
25227
|
scheduledAt,
|
|
24891
25228
|
pendingAt,
|
|
24892
25229
|
}, {
|
|
@@ -24916,7 +25253,7 @@ class StrategyReportService {
|
|
|
24916
25253
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
24917
25254
|
* @param activateId - Optional identifier for the activation reason
|
|
24918
25255
|
*/
|
|
24919
|
-
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) => {
|
|
24920
25257
|
this.loggerService.log("strategyReportService activateScheduled", {
|
|
24921
25258
|
symbol,
|
|
24922
25259
|
currentPrice,
|
|
@@ -24948,6 +25285,8 @@ class StrategyReportService {
|
|
|
24948
25285
|
priceStopLoss,
|
|
24949
25286
|
originalPriceTakeProfit,
|
|
24950
25287
|
originalPriceStopLoss,
|
|
25288
|
+
originalPriceOpen,
|
|
25289
|
+
totalEntries,
|
|
24951
25290
|
scheduledAt,
|
|
24952
25291
|
pendingAt,
|
|
24953
25292
|
}, {
|
|
@@ -24959,6 +25298,71 @@ class StrategyReportService {
|
|
|
24959
25298
|
walkerName: "",
|
|
24960
25299
|
});
|
|
24961
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
|
+
};
|
|
24962
25366
|
/**
|
|
24963
25367
|
* Initializes the service for event logging.
|
|
24964
25368
|
*
|
|
@@ -24989,43 +25393,50 @@ class StrategyReportService {
|
|
|
24989
25393
|
exchangeName: event.exchangeName,
|
|
24990
25394
|
frameName: event.frameName,
|
|
24991
25395
|
strategyName: event.strategyName,
|
|
24992
|
-
}, 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));
|
|
24993
25397
|
const unPartialLoss = strategyCommitSubject
|
|
24994
25398
|
.filter(({ action }) => action === "partial-loss")
|
|
24995
25399
|
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
24996
25400
|
exchangeName: event.exchangeName,
|
|
24997
25401
|
frameName: event.frameName,
|
|
24998
25402
|
strategyName: event.strategyName,
|
|
24999
|
-
}, 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));
|
|
25000
25404
|
const unTrailingStop = strategyCommitSubject
|
|
25001
25405
|
.filter(({ action }) => action === "trailing-stop")
|
|
25002
25406
|
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
25003
25407
|
exchangeName: event.exchangeName,
|
|
25004
25408
|
frameName: event.frameName,
|
|
25005
25409
|
strategyName: event.strategyName,
|
|
25006
|
-
}, 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));
|
|
25007
25411
|
const unTrailingTake = strategyCommitSubject
|
|
25008
25412
|
.filter(({ action }) => action === "trailing-take")
|
|
25009
25413
|
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
25010
25414
|
exchangeName: event.exchangeName,
|
|
25011
25415
|
frameName: event.frameName,
|
|
25012
25416
|
strategyName: event.strategyName,
|
|
25013
|
-
}, 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));
|
|
25014
25418
|
const unBreakeven = strategyCommitSubject
|
|
25015
25419
|
.filter(({ action }) => action === "breakeven")
|
|
25016
25420
|
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
25017
25421
|
exchangeName: event.exchangeName,
|
|
25018
25422
|
frameName: event.frameName,
|
|
25019
25423
|
strategyName: event.strategyName,
|
|
25020
|
-
}, 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));
|
|
25021
25425
|
const unActivateScheduled = strategyCommitSubject
|
|
25022
25426
|
.filter(({ action }) => action === "activate-scheduled")
|
|
25023
25427
|
.connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
|
|
25024
25428
|
exchangeName: event.exchangeName,
|
|
25025
25429
|
frameName: event.frameName,
|
|
25026
25430
|
strategyName: event.strategyName,
|
|
25027
|
-
}, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
|
|
25028
|
-
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());
|
|
25029
25440
|
return () => {
|
|
25030
25441
|
disposeFn();
|
|
25031
25442
|
this.subscribe.clear();
|
|
@@ -25157,6 +25568,7 @@ class ReportStorage {
|
|
|
25157
25568
|
trailingTakeCount: 0,
|
|
25158
25569
|
breakevenCount: 0,
|
|
25159
25570
|
activateScheduledCount: 0,
|
|
25571
|
+
averageBuyCount: 0,
|
|
25160
25572
|
};
|
|
25161
25573
|
}
|
|
25162
25574
|
return {
|
|
@@ -25170,6 +25582,7 @@ class ReportStorage {
|
|
|
25170
25582
|
trailingTakeCount: this._eventList.filter(e => e.action === "trailing-take").length,
|
|
25171
25583
|
breakevenCount: this._eventList.filter(e => e.action === "breakeven").length,
|
|
25172
25584
|
activateScheduledCount: this._eventList.filter(e => e.action === "activate-scheduled").length,
|
|
25585
|
+
averageBuyCount: this._eventList.filter(e => e.action === "average-buy").length,
|
|
25173
25586
|
};
|
|
25174
25587
|
}
|
|
25175
25588
|
/**
|
|
@@ -25219,6 +25632,7 @@ class ReportStorage {
|
|
|
25219
25632
|
`- Trailing take: ${stats.trailingTakeCount}`,
|
|
25220
25633
|
`- Breakeven: ${stats.breakevenCount}`,
|
|
25221
25634
|
`- Activate scheduled: ${stats.activateScheduledCount}`,
|
|
25635
|
+
`- Average buy: ${stats.averageBuyCount}`,
|
|
25222
25636
|
].join("\n");
|
|
25223
25637
|
}
|
|
25224
25638
|
/**
|
|
@@ -25406,7 +25820,7 @@ class StrategyMarkdownService {
|
|
|
25406
25820
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
25407
25821
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25408
25822
|
*/
|
|
25409
|
-
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) => {
|
|
25410
25824
|
this.loggerService.log("strategyMarkdownService partialProfit", {
|
|
25411
25825
|
symbol,
|
|
25412
25826
|
percentToClose,
|
|
@@ -25444,6 +25858,8 @@ class StrategyMarkdownService {
|
|
|
25444
25858
|
priceStopLoss,
|
|
25445
25859
|
originalPriceTakeProfit,
|
|
25446
25860
|
originalPriceStopLoss,
|
|
25861
|
+
originalPriceOpen,
|
|
25862
|
+
totalEntries,
|
|
25447
25863
|
scheduledAt,
|
|
25448
25864
|
pendingAt,
|
|
25449
25865
|
});
|
|
@@ -25466,7 +25882,7 @@ class StrategyMarkdownService {
|
|
|
25466
25882
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
25467
25883
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25468
25884
|
*/
|
|
25469
|
-
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) => {
|
|
25470
25886
|
this.loggerService.log("strategyMarkdownService partialLoss", {
|
|
25471
25887
|
symbol,
|
|
25472
25888
|
percentToClose,
|
|
@@ -25504,6 +25920,8 @@ class StrategyMarkdownService {
|
|
|
25504
25920
|
priceStopLoss,
|
|
25505
25921
|
originalPriceTakeProfit,
|
|
25506
25922
|
originalPriceStopLoss,
|
|
25923
|
+
originalPriceOpen,
|
|
25924
|
+
totalEntries,
|
|
25507
25925
|
scheduledAt,
|
|
25508
25926
|
pendingAt,
|
|
25509
25927
|
});
|
|
@@ -25526,7 +25944,7 @@ class StrategyMarkdownService {
|
|
|
25526
25944
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
25527
25945
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25528
25946
|
*/
|
|
25529
|
-
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) => {
|
|
25530
25948
|
this.loggerService.log("strategyMarkdownService trailingStop", {
|
|
25531
25949
|
symbol,
|
|
25532
25950
|
percentShift,
|
|
@@ -25564,6 +25982,8 @@ class StrategyMarkdownService {
|
|
|
25564
25982
|
priceStopLoss,
|
|
25565
25983
|
originalPriceTakeProfit,
|
|
25566
25984
|
originalPriceStopLoss,
|
|
25985
|
+
originalPriceOpen,
|
|
25986
|
+
totalEntries,
|
|
25567
25987
|
scheduledAt,
|
|
25568
25988
|
pendingAt,
|
|
25569
25989
|
});
|
|
@@ -25586,7 +26006,7 @@ class StrategyMarkdownService {
|
|
|
25586
26006
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
25587
26007
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25588
26008
|
*/
|
|
25589
|
-
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) => {
|
|
25590
26010
|
this.loggerService.log("strategyMarkdownService trailingTake", {
|
|
25591
26011
|
symbol,
|
|
25592
26012
|
percentShift,
|
|
@@ -25624,6 +26044,8 @@ class StrategyMarkdownService {
|
|
|
25624
26044
|
priceStopLoss,
|
|
25625
26045
|
originalPriceTakeProfit,
|
|
25626
26046
|
originalPriceStopLoss,
|
|
26047
|
+
originalPriceOpen,
|
|
26048
|
+
totalEntries,
|
|
25627
26049
|
scheduledAt,
|
|
25628
26050
|
pendingAt,
|
|
25629
26051
|
});
|
|
@@ -25645,7 +26067,7 @@ class StrategyMarkdownService {
|
|
|
25645
26067
|
* @param scheduledAt - Signal creation timestamp in milliseconds
|
|
25646
26068
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25647
26069
|
*/
|
|
25648
|
-
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) => {
|
|
25649
26071
|
this.loggerService.log("strategyMarkdownService breakeven", {
|
|
25650
26072
|
symbol,
|
|
25651
26073
|
currentPrice,
|
|
@@ -25681,6 +26103,8 @@ class StrategyMarkdownService {
|
|
|
25681
26103
|
priceStopLoss,
|
|
25682
26104
|
originalPriceTakeProfit,
|
|
25683
26105
|
originalPriceStopLoss,
|
|
26106
|
+
originalPriceOpen,
|
|
26107
|
+
totalEntries,
|
|
25684
26108
|
scheduledAt,
|
|
25685
26109
|
pendingAt,
|
|
25686
26110
|
});
|
|
@@ -25703,7 +26127,7 @@ class StrategyMarkdownService {
|
|
|
25703
26127
|
* @param pendingAt - Pending timestamp in milliseconds
|
|
25704
26128
|
* @param activateId - Optional identifier for the activation reason
|
|
25705
26129
|
*/
|
|
25706
|
-
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) => {
|
|
25707
26131
|
this.loggerService.log("strategyMarkdownService activateScheduled", {
|
|
25708
26132
|
symbol,
|
|
25709
26133
|
currentPrice,
|
|
@@ -25741,6 +26165,72 @@ class StrategyMarkdownService {
|
|
|
25741
26165
|
priceStopLoss,
|
|
25742
26166
|
originalPriceTakeProfit,
|
|
25743
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,
|
|
25744
26234
|
scheduledAt,
|
|
25745
26235
|
pendingAt,
|
|
25746
26236
|
});
|
|
@@ -25889,43 +26379,50 @@ class StrategyMarkdownService {
|
|
|
25889
26379
|
exchangeName: event.exchangeName,
|
|
25890
26380
|
frameName: event.frameName,
|
|
25891
26381
|
strategyName: event.strategyName,
|
|
25892
|
-
}, 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));
|
|
25893
26383
|
const unPartialLoss = strategyCommitSubject
|
|
25894
26384
|
.filter(({ action }) => action === "partial-loss")
|
|
25895
26385
|
.connect(async (event) => await this.partialLoss(event.symbol, event.percentToClose, event.currentPrice, event.backtest, {
|
|
25896
26386
|
exchangeName: event.exchangeName,
|
|
25897
26387
|
frameName: event.frameName,
|
|
25898
26388
|
strategyName: event.strategyName,
|
|
25899
|
-
}, 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));
|
|
25900
26390
|
const unTrailingStop = strategyCommitSubject
|
|
25901
26391
|
.filter(({ action }) => action === "trailing-stop")
|
|
25902
26392
|
.connect(async (event) => await this.trailingStop(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
25903
26393
|
exchangeName: event.exchangeName,
|
|
25904
26394
|
frameName: event.frameName,
|
|
25905
26395
|
strategyName: event.strategyName,
|
|
25906
|
-
}, 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));
|
|
25907
26397
|
const unTrailingTake = strategyCommitSubject
|
|
25908
26398
|
.filter(({ action }) => action === "trailing-take")
|
|
25909
26399
|
.connect(async (event) => await this.trailingTake(event.symbol, event.percentShift, event.currentPrice, event.backtest, {
|
|
25910
26400
|
exchangeName: event.exchangeName,
|
|
25911
26401
|
frameName: event.frameName,
|
|
25912
26402
|
strategyName: event.strategyName,
|
|
25913
|
-
}, 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));
|
|
25914
26404
|
const unBreakeven = strategyCommitSubject
|
|
25915
26405
|
.filter(({ action }) => action === "breakeven")
|
|
25916
26406
|
.connect(async (event) => await this.breakeven(event.symbol, event.currentPrice, event.backtest, {
|
|
25917
26407
|
exchangeName: event.exchangeName,
|
|
25918
26408
|
frameName: event.frameName,
|
|
25919
26409
|
strategyName: event.strategyName,
|
|
25920
|
-
}, 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));
|
|
25921
26411
|
const unActivateScheduled = strategyCommitSubject
|
|
25922
26412
|
.filter(({ action }) => action === "activate-scheduled")
|
|
25923
26413
|
.connect(async (event) => await this.activateScheduled(event.symbol, event.currentPrice, event.backtest, {
|
|
25924
26414
|
exchangeName: event.exchangeName,
|
|
25925
26415
|
frameName: event.frameName,
|
|
25926
26416
|
strategyName: event.strategyName,
|
|
25927
|
-
}, event.timestamp, event.position, event.priceOpen, event.priceTakeProfit, event.priceStopLoss, event.originalPriceTakeProfit, event.originalPriceStopLoss, event.scheduledAt, event.pendingAt, event.activateId));
|
|
25928
|
-
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());
|
|
25929
26426
|
return () => {
|
|
25930
26427
|
disposeFn();
|
|
25931
26428
|
this.subscribe.clear();
|
|
@@ -27731,6 +28228,7 @@ const TRAILING_STOP_METHOD_NAME = "strategy.commitTrailingStop";
|
|
|
27731
28228
|
const TRAILING_PROFIT_METHOD_NAME = "strategy.commitTrailingTake";
|
|
27732
28229
|
const BREAKEVEN_METHOD_NAME = "strategy.commitBreakeven";
|
|
27733
28230
|
const ACTIVATE_SCHEDULED_METHOD_NAME = "strategy.commitActivateScheduled";
|
|
28231
|
+
const AVERAGE_BUY_METHOD_NAME = "strategy.commitAverageBuy";
|
|
27734
28232
|
/**
|
|
27735
28233
|
* Cancels the scheduled signal without stopping the strategy.
|
|
27736
28234
|
*
|
|
@@ -28083,6 +28581,45 @@ async function commitActivateScheduled(symbol, activateId) {
|
|
|
28083
28581
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
28084
28582
|
await bt.strategyCoreService.activateScheduled(isBacktest, symbol, { exchangeName, frameName, strategyName }, activateId);
|
|
28085
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
|
+
}
|
|
28086
28623
|
|
|
28087
28624
|
const STOP_STRATEGY_METHOD_NAME = "control.stopStrategy";
|
|
28088
28625
|
/**
|
|
@@ -30363,6 +30900,7 @@ const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.commitPartialLoss";
|
|
|
30363
30900
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.commitTrailingStop";
|
|
30364
30901
|
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.commitTrailingTake";
|
|
30365
30902
|
const BACKTEST_METHOD_NAME_ACTIVATE_SCHEDULED = "Backtest.commitActivateScheduled";
|
|
30903
|
+
const BACKTEST_METHOD_NAME_AVERAGE_BUY = "Backtest.commitAverageBuy";
|
|
30366
30904
|
const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
|
|
30367
30905
|
/**
|
|
30368
30906
|
* Internal task function that runs backtest and handles completion.
|
|
@@ -31259,6 +31797,49 @@ class BacktestUtils {
|
|
|
31259
31797
|
}
|
|
31260
31798
|
await bt.strategyCoreService.activateScheduled(true, symbol, context, activateId);
|
|
31261
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
|
+
};
|
|
31262
31843
|
/**
|
|
31263
31844
|
* Gets statistical data from all closed signals for a symbol-strategy pair.
|
|
31264
31845
|
*
|
|
@@ -31435,6 +32016,7 @@ const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.commitPartialLoss";
|
|
|
31435
32016
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.commitTrailingStop";
|
|
31436
32017
|
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.commitTrailingTake";
|
|
31437
32018
|
const LIVE_METHOD_NAME_ACTIVATE_SCHEDULED = "Live.commitActivateScheduled";
|
|
32019
|
+
const LIVE_METHOD_NAME_AVERAGE_BUY = "Live.commitAverageBuy";
|
|
31438
32020
|
/**
|
|
31439
32021
|
* Internal task function that runs live trading and handles completion.
|
|
31440
32022
|
* Consumes live trading results and updates instance state flags.
|
|
@@ -32299,6 +32881,49 @@ class LiveUtils {
|
|
|
32299
32881
|
frameName: "",
|
|
32300
32882
|
}, activateId);
|
|
32301
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
|
+
};
|
|
32302
32927
|
/**
|
|
32303
32928
|
* Gets statistical data from all live trading events for a symbol-strategy pair.
|
|
32304
32929
|
*
|
|
@@ -34962,6 +35587,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
34962
35587
|
priceStopLoss: data.signal.priceStopLoss,
|
|
34963
35588
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34964
35589
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
35590
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
35591
|
+
totalEntries: data.signal.totalEntries,
|
|
34965
35592
|
note: data.signal.note,
|
|
34966
35593
|
scheduledAt: data.signal.scheduledAt,
|
|
34967
35594
|
pendingAt: data.signal.pendingAt,
|
|
@@ -34987,6 +35614,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
34987
35614
|
priceStopLoss: data.signal.priceStopLoss,
|
|
34988
35615
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
34989
35616
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
35617
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
35618
|
+
totalEntries: data.signal.totalEntries,
|
|
34990
35619
|
pnlPercentage: data.pnl.pnlPercentage,
|
|
34991
35620
|
closeReason: data.closeReason,
|
|
34992
35621
|
duration: durationMin,
|
|
@@ -35012,6 +35641,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
35012
35641
|
priceStopLoss: data.signal.priceStopLoss,
|
|
35013
35642
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
35014
35643
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
35644
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
35645
|
+
totalEntries: data.signal.totalEntries,
|
|
35015
35646
|
scheduledAt: data.signal.scheduledAt,
|
|
35016
35647
|
currentPrice: data.currentPrice,
|
|
35017
35648
|
createdAt: data.createdAt,
|
|
@@ -35035,6 +35666,8 @@ const CREATE_SIGNAL_NOTIFICATION_FN = (data) => {
|
|
|
35035
35666
|
priceStopLoss: data.signal.priceStopLoss,
|
|
35036
35667
|
originalPriceTakeProfit: data.signal.originalPriceTakeProfit,
|
|
35037
35668
|
originalPriceStopLoss: data.signal.originalPriceStopLoss,
|
|
35669
|
+
originalPriceOpen: data.signal.originalPriceOpen,
|
|
35670
|
+
totalEntries: data.signal.totalEntries,
|
|
35038
35671
|
cancelReason: data.reason,
|
|
35039
35672
|
cancelId: data.cancelId,
|
|
35040
35673
|
duration: durationMin,
|
|
@@ -35067,6 +35700,8 @@ const CREATE_PARTIAL_PROFIT_NOTIFICATION_FN = (data) => ({
|
|
|
35067
35700
|
priceStopLoss: data.data.priceStopLoss,
|
|
35068
35701
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
35069
35702
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
35703
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
35704
|
+
totalEntries: data.data.totalEntries,
|
|
35070
35705
|
scheduledAt: data.data.scheduledAt,
|
|
35071
35706
|
pendingAt: data.data.pendingAt,
|
|
35072
35707
|
createdAt: data.timestamp,
|
|
@@ -35093,6 +35728,8 @@ const CREATE_PARTIAL_LOSS_NOTIFICATION_FN = (data) => ({
|
|
|
35093
35728
|
priceStopLoss: data.data.priceStopLoss,
|
|
35094
35729
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
35095
35730
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
35731
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
35732
|
+
totalEntries: data.data.totalEntries,
|
|
35096
35733
|
scheduledAt: data.data.scheduledAt,
|
|
35097
35734
|
pendingAt: data.data.pendingAt,
|
|
35098
35735
|
createdAt: data.timestamp,
|
|
@@ -35118,6 +35755,8 @@ const CREATE_BREAKEVEN_NOTIFICATION_FN = (data) => ({
|
|
|
35118
35755
|
priceStopLoss: data.data.priceStopLoss,
|
|
35119
35756
|
originalPriceTakeProfit: data.data.originalPriceTakeProfit,
|
|
35120
35757
|
originalPriceStopLoss: data.data.originalPriceStopLoss,
|
|
35758
|
+
originalPriceOpen: data.data.originalPriceOpen,
|
|
35759
|
+
totalEntries: data.data.totalEntries,
|
|
35121
35760
|
scheduledAt: data.data.scheduledAt,
|
|
35122
35761
|
pendingAt: data.data.pendingAt,
|
|
35123
35762
|
createdAt: data.timestamp,
|
|
@@ -35148,6 +35787,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35148
35787
|
priceStopLoss: data.priceStopLoss,
|
|
35149
35788
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35150
35789
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35790
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
35791
|
+
totalEntries: data.totalEntries,
|
|
35151
35792
|
scheduledAt: data.scheduledAt,
|
|
35152
35793
|
pendingAt: data.pendingAt,
|
|
35153
35794
|
createdAt: data.timestamp,
|
|
@@ -35171,6 +35812,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35171
35812
|
priceStopLoss: data.priceStopLoss,
|
|
35172
35813
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35173
35814
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35815
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
35816
|
+
totalEntries: data.totalEntries,
|
|
35174
35817
|
scheduledAt: data.scheduledAt,
|
|
35175
35818
|
pendingAt: data.pendingAt,
|
|
35176
35819
|
createdAt: data.timestamp,
|
|
@@ -35193,6 +35836,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35193
35836
|
priceStopLoss: data.priceStopLoss,
|
|
35194
35837
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35195
35838
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35839
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
35840
|
+
totalEntries: data.totalEntries,
|
|
35196
35841
|
scheduledAt: data.scheduledAt,
|
|
35197
35842
|
pendingAt: data.pendingAt,
|
|
35198
35843
|
createdAt: data.timestamp,
|
|
@@ -35216,6 +35861,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35216
35861
|
priceStopLoss: data.priceStopLoss,
|
|
35217
35862
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35218
35863
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35864
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
35865
|
+
totalEntries: data.totalEntries,
|
|
35219
35866
|
scheduledAt: data.scheduledAt,
|
|
35220
35867
|
pendingAt: data.pendingAt,
|
|
35221
35868
|
createdAt: data.timestamp,
|
|
@@ -35239,6 +35886,8 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35239
35886
|
priceStopLoss: data.priceStopLoss,
|
|
35240
35887
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35241
35888
|
originalPriceStopLoss: data.originalPriceStopLoss,
|
|
35889
|
+
originalPriceOpen: data.originalPriceOpen,
|
|
35890
|
+
totalEntries: data.totalEntries,
|
|
35242
35891
|
scheduledAt: data.scheduledAt,
|
|
35243
35892
|
pendingAt: data.pendingAt,
|
|
35244
35893
|
createdAt: data.timestamp,
|
|
@@ -35262,6 +35911,33 @@ const CREATE_STRATEGY_COMMIT_NOTIFICATION_FN = (data) => {
|
|
|
35262
35911
|
priceStopLoss: data.priceStopLoss,
|
|
35263
35912
|
originalPriceTakeProfit: data.originalPriceTakeProfit,
|
|
35264
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,
|
|
35265
35941
|
scheduledAt: data.scheduledAt,
|
|
35266
35942
|
pendingAt: data.pendingAt,
|
|
35267
35943
|
createdAt: data.timestamp,
|
|
@@ -37651,6 +38327,7 @@ exports.addWalkerSchema = addWalkerSchema;
|
|
|
37651
38327
|
exports.alignToInterval = alignToInterval;
|
|
37652
38328
|
exports.checkCandles = checkCandles;
|
|
37653
38329
|
exports.commitActivateScheduled = commitActivateScheduled;
|
|
38330
|
+
exports.commitAverageBuy = commitAverageBuy;
|
|
37654
38331
|
exports.commitBreakeven = commitBreakeven;
|
|
37655
38332
|
exports.commitCancelScheduled = commitCancelScheduled;
|
|
37656
38333
|
exports.commitClosePending = commitClosePending;
|