backtest-kit 1.10.1 → 1.10.3
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/LICENSE +21 -21
- package/README.md +259 -259
- package/build/index.cjs +1002 -98
- package/build/index.mjs +1002 -99
- package/package.json +81 -81
- package/types.d.ts +397 -28
package/build/index.mjs
CHANGED
|
@@ -2122,8 +2122,10 @@ const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
|
2122
2122
|
* It hides internal implementation details while exposing effective values:
|
|
2123
2123
|
*
|
|
2124
2124
|
* - Replaces internal _trailingPriceStopLoss with effective priceStopLoss
|
|
2125
|
+
* - Replaces internal _trailingPriceTakeProfit with effective priceTakeProfit
|
|
2125
2126
|
* - Preserves original stop-loss in originalPriceStopLoss for reference
|
|
2126
|
-
* -
|
|
2127
|
+
* - Preserves original take-profit in originalPriceTakeProfit for reference
|
|
2128
|
+
* - Ensures external code never sees private _trailing* fields
|
|
2127
2129
|
* - Maintains backward compatibility with non-trailing positions
|
|
2128
2130
|
*
|
|
2129
2131
|
* Key differences from TO_RISK_SIGNAL (in ClientRisk.ts):
|
|
@@ -2138,34 +2140,37 @@ const TIMEOUT_SYMBOL = Symbol('timeout');
|
|
|
2138
2140
|
* - Event emissions and logging
|
|
2139
2141
|
* - Integration with ClientPartial and ClientRisk
|
|
2140
2142
|
*
|
|
2141
|
-
* @param signal - Internal signal row with optional trailing stop-loss
|
|
2142
|
-
* @returns Signal in IPublicSignalRow format with effective
|
|
2143
|
+
* @param signal - Internal signal row with optional trailing stop-loss/take-profit
|
|
2144
|
+
* @returns Signal in IPublicSignalRow format with effective SL/TP and hidden internals
|
|
2143
2145
|
*
|
|
2144
2146
|
* @example
|
|
2145
2147
|
* ```typescript
|
|
2146
|
-
* // Signal without trailing SL
|
|
2148
|
+
* // Signal without trailing SL/TP
|
|
2147
2149
|
* const publicSignal = TO_PUBLIC_SIGNAL(signal);
|
|
2148
2150
|
* // publicSignal.priceStopLoss = signal.priceStopLoss
|
|
2151
|
+
* // publicSignal.priceTakeProfit = signal.priceTakeProfit
|
|
2149
2152
|
* // publicSignal.originalPriceStopLoss = signal.priceStopLoss
|
|
2153
|
+
* // publicSignal.originalPriceTakeProfit = signal.priceTakeProfit
|
|
2150
2154
|
*
|
|
2151
|
-
* // Signal with trailing SL
|
|
2155
|
+
* // Signal with trailing SL/TP
|
|
2152
2156
|
* const publicSignal = TO_PUBLIC_SIGNAL(signalWithTrailing);
|
|
2153
2157
|
* // publicSignal.priceStopLoss = signal._trailingPriceStopLoss (effective)
|
|
2158
|
+
* // publicSignal.priceTakeProfit = signal._trailingPriceTakeProfit (effective)
|
|
2154
2159
|
* // publicSignal.originalPriceStopLoss = signal.priceStopLoss (original)
|
|
2160
|
+
* // publicSignal.originalPriceTakeProfit = signal.priceTakeProfit (original)
|
|
2155
2161
|
* // publicSignal._trailingPriceStopLoss = undefined (hidden from external API)
|
|
2162
|
+
* // publicSignal._trailingPriceTakeProfit = undefined (hidden from external API)
|
|
2156
2163
|
* ```
|
|
2157
2164
|
*/
|
|
2158
2165
|
const TO_PUBLIC_SIGNAL = (signal) => {
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
...structuredClone(signal),
|
|
2162
|
-
priceStopLoss: signal._trailingPriceStopLoss,
|
|
2163
|
-
originalPriceStopLoss: signal.priceStopLoss,
|
|
2164
|
-
};
|
|
2165
|
-
}
|
|
2166
|
+
const hasTrailingSL = signal._trailingPriceStopLoss !== undefined;
|
|
2167
|
+
const hasTrailingTP = signal._trailingPriceTakeProfit !== undefined;
|
|
2166
2168
|
return {
|
|
2167
2169
|
...structuredClone(signal),
|
|
2170
|
+
priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
|
|
2171
|
+
priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
|
|
2168
2172
|
originalPriceStopLoss: signal.priceStopLoss,
|
|
2173
|
+
originalPriceTakeProfit: signal.priceTakeProfit,
|
|
2169
2174
|
};
|
|
2170
2175
|
};
|
|
2171
2176
|
const VALIDATE_SIGNAL_FN = (signal, currentPrice, isScheduled) => {
|
|
@@ -2670,33 +2675,29 @@ const PARTIAL_LOSS_FN = (self, signal, percentToClose, currentPrice) => {
|
|
|
2670
2675
|
});
|
|
2671
2676
|
};
|
|
2672
2677
|
const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
2673
|
-
//
|
|
2674
|
-
const
|
|
2675
|
-
// Calculate
|
|
2678
|
+
// Get current effective stop-loss (trailing or original)
|
|
2679
|
+
const currentStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
2680
|
+
// Calculate distance between entry and CURRENT stop-loss AS PERCENTAGE of entry price
|
|
2681
|
+
const currentSlDistancePercent = Math.abs((signal.priceOpen - currentStopLoss) / signal.priceOpen * 100);
|
|
2682
|
+
// Calculate new stop-loss distance percentage by adding shift to CURRENT distance
|
|
2676
2683
|
// Negative percentShift: reduces distance % (tightens stop, moves SL toward entry or beyond)
|
|
2677
2684
|
// Positive percentShift: increases distance % (loosens stop, moves SL away from entry)
|
|
2678
|
-
const newSlDistancePercent =
|
|
2685
|
+
const newSlDistancePercent = currentSlDistancePercent + percentShift;
|
|
2679
2686
|
// Calculate new stop-loss price based on new distance percentage
|
|
2680
2687
|
// Negative newSlDistancePercent means SL crosses entry into profit zone
|
|
2681
2688
|
let newStopLoss;
|
|
2682
2689
|
if (signal.position === "long") {
|
|
2683
2690
|
// LONG: SL is below entry (or above entry if in profit zone)
|
|
2684
2691
|
// Formula: entry * (1 - newDistance%)
|
|
2685
|
-
// Example: entry=100,
|
|
2686
|
-
// Example: entry=100, originalSL=90 (10%), shift=-5% → newDistance=5% → 100 * 0.95 = 95 (tighter)
|
|
2687
|
-
// Example: entry=100, originalSL=90 (10%), shift=+5% → newDistance=15% → 100 * 0.85 = 85 (looser)
|
|
2692
|
+
// Example: entry=100, currentSL=95 (5%), shift=-3% → newDistance=2% → 100 * 0.98 = 98 (tighter)
|
|
2688
2693
|
newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
|
|
2689
2694
|
}
|
|
2690
2695
|
else {
|
|
2691
2696
|
// SHORT: SL is above entry (or below entry if in profit zone)
|
|
2692
2697
|
// Formula: entry * (1 + newDistance%)
|
|
2693
|
-
// Example: entry=100,
|
|
2694
|
-
// Example: entry=100, originalSL=110 (10%), shift=-5% → newDistance=5% → 100 * 1.05 = 105 (tighter)
|
|
2695
|
-
// Example: entry=100, originalSL=110 (10%), shift=+5% → newDistance=15% → 100 * 1.15 = 115 (looser)
|
|
2698
|
+
// Example: entry=100, currentSL=105 (5%), shift=-3% → newDistance=2% → 100 * 1.02 = 102 (tighter)
|
|
2696
2699
|
newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
|
|
2697
2700
|
}
|
|
2698
|
-
// Get current effective stop-loss (trailing or original)
|
|
2699
|
-
const currentStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
2700
2701
|
// Determine if this is the first trailing stop call (direction not set yet)
|
|
2701
2702
|
const isFirstCall = signal._trailingPriceStopLoss === undefined;
|
|
2702
2703
|
if (isFirstCall) {
|
|
@@ -2707,7 +2708,7 @@ const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
|
2707
2708
|
position: signal.position,
|
|
2708
2709
|
priceOpen: signal.priceOpen,
|
|
2709
2710
|
originalStopLoss: signal.priceStopLoss,
|
|
2710
|
-
|
|
2711
|
+
currentDistancePercent: currentSlDistancePercent,
|
|
2711
2712
|
previousStopLoss: currentStopLoss,
|
|
2712
2713
|
newStopLoss,
|
|
2713
2714
|
newDistancePercent: newSlDistancePercent,
|
|
@@ -2718,19 +2719,28 @@ const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
|
2718
2719
|
}
|
|
2719
2720
|
else {
|
|
2720
2721
|
// Subsequent calls: only update if new SL continues in the same direction
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2722
|
+
// Determine initial direction: "closer" or "farther" relative to entry
|
|
2723
|
+
let initialDirection;
|
|
2724
|
+
if (signal.position === "long") {
|
|
2725
|
+
// LONG: closer = SL closer to entry = higher SL value (moving up)
|
|
2726
|
+
initialDirection = signal._trailingPriceStopLoss > signal.priceStopLoss ? "closer" : "farther";
|
|
2727
|
+
}
|
|
2728
|
+
else {
|
|
2729
|
+
// SHORT: closer = SL closer to entry = lower SL value (moving down)
|
|
2730
|
+
initialDirection = signal._trailingPriceStopLoss < signal.priceStopLoss ? "closer" : "farther";
|
|
2731
|
+
}
|
|
2732
|
+
// Determine new direction
|
|
2733
|
+
let newDirection;
|
|
2734
|
+
if (signal.position === "long") {
|
|
2735
|
+
// LONG: closer = higher SL value
|
|
2736
|
+
newDirection = newStopLoss > currentStopLoss ? "closer" : "farther";
|
|
2737
|
+
}
|
|
2738
|
+
else {
|
|
2739
|
+
// SHORT: closer = lower SL value
|
|
2740
|
+
newDirection = newStopLoss < currentStopLoss ? "closer" : "farther";
|
|
2733
2741
|
}
|
|
2742
|
+
// Only allow continuation in same direction
|
|
2743
|
+
const shouldUpdate = initialDirection === newDirection;
|
|
2734
2744
|
if (!shouldUpdate) {
|
|
2735
2745
|
self.params.logger.debug("TRAILING_STOP_FN: new SL not in same direction, skipping", {
|
|
2736
2746
|
signalId: signal.id,
|
|
@@ -2739,7 +2749,7 @@ const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
|
2739
2749
|
newStopLoss,
|
|
2740
2750
|
percentShift,
|
|
2741
2751
|
initialDirection,
|
|
2742
|
-
attemptedDirection:
|
|
2752
|
+
attemptedDirection: newDirection,
|
|
2743
2753
|
});
|
|
2744
2754
|
return;
|
|
2745
2755
|
}
|
|
@@ -2750,7 +2760,7 @@ const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
|
2750
2760
|
position: signal.position,
|
|
2751
2761
|
priceOpen: signal.priceOpen,
|
|
2752
2762
|
originalStopLoss: signal.priceStopLoss,
|
|
2753
|
-
|
|
2763
|
+
currentDistancePercent: currentSlDistancePercent,
|
|
2754
2764
|
previousStopLoss: currentStopLoss,
|
|
2755
2765
|
newStopLoss,
|
|
2756
2766
|
newDistancePercent: newSlDistancePercent,
|
|
@@ -2760,6 +2770,99 @@ const TRAILING_STOP_FN = (self, signal, percentShift) => {
|
|
|
2760
2770
|
});
|
|
2761
2771
|
}
|
|
2762
2772
|
};
|
|
2773
|
+
const TRAILING_PROFIT_FN = (self, signal, percentShift) => {
|
|
2774
|
+
// Get current effective take-profit (trailing or original)
|
|
2775
|
+
const currentTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
2776
|
+
// Calculate distance between entry and CURRENT take-profit AS PERCENTAGE of entry price
|
|
2777
|
+
const currentTpDistancePercent = Math.abs((currentTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
|
|
2778
|
+
// Calculate new take-profit distance percentage by adding shift to CURRENT distance
|
|
2779
|
+
// Negative percentShift: reduces distance % (brings TP closer to entry)
|
|
2780
|
+
// Positive percentShift: increases distance % (moves TP further from entry)
|
|
2781
|
+
const newTpDistancePercent = currentTpDistancePercent + percentShift;
|
|
2782
|
+
// Calculate new take-profit price based on new distance percentage
|
|
2783
|
+
let newTakeProfit;
|
|
2784
|
+
if (signal.position === "long") {
|
|
2785
|
+
// LONG: TP is above entry
|
|
2786
|
+
// Formula: entry * (1 + newDistance%)
|
|
2787
|
+
// Example: entry=100, currentTP=115 (15%), shift=-3% → newDistance=12% → 100 * 1.12 = 112 (closer)
|
|
2788
|
+
newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
|
|
2789
|
+
}
|
|
2790
|
+
else {
|
|
2791
|
+
// SHORT: TP is below entry
|
|
2792
|
+
// Formula: entry * (1 - newDistance%)
|
|
2793
|
+
// Example: entry=100, currentTP=85 (15%), shift=-3% → newDistance=12% → 100 * 0.88 = 88 (closer)
|
|
2794
|
+
newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
|
|
2795
|
+
}
|
|
2796
|
+
// Determine if this is the first trailing profit call (direction not set yet)
|
|
2797
|
+
const isFirstCall = signal._trailingPriceTakeProfit === undefined;
|
|
2798
|
+
if (isFirstCall) {
|
|
2799
|
+
// First call: set the direction and update TP unconditionally
|
|
2800
|
+
signal._trailingPriceTakeProfit = newTakeProfit;
|
|
2801
|
+
self.params.logger.info("TRAILING_PROFIT_FN executed (first call - direction set)", {
|
|
2802
|
+
signalId: signal.id,
|
|
2803
|
+
position: signal.position,
|
|
2804
|
+
priceOpen: signal.priceOpen,
|
|
2805
|
+
originalTakeProfit: signal.priceTakeProfit,
|
|
2806
|
+
currentDistancePercent: currentTpDistancePercent,
|
|
2807
|
+
previousTakeProfit: currentTakeProfit,
|
|
2808
|
+
newTakeProfit,
|
|
2809
|
+
newDistancePercent: newTpDistancePercent,
|
|
2810
|
+
percentShift,
|
|
2811
|
+
direction: newTakeProfit > currentTakeProfit ? "up" : "down",
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
else {
|
|
2815
|
+
// Subsequent calls: only update if new TP continues in the same direction
|
|
2816
|
+
// Determine initial direction: "closer" or "farther" relative to entry
|
|
2817
|
+
let initialDirection;
|
|
2818
|
+
if (signal.position === "long") {
|
|
2819
|
+
// LONG: closer = TP closer to entry = lower TP value
|
|
2820
|
+
initialDirection = signal._trailingPriceTakeProfit < signal.priceTakeProfit ? "closer" : "farther";
|
|
2821
|
+
}
|
|
2822
|
+
else {
|
|
2823
|
+
// SHORT: closer = TP closer to entry = higher TP value
|
|
2824
|
+
initialDirection = signal._trailingPriceTakeProfit > signal.priceTakeProfit ? "closer" : "farther";
|
|
2825
|
+
}
|
|
2826
|
+
// Determine new direction
|
|
2827
|
+
let newDirection;
|
|
2828
|
+
if (signal.position === "long") {
|
|
2829
|
+
// LONG: closer = lower TP value
|
|
2830
|
+
newDirection = newTakeProfit < currentTakeProfit ? "closer" : "farther";
|
|
2831
|
+
}
|
|
2832
|
+
else {
|
|
2833
|
+
// SHORT: closer = higher TP value
|
|
2834
|
+
newDirection = newTakeProfit > currentTakeProfit ? "closer" : "farther";
|
|
2835
|
+
}
|
|
2836
|
+
// Only allow continuation in same direction
|
|
2837
|
+
const shouldUpdate = initialDirection === newDirection;
|
|
2838
|
+
if (!shouldUpdate) {
|
|
2839
|
+
self.params.logger.debug("TRAILING_PROFIT_FN: new TP not in same direction, skipping", {
|
|
2840
|
+
signalId: signal.id,
|
|
2841
|
+
position: signal.position,
|
|
2842
|
+
currentTakeProfit,
|
|
2843
|
+
newTakeProfit,
|
|
2844
|
+
percentShift,
|
|
2845
|
+
initialDirection,
|
|
2846
|
+
attemptedDirection: newDirection,
|
|
2847
|
+
});
|
|
2848
|
+
return;
|
|
2849
|
+
}
|
|
2850
|
+
// Update trailing take-profit
|
|
2851
|
+
signal._trailingPriceTakeProfit = newTakeProfit;
|
|
2852
|
+
self.params.logger.info("TRAILING_PROFIT_FN executed", {
|
|
2853
|
+
signalId: signal.id,
|
|
2854
|
+
position: signal.position,
|
|
2855
|
+
priceOpen: signal.priceOpen,
|
|
2856
|
+
originalTakeProfit: signal.priceTakeProfit,
|
|
2857
|
+
currentDistancePercent: currentTpDistancePercent,
|
|
2858
|
+
previousTakeProfit: currentTakeProfit,
|
|
2859
|
+
newTakeProfit,
|
|
2860
|
+
newDistancePercent: newTpDistancePercent,
|
|
2861
|
+
percentShift,
|
|
2862
|
+
direction: initialDirection,
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
};
|
|
2763
2866
|
const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
2764
2867
|
// Calculate breakeven threshold based on slippage and fees
|
|
2765
2868
|
// Need to cover: entry slippage + entry fee + exit slippage + exit fee
|
|
@@ -2790,6 +2893,19 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
2790
2893
|
const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
|
|
2791
2894
|
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
2792
2895
|
if (isThresholdReached && breakevenPrice > trailingStopLoss) {
|
|
2896
|
+
// Check for price intrusion before setting new SL
|
|
2897
|
+
if (currentPrice < breakevenPrice) {
|
|
2898
|
+
// Price already crossed the breakeven level - skip setting SL
|
|
2899
|
+
self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
|
|
2900
|
+
signalId: signal.id,
|
|
2901
|
+
position: signal.position,
|
|
2902
|
+
priceOpen: signal.priceOpen,
|
|
2903
|
+
breakevenPrice,
|
|
2904
|
+
currentPrice,
|
|
2905
|
+
reason: "currentPrice below breakevenPrice (LONG position)"
|
|
2906
|
+
});
|
|
2907
|
+
return false;
|
|
2908
|
+
}
|
|
2793
2909
|
// Breakeven is better than current trailing SL - upgrade to breakeven
|
|
2794
2910
|
signal._trailingPriceStopLoss = breakevenPrice;
|
|
2795
2911
|
self.params.logger.info("BREAKEVEN_FN: upgraded negative trailing stop to breakeven", {
|
|
@@ -2842,6 +2958,19 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
2842
2958
|
const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
|
|
2843
2959
|
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
2844
2960
|
if (isThresholdReached && breakevenPrice < trailingStopLoss) {
|
|
2961
|
+
// Check for price intrusion before setting new SL
|
|
2962
|
+
if (currentPrice > breakevenPrice) {
|
|
2963
|
+
// Price already crossed the breakeven level - skip setting SL
|
|
2964
|
+
self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
|
|
2965
|
+
signalId: signal.id,
|
|
2966
|
+
position: signal.position,
|
|
2967
|
+
priceOpen: signal.priceOpen,
|
|
2968
|
+
breakevenPrice,
|
|
2969
|
+
currentPrice,
|
|
2970
|
+
reason: "currentPrice above breakevenPrice (SHORT position)"
|
|
2971
|
+
});
|
|
2972
|
+
return false;
|
|
2973
|
+
}
|
|
2845
2974
|
// Breakeven is better than current trailing SL - upgrade to breakeven
|
|
2846
2975
|
signal._trailingPriceStopLoss = breakevenPrice;
|
|
2847
2976
|
self.params.logger.info("BREAKEVEN_FN: upgraded negative trailing stop to breakeven", {
|
|
@@ -2912,6 +3041,31 @@ const BREAKEVEN_FN = (self, signal, currentPrice) => {
|
|
|
2912
3041
|
});
|
|
2913
3042
|
return false;
|
|
2914
3043
|
}
|
|
3044
|
+
// Check for price intrusion before setting new SL
|
|
3045
|
+
if (signal.position === "long" && currentPrice < breakevenPrice) {
|
|
3046
|
+
// LONG: Price already crossed the breakeven level - skip setting SL
|
|
3047
|
+
self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
|
|
3048
|
+
signalId: signal.id,
|
|
3049
|
+
position: signal.position,
|
|
3050
|
+
priceOpen: signal.priceOpen,
|
|
3051
|
+
breakevenPrice,
|
|
3052
|
+
currentPrice,
|
|
3053
|
+
reason: "currentPrice below breakevenPrice (LONG position)"
|
|
3054
|
+
});
|
|
3055
|
+
return false;
|
|
3056
|
+
}
|
|
3057
|
+
if (signal.position === "short" && currentPrice > breakevenPrice) {
|
|
3058
|
+
// SHORT: Price already crossed the breakeven level - skip setting SL
|
|
3059
|
+
self.params.logger.debug("BREAKEVEN_FN: price intrusion detected, skipping SL update", {
|
|
3060
|
+
signalId: signal.id,
|
|
3061
|
+
position: signal.position,
|
|
3062
|
+
priceOpen: signal.priceOpen,
|
|
3063
|
+
breakevenPrice,
|
|
3064
|
+
currentPrice,
|
|
3065
|
+
reason: "currentPrice above breakevenPrice (SHORT position)"
|
|
3066
|
+
});
|
|
3067
|
+
return false;
|
|
3068
|
+
}
|
|
2915
3069
|
// Move SL to breakeven (entry price)
|
|
2916
3070
|
signal._trailingPriceStopLoss = breakevenPrice;
|
|
2917
3071
|
self.params.logger.info("BREAKEVEN_FN executed", {
|
|
@@ -3520,13 +3674,14 @@ const CHECK_PENDING_SIGNAL_COMPLETION_FN = async (self, signal, averagePrice) =>
|
|
|
3520
3674
|
if (elapsedTime >= maxTimeToWait) {
|
|
3521
3675
|
return await CLOSE_PENDING_SIGNAL_FN(self, signal, averagePrice, "time_expired");
|
|
3522
3676
|
}
|
|
3523
|
-
// Check take profit
|
|
3524
|
-
|
|
3525
|
-
|
|
3677
|
+
// Check take profit (use trailing TP if set, otherwise original TP)
|
|
3678
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
3679
|
+
if (signal.position === "long" && averagePrice >= effectiveTakeProfit) {
|
|
3680
|
+
return await CLOSE_PENDING_SIGNAL_FN(self, signal, effectiveTakeProfit, // КРИТИЧНО: используем точную цену TP
|
|
3526
3681
|
"take_profit");
|
|
3527
3682
|
}
|
|
3528
|
-
if (signal.position === "short" && averagePrice <=
|
|
3529
|
-
return await CLOSE_PENDING_SIGNAL_FN(self, signal,
|
|
3683
|
+
if (signal.position === "short" && averagePrice <= effectiveTakeProfit) {
|
|
3684
|
+
return await CLOSE_PENDING_SIGNAL_FN(self, signal, effectiveTakeProfit, // КРИТИЧНО: используем точную цену TP
|
|
3530
3685
|
"take_profit");
|
|
3531
3686
|
}
|
|
3532
3687
|
// Check stop loss (use trailing SL if set, otherwise original SL)
|
|
@@ -3588,8 +3743,9 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
3588
3743
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
|
|
3589
3744
|
}
|
|
3590
3745
|
if (currentDistance > 0) {
|
|
3591
|
-
// Moving towards TP
|
|
3592
|
-
const
|
|
3746
|
+
// Moving towards TP (use trailing TP if set)
|
|
3747
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
3748
|
+
const tpDistance = effectiveTakeProfit - signal.priceOpen;
|
|
3593
3749
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
3594
3750
|
percentTp = Math.min(progressPercent, 100);
|
|
3595
3751
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
@@ -3611,8 +3767,9 @@ const RETURN_PENDING_SIGNAL_ACTIVE_FN = async (self, signal, currentPrice) => {
|
|
|
3611
3767
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, currentPrice, currentTime, self.params.execution.context.backtest);
|
|
3612
3768
|
}
|
|
3613
3769
|
if (currentDistance > 0) {
|
|
3614
|
-
// Moving towards TP
|
|
3615
|
-
const
|
|
3770
|
+
// Moving towards TP (use trailing TP if set)
|
|
3771
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
3772
|
+
const tpDistance = signal.priceOpen - effectiveTakeProfit;
|
|
3616
3773
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
3617
3774
|
percentTp = Math.min(progressPercent, 100);
|
|
3618
3775
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, currentPrice, percentTp, currentTime, self.params.execution.context.backtest);
|
|
@@ -3874,11 +4031,12 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
3874
4031
|
}
|
|
3875
4032
|
// Check TP/SL only if not expired
|
|
3876
4033
|
// КРИТИЧНО: используем averagePrice (VWAP) для проверки достижения TP/SL (как в live mode)
|
|
3877
|
-
// КРИТИЧНО: используем trailing SL если
|
|
4034
|
+
// КРИТИЧНО: используем trailing SL и TP если установлены
|
|
3878
4035
|
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
4036
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
3879
4037
|
if (!shouldClose && signal.position === "long") {
|
|
3880
4038
|
// Для LONG: TP срабатывает если VWAP >= TP, SL если VWAP <= SL
|
|
3881
|
-
if (averagePrice >=
|
|
4039
|
+
if (averagePrice >= effectiveTakeProfit) {
|
|
3882
4040
|
shouldClose = true;
|
|
3883
4041
|
closeReason = "take_profit";
|
|
3884
4042
|
}
|
|
@@ -3889,7 +4047,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
3889
4047
|
}
|
|
3890
4048
|
if (!shouldClose && signal.position === "short") {
|
|
3891
4049
|
// Для SHORT: TP срабатывает если VWAP <= TP, SL если VWAP >= SL
|
|
3892
|
-
if (averagePrice <=
|
|
4050
|
+
if (averagePrice <= effectiveTakeProfit) {
|
|
3893
4051
|
shouldClose = true;
|
|
3894
4052
|
closeReason = "take_profit";
|
|
3895
4053
|
}
|
|
@@ -3902,7 +4060,7 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
3902
4060
|
// КРИТИЧНО: используем точную цену TP/SL для закрытия (как в live mode)
|
|
3903
4061
|
let closePrice;
|
|
3904
4062
|
if (closeReason === "take_profit") {
|
|
3905
|
-
closePrice =
|
|
4063
|
+
closePrice = effectiveTakeProfit; // используем trailing TP если установлен
|
|
3906
4064
|
}
|
|
3907
4065
|
else if (closeReason === "stop_loss") {
|
|
3908
4066
|
closePrice = effectiveStopLoss;
|
|
@@ -3923,8 +4081,9 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
3923
4081
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
|
|
3924
4082
|
}
|
|
3925
4083
|
if (currentDistance > 0) {
|
|
3926
|
-
// Moving towards TP
|
|
3927
|
-
const
|
|
4084
|
+
// Moving towards TP (use trailing TP if set)
|
|
4085
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
4086
|
+
const tpDistance = effectiveTakeProfit - signal.priceOpen;
|
|
3928
4087
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
3929
4088
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
3930
4089
|
}
|
|
@@ -3944,8 +4103,9 @@ const PROCESS_PENDING_SIGNAL_CANDLES_FN = async (self, signal, candles) => {
|
|
|
3944
4103
|
await CALL_BREAKEVEN_CHECK_FN(self, self.params.execution.context.symbol, signal, averagePrice, currentCandleTimestamp, self.params.execution.context.backtest);
|
|
3945
4104
|
}
|
|
3946
4105
|
if (currentDistance > 0) {
|
|
3947
|
-
// Moving towards TP
|
|
3948
|
-
const
|
|
4106
|
+
// Moving towards TP (use trailing TP if set)
|
|
4107
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
4108
|
+
const tpDistance = signal.priceOpen - effectiveTakeProfit;
|
|
3949
4109
|
const progressPercent = (currentDistance / tpDistance) * 100;
|
|
3950
4110
|
await CALL_PARTIAL_PROFIT_CALLBACKS_FN(self, self.params.execution.context.symbol, signal, averagePrice, Math.min(progressPercent, 100), currentCandleTimestamp, self.params.execution.context.backtest);
|
|
3951
4111
|
}
|
|
@@ -4073,6 +4233,112 @@ class ClientStrategy {
|
|
|
4073
4233
|
});
|
|
4074
4234
|
return this._scheduledSignal ? TO_PUBLIC_SIGNAL(this._scheduledSignal) : null;
|
|
4075
4235
|
}
|
|
4236
|
+
/**
|
|
4237
|
+
* Checks if breakeven threshold has been reached for the current pending signal.
|
|
4238
|
+
*
|
|
4239
|
+
* Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
|
|
4240
|
+
* to cover transaction costs (slippage + fees) and allow breakeven to be set.
|
|
4241
|
+
* Threshold: (CC_PERCENT_SLIPPAGE + CC_PERCENT_FEE) * 2 transactions
|
|
4242
|
+
*
|
|
4243
|
+
* For LONG position:
|
|
4244
|
+
* - Returns true when: currentPrice >= priceOpen * (1 + threshold%)
|
|
4245
|
+
* - Example: entry=100, threshold=0.4% → true when price >= 100.4
|
|
4246
|
+
*
|
|
4247
|
+
* For SHORT position:
|
|
4248
|
+
* - Returns true when: currentPrice <= priceOpen * (1 - threshold%)
|
|
4249
|
+
* - Example: entry=100, threshold=0.4% → true when price <= 99.6
|
|
4250
|
+
*
|
|
4251
|
+
* Special cases:
|
|
4252
|
+
* - Returns false if no pending signal exists
|
|
4253
|
+
* - Returns true if trailing stop is already in profit zone (breakeven already achieved)
|
|
4254
|
+
* - Returns false if threshold not reached yet
|
|
4255
|
+
*
|
|
4256
|
+
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
4257
|
+
* @param currentPrice - Current market price to check against threshold
|
|
4258
|
+
* @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
|
|
4259
|
+
*
|
|
4260
|
+
* @example
|
|
4261
|
+
* ```typescript
|
|
4262
|
+
* // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
|
|
4263
|
+
* const canBreakeven = await strategy.getBreakeven("BTCUSDT", 100.5);
|
|
4264
|
+
* // Returns true (price >= 100.4)
|
|
4265
|
+
*
|
|
4266
|
+
* if (canBreakeven) {
|
|
4267
|
+
* await strategy.breakeven("BTCUSDT", 100.5, false);
|
|
4268
|
+
* }
|
|
4269
|
+
* ```
|
|
4270
|
+
*/
|
|
4271
|
+
async getBreakeven(symbol, currentPrice) {
|
|
4272
|
+
this.params.logger.debug("ClientStrategy getBreakeven", {
|
|
4273
|
+
symbol,
|
|
4274
|
+
currentPrice,
|
|
4275
|
+
});
|
|
4276
|
+
// No pending signal - breakeven not available
|
|
4277
|
+
if (!this._pendingSignal) {
|
|
4278
|
+
return false;
|
|
4279
|
+
}
|
|
4280
|
+
const signal = this._pendingSignal;
|
|
4281
|
+
// Calculate breakeven threshold based on slippage and fees
|
|
4282
|
+
// Need to cover: entry slippage + entry fee + exit slippage + exit fee
|
|
4283
|
+
// Total: (slippage + fee) * 2 transactions
|
|
4284
|
+
const breakevenThresholdPercent = (GLOBAL_CONFIG.CC_PERCENT_SLIPPAGE + GLOBAL_CONFIG.CC_PERCENT_FEE) * 2 + GLOBAL_CONFIG.CC_BREAKEVEN_THRESHOLD;
|
|
4285
|
+
// Check if trailing stop is already set
|
|
4286
|
+
if (signal._trailingPriceStopLoss !== undefined) {
|
|
4287
|
+
const trailingStopLoss = signal._trailingPriceStopLoss;
|
|
4288
|
+
if (signal.position === "long") {
|
|
4289
|
+
// LONG: trailing SL is positive if it's above entry (in profit zone)
|
|
4290
|
+
const isPositiveTrailing = trailingStopLoss > signal.priceOpen;
|
|
4291
|
+
if (isPositiveTrailing) {
|
|
4292
|
+
// Trailing stop is already protecting profit - breakeven achieved
|
|
4293
|
+
return true;
|
|
4294
|
+
}
|
|
4295
|
+
// Trailing stop is negative (below entry)
|
|
4296
|
+
// Check if we can upgrade it to breakeven
|
|
4297
|
+
const thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
|
|
4298
|
+
const isThresholdReached = currentPrice >= thresholdPrice;
|
|
4299
|
+
const breakevenPrice = signal.priceOpen;
|
|
4300
|
+
// Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
|
|
4301
|
+
return isThresholdReached && breakevenPrice > trailingStopLoss;
|
|
4302
|
+
}
|
|
4303
|
+
else {
|
|
4304
|
+
// SHORT: trailing SL is positive if it's below entry (in profit zone)
|
|
4305
|
+
const isPositiveTrailing = trailingStopLoss < signal.priceOpen;
|
|
4306
|
+
if (isPositiveTrailing) {
|
|
4307
|
+
// Trailing stop is already protecting profit - breakeven achieved
|
|
4308
|
+
return true;
|
|
4309
|
+
}
|
|
4310
|
+
// Trailing stop is negative (above entry)
|
|
4311
|
+
// Check if we can upgrade it to breakeven
|
|
4312
|
+
const thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
|
|
4313
|
+
const isThresholdReached = currentPrice <= thresholdPrice;
|
|
4314
|
+
const breakevenPrice = signal.priceOpen;
|
|
4315
|
+
// Can upgrade to breakeven if threshold reached and breakeven is better than current trailing SL
|
|
4316
|
+
return isThresholdReached && breakevenPrice < trailingStopLoss;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
// No trailing stop set - proceed with normal breakeven logic
|
|
4320
|
+
const currentStopLoss = signal.priceStopLoss;
|
|
4321
|
+
const breakevenPrice = signal.priceOpen;
|
|
4322
|
+
// Calculate threshold price
|
|
4323
|
+
let thresholdPrice;
|
|
4324
|
+
let isThresholdReached;
|
|
4325
|
+
let canMoveToBreakeven;
|
|
4326
|
+
if (signal.position === "long") {
|
|
4327
|
+
// LONG: threshold reached when price goes UP by breakevenThresholdPercent from entry
|
|
4328
|
+
thresholdPrice = signal.priceOpen * (1 + breakevenThresholdPercent / 100);
|
|
4329
|
+
isThresholdReached = currentPrice >= thresholdPrice;
|
|
4330
|
+
// Can move to breakeven only if threshold reached and SL is below entry
|
|
4331
|
+
canMoveToBreakeven = isThresholdReached && currentStopLoss < breakevenPrice;
|
|
4332
|
+
}
|
|
4333
|
+
else {
|
|
4334
|
+
// SHORT: threshold reached when price goes DOWN by breakevenThresholdPercent from entry
|
|
4335
|
+
thresholdPrice = signal.priceOpen * (1 - breakevenThresholdPercent / 100);
|
|
4336
|
+
isThresholdReached = currentPrice <= thresholdPrice;
|
|
4337
|
+
// Can move to breakeven only if threshold reached and SL is above entry
|
|
4338
|
+
canMoveToBreakeven = isThresholdReached && currentStopLoss > breakevenPrice;
|
|
4339
|
+
}
|
|
4340
|
+
return canMoveToBreakeven;
|
|
4341
|
+
}
|
|
4076
4342
|
/**
|
|
4077
4343
|
* Returns the stopped state of the strategy.
|
|
4078
4344
|
*
|
|
@@ -4736,30 +5002,38 @@ class ClientStrategy {
|
|
|
4736
5002
|
* - Throws if percentShift is not a finite number
|
|
4737
5003
|
* - Throws if percentShift < -100 or > 100
|
|
4738
5004
|
* - Throws if percentShift === 0
|
|
5005
|
+
* - Throws if currentPrice is not a positive finite number
|
|
4739
5006
|
* - Skips if new SL would cross entry price
|
|
5007
|
+
* - Skips if currentPrice already crossed new SL level (price intrusion protection)
|
|
4740
5008
|
*
|
|
4741
5009
|
* @param symbol - Trading pair symbol (e.g., "BTCUSDT")
|
|
4742
5010
|
* @param percentShift - Percentage shift of SL distance [-100, 100], excluding 0
|
|
5011
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
4743
5012
|
* @param backtest - Whether running in backtest mode (controls persistence)
|
|
4744
5013
|
* @returns Promise that resolves when trailing SL is updated and persisted
|
|
4745
5014
|
*
|
|
4746
5015
|
* @example
|
|
4747
5016
|
* ```typescript
|
|
4748
|
-
* // LONG position: entry=100, originalSL=90, distance=10
|
|
5017
|
+
* // LONG position: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
4749
5018
|
*
|
|
4750
|
-
* // Move SL 50% closer to entry (tighten)
|
|
4751
|
-
* await strategy.trailingStop("BTCUSDT", -50, false);
|
|
4752
|
-
* // newSL = 100
|
|
5019
|
+
* // Move SL 50% closer to entry (tighten): reduces distance by 50%
|
|
5020
|
+
* await strategy.trailingStop("BTCUSDT", -50, 102, false);
|
|
5021
|
+
* // newDistance = 10% - 50% = 5%, newSL = 100 * (1 - 0.05) = 95
|
|
4753
5022
|
*
|
|
4754
|
-
* // Move SL 30% away from entry (loosen
|
|
4755
|
-
* await strategy.trailingStop("BTCUSDT", 30, false);
|
|
4756
|
-
* // newSL = 100
|
|
5023
|
+
* // Move SL 30% away from entry (loosen): increases distance by 30%
|
|
5024
|
+
* await strategy.trailingStop("BTCUSDT", 30, 102, false);
|
|
5025
|
+
* // newDistance = 10% + 30% = 40%, newSL = 100 * (1 - 0.4) = 60
|
|
5026
|
+
*
|
|
5027
|
+
* // Price intrusion example: currentPrice=92, trying to set SL=95
|
|
5028
|
+
* await strategy.trailingStop("BTCUSDT", -50, 92, false);
|
|
5029
|
+
* // SKIPPED: currentPrice (92) < newSL (95) - would trigger immediate stop
|
|
4757
5030
|
* ```
|
|
4758
5031
|
*/
|
|
4759
|
-
async trailingStop(symbol, percentShift, backtest) {
|
|
5032
|
+
async trailingStop(symbol, percentShift, currentPrice, backtest) {
|
|
4760
5033
|
this.params.logger.debug("ClientStrategy trailingStop", {
|
|
4761
5034
|
symbol,
|
|
4762
5035
|
percentShift,
|
|
5036
|
+
currentPrice,
|
|
4763
5037
|
hasPendingSignal: this._pendingSignal !== null,
|
|
4764
5038
|
});
|
|
4765
5039
|
// Validation: must have pending signal
|
|
@@ -4776,6 +5050,72 @@ class ClientStrategy {
|
|
|
4776
5050
|
if (percentShift === 0) {
|
|
4777
5051
|
throw new Error(`ClientStrategy trailingStop: percentShift cannot be 0`);
|
|
4778
5052
|
}
|
|
5053
|
+
// Validation: currentPrice must be valid
|
|
5054
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
|
|
5055
|
+
throw new Error(`ClientStrategy trailingStop: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
5056
|
+
}
|
|
5057
|
+
// Calculate what the new stop loss would be
|
|
5058
|
+
const signal = this._pendingSignal;
|
|
5059
|
+
const slDistancePercent = Math.abs((signal.priceOpen - signal.priceStopLoss) / signal.priceOpen * 100);
|
|
5060
|
+
const newSlDistancePercent = slDistancePercent + percentShift;
|
|
5061
|
+
let newStopLoss;
|
|
5062
|
+
if (signal.position === "long") {
|
|
5063
|
+
newStopLoss = signal.priceOpen * (1 - newSlDistancePercent / 100);
|
|
5064
|
+
}
|
|
5065
|
+
else {
|
|
5066
|
+
newStopLoss = signal.priceOpen * (1 + newSlDistancePercent / 100);
|
|
5067
|
+
}
|
|
5068
|
+
// Check for price intrusion before executing trailing logic
|
|
5069
|
+
if (signal.position === "long" && currentPrice < newStopLoss) {
|
|
5070
|
+
// LONG: Price already crossed the new stop loss level - skip setting SL
|
|
5071
|
+
this.params.logger.debug("ClientStrategy trailingStop: price intrusion detected, skipping SL update", {
|
|
5072
|
+
signalId: signal.id,
|
|
5073
|
+
position: signal.position,
|
|
5074
|
+
priceOpen: signal.priceOpen,
|
|
5075
|
+
newStopLoss,
|
|
5076
|
+
currentPrice,
|
|
5077
|
+
reason: "currentPrice below newStopLoss (LONG position)"
|
|
5078
|
+
});
|
|
5079
|
+
return;
|
|
5080
|
+
}
|
|
5081
|
+
if (signal.position === "short" && currentPrice > newStopLoss) {
|
|
5082
|
+
// SHORT: Price already crossed the new stop loss level - skip setting SL
|
|
5083
|
+
this.params.logger.debug("ClientStrategy trailingStop: price intrusion detected, skipping SL update", {
|
|
5084
|
+
signalId: signal.id,
|
|
5085
|
+
position: signal.position,
|
|
5086
|
+
priceOpen: signal.priceOpen,
|
|
5087
|
+
newStopLoss,
|
|
5088
|
+
currentPrice,
|
|
5089
|
+
reason: "currentPrice above newStopLoss (SHORT position)"
|
|
5090
|
+
});
|
|
5091
|
+
return;
|
|
5092
|
+
}
|
|
5093
|
+
// Check for conflict with existing trailing take profit
|
|
5094
|
+
const effectiveTakeProfit = signal._trailingPriceTakeProfit ?? signal.priceTakeProfit;
|
|
5095
|
+
if (signal.position === "long" && newStopLoss >= effectiveTakeProfit) {
|
|
5096
|
+
// LONG: New SL would be at or above current TP - invalid configuration
|
|
5097
|
+
this.params.logger.debug("ClientStrategy trailingStop: SL/TP conflict detected, skipping SL update", {
|
|
5098
|
+
signalId: signal.id,
|
|
5099
|
+
position: signal.position,
|
|
5100
|
+
priceOpen: signal.priceOpen,
|
|
5101
|
+
newStopLoss,
|
|
5102
|
+
effectiveTakeProfit,
|
|
5103
|
+
reason: "newStopLoss >= effectiveTakeProfit (LONG position)"
|
|
5104
|
+
});
|
|
5105
|
+
return;
|
|
5106
|
+
}
|
|
5107
|
+
if (signal.position === "short" && newStopLoss <= effectiveTakeProfit) {
|
|
5108
|
+
// SHORT: New SL would be at or below current TP - invalid configuration
|
|
5109
|
+
this.params.logger.debug("ClientStrategy trailingStop: SL/TP conflict detected, skipping SL update", {
|
|
5110
|
+
signalId: signal.id,
|
|
5111
|
+
position: signal.position,
|
|
5112
|
+
priceOpen: signal.priceOpen,
|
|
5113
|
+
newStopLoss,
|
|
5114
|
+
effectiveTakeProfit,
|
|
5115
|
+
reason: "newStopLoss <= effectiveTakeProfit (SHORT position)"
|
|
5116
|
+
});
|
|
5117
|
+
return;
|
|
5118
|
+
}
|
|
4779
5119
|
// Execute trailing logic
|
|
4780
5120
|
TRAILING_STOP_FN(this, this._pendingSignal, percentShift);
|
|
4781
5121
|
// Persist updated signal state (inline setPendingSignal content)
|
|
@@ -4792,6 +5132,136 @@ class ClientStrategy {
|
|
|
4792
5132
|
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
|
|
4793
5133
|
}
|
|
4794
5134
|
}
|
|
5135
|
+
/**
|
|
5136
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
5137
|
+
*
|
|
5138
|
+
* Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
|
|
5139
|
+
* Negative percentShift brings TP closer to entry, positive percentShift moves it further.
|
|
5140
|
+
* Once direction is set on first call, subsequent calls must continue in same direction.
|
|
5141
|
+
*
|
|
5142
|
+
* Price intrusion protection: If current price has already crossed the new TP level,
|
|
5143
|
+
* the update is skipped to prevent immediate TP triggering.
|
|
5144
|
+
*
|
|
5145
|
+
* @param symbol - Trading pair symbol
|
|
5146
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
5147
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
5148
|
+
* @param backtest - Whether running in backtest mode
|
|
5149
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
5150
|
+
*
|
|
5151
|
+
* @example
|
|
5152
|
+
* ```typescript
|
|
5153
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
5154
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
5155
|
+
* await strategy.trailingProfit("BTCUSDT", 50, 102, false);
|
|
5156
|
+
*
|
|
5157
|
+
* // SHORT: entry=100, originalTP=90, distance=10%, currentPrice=98
|
|
5158
|
+
* // Move TP closer by 30%: newTP = 100 - 7% = 93
|
|
5159
|
+
* await strategy.trailingProfit("BTCUSDT", -30, 98, false);
|
|
5160
|
+
* ```
|
|
5161
|
+
*/
|
|
5162
|
+
async trailingProfit(symbol, percentShift, currentPrice, backtest) {
|
|
5163
|
+
this.params.logger.debug("ClientStrategy trailingProfit", {
|
|
5164
|
+
symbol,
|
|
5165
|
+
percentShift,
|
|
5166
|
+
currentPrice,
|
|
5167
|
+
hasPendingSignal: this._pendingSignal !== null,
|
|
5168
|
+
});
|
|
5169
|
+
// Validation: must have pending signal
|
|
5170
|
+
if (!this._pendingSignal) {
|
|
5171
|
+
throw new Error(`ClientStrategy trailingProfit: No pending signal exists for symbol=${symbol}`);
|
|
5172
|
+
}
|
|
5173
|
+
// Validation: percentShift must be valid
|
|
5174
|
+
if (typeof percentShift !== "number" || !isFinite(percentShift)) {
|
|
5175
|
+
throw new Error(`ClientStrategy trailingProfit: percentShift must be a finite number, got ${percentShift} (${typeof percentShift})`);
|
|
5176
|
+
}
|
|
5177
|
+
if (percentShift < -100 || percentShift > 100) {
|
|
5178
|
+
throw new Error(`ClientStrategy trailingProfit: percentShift must be in range [-100, 100], got ${percentShift}`);
|
|
5179
|
+
}
|
|
5180
|
+
if (percentShift === 0) {
|
|
5181
|
+
throw new Error(`ClientStrategy trailingProfit: percentShift cannot be 0`);
|
|
5182
|
+
}
|
|
5183
|
+
// Validation: currentPrice must be valid
|
|
5184
|
+
if (typeof currentPrice !== "number" || !isFinite(currentPrice) || currentPrice <= 0) {
|
|
5185
|
+
throw new Error(`ClientStrategy trailingProfit: currentPrice must be a positive finite number, got ${currentPrice}`);
|
|
5186
|
+
}
|
|
5187
|
+
// Calculate what the new take profit would be
|
|
5188
|
+
const signal = this._pendingSignal;
|
|
5189
|
+
const tpDistancePercent = Math.abs((signal.priceTakeProfit - signal.priceOpen) / signal.priceOpen * 100);
|
|
5190
|
+
const newTpDistancePercent = tpDistancePercent + percentShift;
|
|
5191
|
+
let newTakeProfit;
|
|
5192
|
+
if (signal.position === "long") {
|
|
5193
|
+
newTakeProfit = signal.priceOpen * (1 + newTpDistancePercent / 100);
|
|
5194
|
+
}
|
|
5195
|
+
else {
|
|
5196
|
+
newTakeProfit = signal.priceOpen * (1 - newTpDistancePercent / 100);
|
|
5197
|
+
}
|
|
5198
|
+
// Check for price intrusion before executing trailing logic
|
|
5199
|
+
if (signal.position === "long" && currentPrice > newTakeProfit) {
|
|
5200
|
+
// LONG: Price already crossed the new take profit level - skip setting TP
|
|
5201
|
+
this.params.logger.debug("ClientStrategy trailingProfit: price intrusion detected, skipping TP update", {
|
|
5202
|
+
signalId: signal.id,
|
|
5203
|
+
position: signal.position,
|
|
5204
|
+
priceOpen: signal.priceOpen,
|
|
5205
|
+
newTakeProfit,
|
|
5206
|
+
currentPrice,
|
|
5207
|
+
reason: "currentPrice above newTakeProfit (LONG position)"
|
|
5208
|
+
});
|
|
5209
|
+
return;
|
|
5210
|
+
}
|
|
5211
|
+
if (signal.position === "short" && currentPrice < newTakeProfit) {
|
|
5212
|
+
// SHORT: Price already crossed the new take profit level - skip setting TP
|
|
5213
|
+
this.params.logger.debug("ClientStrategy trailingProfit: price intrusion detected, skipping TP update", {
|
|
5214
|
+
signalId: signal.id,
|
|
5215
|
+
position: signal.position,
|
|
5216
|
+
priceOpen: signal.priceOpen,
|
|
5217
|
+
newTakeProfit,
|
|
5218
|
+
currentPrice,
|
|
5219
|
+
reason: "currentPrice below newTakeProfit (SHORT position)"
|
|
5220
|
+
});
|
|
5221
|
+
return;
|
|
5222
|
+
}
|
|
5223
|
+
// Check for conflict with existing trailing stop loss
|
|
5224
|
+
const effectiveStopLoss = signal._trailingPriceStopLoss ?? signal.priceStopLoss;
|
|
5225
|
+
if (signal.position === "long" && newTakeProfit <= effectiveStopLoss) {
|
|
5226
|
+
// LONG: New TP would be at or below current SL - invalid configuration
|
|
5227
|
+
this.params.logger.debug("ClientStrategy trailingProfit: TP/SL conflict detected, skipping TP update", {
|
|
5228
|
+
signalId: signal.id,
|
|
5229
|
+
position: signal.position,
|
|
5230
|
+
priceOpen: signal.priceOpen,
|
|
5231
|
+
newTakeProfit,
|
|
5232
|
+
effectiveStopLoss,
|
|
5233
|
+
reason: "newTakeProfit <= effectiveStopLoss (LONG position)"
|
|
5234
|
+
});
|
|
5235
|
+
return;
|
|
5236
|
+
}
|
|
5237
|
+
if (signal.position === "short" && newTakeProfit >= effectiveStopLoss) {
|
|
5238
|
+
// SHORT: New TP would be at or above current SL - invalid configuration
|
|
5239
|
+
this.params.logger.debug("ClientStrategy trailingProfit: TP/SL conflict detected, skipping TP update", {
|
|
5240
|
+
signalId: signal.id,
|
|
5241
|
+
position: signal.position,
|
|
5242
|
+
priceOpen: signal.priceOpen,
|
|
5243
|
+
newTakeProfit,
|
|
5244
|
+
effectiveStopLoss,
|
|
5245
|
+
reason: "newTakeProfit >= effectiveStopLoss (SHORT position)"
|
|
5246
|
+
});
|
|
5247
|
+
return;
|
|
5248
|
+
}
|
|
5249
|
+
// Execute trailing logic
|
|
5250
|
+
TRAILING_PROFIT_FN(this, this._pendingSignal, percentShift);
|
|
5251
|
+
// Persist updated signal state (inline setPendingSignal content)
|
|
5252
|
+
// Note: this._pendingSignal already mutated by TRAILING_PROFIT_FN, no reassignment needed
|
|
5253
|
+
this.params.logger.debug("ClientStrategy setPendingSignal (inline)", {
|
|
5254
|
+
pendingSignal: this._pendingSignal,
|
|
5255
|
+
});
|
|
5256
|
+
// Call onWrite callback for testing persist storage
|
|
5257
|
+
if (this.params.callbacks?.onWrite) {
|
|
5258
|
+
const publicSignal = TO_PUBLIC_SIGNAL(this._pendingSignal);
|
|
5259
|
+
this.params.callbacks.onWrite(this.params.execution.context.symbol, publicSignal, backtest);
|
|
5260
|
+
}
|
|
5261
|
+
if (!backtest) {
|
|
5262
|
+
await PersistSignalAdapter.writeSignalData(this._pendingSignal, this.params.execution.context.symbol, this.params.strategyName, this.params.exchangeName);
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
4795
5265
|
}
|
|
4796
5266
|
|
|
4797
5267
|
const RISK_METHOD_NAME_GET_DATA = "RiskUtils.getData";
|
|
@@ -4961,6 +5431,7 @@ class RiskUtils {
|
|
|
4961
5431
|
strategyName: context.strategyName,
|
|
4962
5432
|
});
|
|
4963
5433
|
bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_DATA);
|
|
5434
|
+
bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_DATA);
|
|
4964
5435
|
{
|
|
4965
5436
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
4966
5437
|
riskName &&
|
|
@@ -5017,6 +5488,7 @@ class RiskUtils {
|
|
|
5017
5488
|
strategyName: context.strategyName,
|
|
5018
5489
|
});
|
|
5019
5490
|
bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_GET_REPORT);
|
|
5491
|
+
bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_GET_REPORT);
|
|
5020
5492
|
{
|
|
5021
5493
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
5022
5494
|
riskName &&
|
|
@@ -5065,6 +5537,7 @@ class RiskUtils {
|
|
|
5065
5537
|
path,
|
|
5066
5538
|
});
|
|
5067
5539
|
bt.strategyValidationService.validate(context.strategyName, RISK_METHOD_NAME_DUMP);
|
|
5540
|
+
bt.exchangeValidationService.validate(context.exchangeName, RISK_METHOD_NAME_DUMP);
|
|
5068
5541
|
{
|
|
5069
5542
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
5070
5543
|
riskName &&
|
|
@@ -5275,6 +5748,46 @@ class StrategyConnectionService {
|
|
|
5275
5748
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
5276
5749
|
return await strategy.getScheduledSignal(symbol);
|
|
5277
5750
|
};
|
|
5751
|
+
/**
|
|
5752
|
+
* Checks if breakeven threshold has been reached for the current pending signal.
|
|
5753
|
+
*
|
|
5754
|
+
* Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
|
|
5755
|
+
* to cover transaction costs and allow breakeven to be set.
|
|
5756
|
+
*
|
|
5757
|
+
* Delegates to ClientStrategy.getBreakeven() with current execution context.
|
|
5758
|
+
*
|
|
5759
|
+
* @param backtest - Whether running in backtest mode
|
|
5760
|
+
* @param symbol - Trading pair symbol
|
|
5761
|
+
* @param currentPrice - Current market price to check against threshold
|
|
5762
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
5763
|
+
* @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
|
|
5764
|
+
*
|
|
5765
|
+
* @example
|
|
5766
|
+
* ```typescript
|
|
5767
|
+
* // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
|
|
5768
|
+
* const canBreakeven = await strategyConnectionService.getBreakeven(
|
|
5769
|
+
* false,
|
|
5770
|
+
* "BTCUSDT",
|
|
5771
|
+
* 100.5,
|
|
5772
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
5773
|
+
* );
|
|
5774
|
+
* // Returns true (price >= 100.4)
|
|
5775
|
+
*
|
|
5776
|
+
* if (canBreakeven) {
|
|
5777
|
+
* await strategyConnectionService.breakeven(false, "BTCUSDT", 100.5, context);
|
|
5778
|
+
* }
|
|
5779
|
+
* ```
|
|
5780
|
+
*/
|
|
5781
|
+
this.getBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
5782
|
+
this.loggerService.log("strategyConnectionService getBreakeven", {
|
|
5783
|
+
symbol,
|
|
5784
|
+
context,
|
|
5785
|
+
currentPrice,
|
|
5786
|
+
backtest,
|
|
5787
|
+
});
|
|
5788
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
5789
|
+
return await strategy.getBreakeven(symbol, currentPrice);
|
|
5790
|
+
};
|
|
5278
5791
|
/**
|
|
5279
5792
|
* Retrieves the stopped state of the strategy.
|
|
5280
5793
|
*
|
|
@@ -5509,30 +6022,72 @@ class StrategyConnectionService {
|
|
|
5509
6022
|
* @param backtest - Whether running in backtest mode
|
|
5510
6023
|
* @param symbol - Trading pair symbol
|
|
5511
6024
|
* @param percentShift - Percentage adjustment to SL distance (-100 to 100)
|
|
6025
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
5512
6026
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
5513
6027
|
* @returns Promise that resolves when trailing SL is updated
|
|
5514
6028
|
*
|
|
5515
6029
|
* @example
|
|
5516
6030
|
* ```typescript
|
|
5517
|
-
* // LONG: entry=100, originalSL=90, distance=10
|
|
5518
|
-
* // Tighten stop by 50%: newSL = 100 -
|
|
6031
|
+
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
6032
|
+
* // Tighten stop by 50%: newSL = 100 - 5% = 95
|
|
5519
6033
|
* await strategyConnectionService.trailingStop(
|
|
5520
6034
|
* false,
|
|
5521
6035
|
* "BTCUSDT",
|
|
5522
6036
|
* -50,
|
|
6037
|
+
* 102,
|
|
5523
6038
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
5524
6039
|
* );
|
|
5525
6040
|
* ```
|
|
5526
6041
|
*/
|
|
5527
|
-
this.trailingStop = async (backtest, symbol, percentShift, context) => {
|
|
6042
|
+
this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
5528
6043
|
this.loggerService.log("strategyConnectionService trailingStop", {
|
|
5529
6044
|
symbol,
|
|
5530
6045
|
context,
|
|
5531
6046
|
percentShift,
|
|
6047
|
+
currentPrice,
|
|
6048
|
+
backtest,
|
|
6049
|
+
});
|
|
6050
|
+
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
6051
|
+
await strategy.trailingStop(symbol, percentShift, currentPrice, backtest);
|
|
6052
|
+
};
|
|
6053
|
+
/**
|
|
6054
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
6055
|
+
*
|
|
6056
|
+
* Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
|
|
6057
|
+
* Negative percentShift brings TP closer to entry, positive percentShift moves it further.
|
|
6058
|
+
*
|
|
6059
|
+
* Delegates to ClientStrategy.trailingProfit() with current execution context.
|
|
6060
|
+
*
|
|
6061
|
+
* @param backtest - Whether running in backtest mode
|
|
6062
|
+
* @param symbol - Trading pair symbol
|
|
6063
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
6064
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
6065
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
6066
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
6067
|
+
*
|
|
6068
|
+
* @example
|
|
6069
|
+
* ```typescript
|
|
6070
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
6071
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
6072
|
+
* await strategyConnectionService.trailingProfit(
|
|
6073
|
+
* false,
|
|
6074
|
+
* "BTCUSDT",
|
|
6075
|
+
* 50,
|
|
6076
|
+
* 102,
|
|
6077
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
6078
|
+
* );
|
|
6079
|
+
* ```
|
|
6080
|
+
*/
|
|
6081
|
+
this.trailingProfit = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
6082
|
+
this.loggerService.log("strategyConnectionService trailingProfit", {
|
|
6083
|
+
symbol,
|
|
6084
|
+
context,
|
|
6085
|
+
percentShift,
|
|
6086
|
+
currentPrice,
|
|
5532
6087
|
backtest,
|
|
5533
6088
|
});
|
|
5534
6089
|
const strategy = this.getStrategy(symbol, context.strategyName, context.exchangeName, context.frameName, backtest);
|
|
5535
|
-
await strategy.
|
|
6090
|
+
await strategy.trailingProfit(symbol, percentShift, currentPrice, backtest);
|
|
5536
6091
|
};
|
|
5537
6092
|
/**
|
|
5538
6093
|
* Delegates to ClientStrategy.breakeven() with current execution context.
|
|
@@ -6003,16 +6558,19 @@ const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
|
|
|
6003
6558
|
*
|
|
6004
6559
|
* - Falls back to currentPrice if priceOpen is not set (for ISignalDto/scheduled signals)
|
|
6005
6560
|
* - Replaces priceStopLoss with trailing SL if active (for positions with trailing stops)
|
|
6561
|
+
* - Replaces priceTakeProfit with trailing TP if active (for positions with trailing take-profit)
|
|
6006
6562
|
* - Preserves original stop-loss in originalPriceStopLoss for reference
|
|
6563
|
+
* - Preserves original take-profit in originalPriceTakeProfit for reference
|
|
6007
6564
|
*
|
|
6008
6565
|
* Use cases:
|
|
6009
6566
|
* - Risk validation before opening a position (checkSignal)
|
|
6010
6567
|
* - Pre-flight validation of scheduled signals
|
|
6011
6568
|
* - Calculating position size based on stop-loss distance
|
|
6569
|
+
* - Calculating risk-reward ratio using effective SL/TP
|
|
6012
6570
|
*
|
|
6013
6571
|
* @param signal - Signal DTO or row (may not have priceOpen for scheduled signals)
|
|
6014
6572
|
* @param currentPrice - Current market price, used as fallback for priceOpen if not set
|
|
6015
|
-
* @returns Signal in IRiskSignalRow format with guaranteed priceOpen and effective
|
|
6573
|
+
* @returns Signal in IRiskSignalRow format with guaranteed priceOpen and effective SL/TP
|
|
6016
6574
|
*
|
|
6017
6575
|
* @example
|
|
6018
6576
|
* ```typescript
|
|
@@ -6020,24 +6578,24 @@ const POSITION_NEED_FETCH = Symbol("risk-need-fetch");
|
|
|
6020
6578
|
* const riskSignal = TO_RISK_SIGNAL(scheduledSignal, 45000);
|
|
6021
6579
|
* // riskSignal.priceOpen = 45000 (fallback to currentPrice)
|
|
6022
6580
|
*
|
|
6023
|
-
* // For signal with trailing SL
|
|
6581
|
+
* // For signal with trailing SL/TP
|
|
6024
6582
|
* const riskSignal = TO_RISK_SIGNAL(activeSignal, 46000);
|
|
6025
|
-
* // riskSignal.priceStopLoss = activeSignal._trailingPriceStopLoss
|
|
6583
|
+
* // riskSignal.priceStopLoss = activeSignal._trailingPriceStopLoss (effective)
|
|
6584
|
+
* // riskSignal.priceTakeProfit = activeSignal._trailingPriceTakeProfit (effective)
|
|
6585
|
+
* // riskSignal.originalPriceStopLoss = activeSignal.priceStopLoss (original)
|
|
6586
|
+
* // riskSignal.originalPriceTakeProfit = activeSignal.priceTakeProfit (original)
|
|
6026
6587
|
* ```
|
|
6027
6588
|
*/
|
|
6028
6589
|
const TO_RISK_SIGNAL = (signal, currentPrice) => {
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
...structuredClone(signal),
|
|
6032
|
-
priceOpen: signal.priceOpen ?? currentPrice,
|
|
6033
|
-
priceStopLoss: signal._trailingPriceStopLoss,
|
|
6034
|
-
originalPriceStopLoss: signal.priceStopLoss,
|
|
6035
|
-
};
|
|
6036
|
-
}
|
|
6590
|
+
const hasTrailingSL = "_trailingPriceStopLoss" in signal && signal._trailingPriceStopLoss !== undefined;
|
|
6591
|
+
const hasTrailingTP = "_trailingPriceTakeProfit" in signal && signal._trailingPriceTakeProfit !== undefined;
|
|
6037
6592
|
return {
|
|
6038
6593
|
...structuredClone(signal),
|
|
6039
6594
|
priceOpen: signal.priceOpen ?? currentPrice,
|
|
6595
|
+
priceStopLoss: hasTrailingSL ? signal._trailingPriceStopLoss : signal.priceStopLoss,
|
|
6596
|
+
priceTakeProfit: hasTrailingTP ? signal._trailingPriceTakeProfit : signal.priceTakeProfit,
|
|
6040
6597
|
originalPriceStopLoss: signal.priceStopLoss,
|
|
6598
|
+
originalPriceTakeProfit: signal.priceTakeProfit,
|
|
6041
6599
|
};
|
|
6042
6600
|
};
|
|
6043
6601
|
/** Key generator for active position map */
|
|
@@ -6673,6 +7231,46 @@ class StrategyCoreService {
|
|
|
6673
7231
|
await this.validate(symbol, context);
|
|
6674
7232
|
return await this.strategyConnectionService.getScheduledSignal(backtest, symbol, context);
|
|
6675
7233
|
};
|
|
7234
|
+
/**
|
|
7235
|
+
* Checks if breakeven threshold has been reached for the current pending signal.
|
|
7236
|
+
*
|
|
7237
|
+
* Validates strategy existence and delegates to connection service
|
|
7238
|
+
* to check if price has moved far enough to cover transaction costs.
|
|
7239
|
+
*
|
|
7240
|
+
* Does not require execution context as this is a state query operation.
|
|
7241
|
+
*
|
|
7242
|
+
* @param backtest - Whether running in backtest mode
|
|
7243
|
+
* @param symbol - Trading pair symbol
|
|
7244
|
+
* @param currentPrice - Current market price to check against threshold
|
|
7245
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
7246
|
+
* @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
|
|
7247
|
+
*
|
|
7248
|
+
* @example
|
|
7249
|
+
* ```typescript
|
|
7250
|
+
* // Check if breakeven is available for LONG position (entry=100, threshold=0.4%)
|
|
7251
|
+
* const canBreakeven = await strategyCoreService.getBreakeven(
|
|
7252
|
+
* false,
|
|
7253
|
+
* "BTCUSDT",
|
|
7254
|
+
* 100.5,
|
|
7255
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
7256
|
+
* );
|
|
7257
|
+
* // Returns true (price >= 100.4)
|
|
7258
|
+
*
|
|
7259
|
+
* if (canBreakeven) {
|
|
7260
|
+
* await strategyCoreService.breakeven(false, "BTCUSDT", 100.5, context);
|
|
7261
|
+
* }
|
|
7262
|
+
* ```
|
|
7263
|
+
*/
|
|
7264
|
+
this.getBreakeven = async (backtest, symbol, currentPrice, context) => {
|
|
7265
|
+
this.loggerService.log("strategyCoreService getBreakeven", {
|
|
7266
|
+
symbol,
|
|
7267
|
+
currentPrice,
|
|
7268
|
+
context,
|
|
7269
|
+
backtest,
|
|
7270
|
+
});
|
|
7271
|
+
await this.validate(symbol, context);
|
|
7272
|
+
return await this.strategyConnectionService.getBreakeven(backtest, symbol, currentPrice, context);
|
|
7273
|
+
};
|
|
6676
7274
|
/**
|
|
6677
7275
|
* Checks if the strategy has been stopped.
|
|
6678
7276
|
*
|
|
@@ -6902,30 +7500,68 @@ class StrategyCoreService {
|
|
|
6902
7500
|
* @param backtest - Whether running in backtest mode
|
|
6903
7501
|
* @param symbol - Trading pair symbol
|
|
6904
7502
|
* @param percentShift - Percentage adjustment to SL distance (-100 to 100)
|
|
7503
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
6905
7504
|
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
6906
7505
|
* @returns Promise that resolves when trailing SL is updated
|
|
6907
7506
|
*
|
|
6908
7507
|
* @example
|
|
6909
7508
|
* ```typescript
|
|
6910
|
-
* // LONG: entry=100, originalSL=90, distance=10
|
|
6911
|
-
* // Tighten stop by 50%: newSL = 100 -
|
|
7509
|
+
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
7510
|
+
* // Tighten stop by 50%: newSL = 100 - 5% = 95
|
|
6912
7511
|
* await strategyCoreService.trailingStop(
|
|
6913
7512
|
* false,
|
|
6914
7513
|
* "BTCUSDT",
|
|
6915
7514
|
* -50,
|
|
7515
|
+
* 102,
|
|
6916
7516
|
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
6917
7517
|
* );
|
|
6918
7518
|
* ```
|
|
6919
7519
|
*/
|
|
6920
|
-
this.trailingStop = async (backtest, symbol, percentShift, context) => {
|
|
7520
|
+
this.trailingStop = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
6921
7521
|
this.loggerService.log("strategyCoreService trailingStop", {
|
|
6922
7522
|
symbol,
|
|
6923
7523
|
percentShift,
|
|
7524
|
+
currentPrice,
|
|
7525
|
+
context,
|
|
7526
|
+
backtest,
|
|
7527
|
+
});
|
|
7528
|
+
await this.validate(symbol, context);
|
|
7529
|
+
return await this.strategyConnectionService.trailingStop(backtest, symbol, percentShift, currentPrice, context);
|
|
7530
|
+
};
|
|
7531
|
+
/**
|
|
7532
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
7533
|
+
* Validates context and delegates to StrategyConnectionService.
|
|
7534
|
+
*
|
|
7535
|
+
* @param backtest - Whether running in backtest mode
|
|
7536
|
+
* @param symbol - Trading pair symbol
|
|
7537
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
7538
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
7539
|
+
* @param context - Strategy context with strategyName, exchangeName, frameName
|
|
7540
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
7541
|
+
*
|
|
7542
|
+
* @example
|
|
7543
|
+
* ```typescript
|
|
7544
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
7545
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
7546
|
+
* await strategyCoreService.trailingProfit(
|
|
7547
|
+
* false,
|
|
7548
|
+
* "BTCUSDT",
|
|
7549
|
+
* 50,
|
|
7550
|
+
* 102,
|
|
7551
|
+
* { strategyName: "my-strategy", exchangeName: "binance", frameName: "" }
|
|
7552
|
+
* );
|
|
7553
|
+
* ```
|
|
7554
|
+
*/
|
|
7555
|
+
this.trailingProfit = async (backtest, symbol, percentShift, currentPrice, context) => {
|
|
7556
|
+
this.loggerService.log("strategyCoreService trailingProfit", {
|
|
7557
|
+
symbol,
|
|
7558
|
+
percentShift,
|
|
7559
|
+
currentPrice,
|
|
6924
7560
|
context,
|
|
6925
7561
|
backtest,
|
|
6926
7562
|
});
|
|
6927
7563
|
await this.validate(symbol, context);
|
|
6928
|
-
return await this.strategyConnectionService.
|
|
7564
|
+
return await this.strategyConnectionService.trailingProfit(backtest, symbol, percentShift, currentPrice, context);
|
|
6929
7565
|
};
|
|
6930
7566
|
/**
|
|
6931
7567
|
* Moves stop-loss to breakeven when price reaches threshold.
|
|
@@ -7034,6 +7670,7 @@ class RiskGlobalService {
|
|
|
7034
7670
|
this.loggerService = inject(TYPES.loggerService);
|
|
7035
7671
|
this.riskConnectionService = inject(TYPES.riskConnectionService);
|
|
7036
7672
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
7673
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
7037
7674
|
/**
|
|
7038
7675
|
* Validates risk configuration.
|
|
7039
7676
|
* Memoized to avoid redundant validations for the same risk-exchange-frame combination.
|
|
@@ -7046,6 +7683,7 @@ class RiskGlobalService {
|
|
|
7046
7683
|
payload,
|
|
7047
7684
|
});
|
|
7048
7685
|
this.riskValidationService.validate(payload.riskName, "riskGlobalService validate");
|
|
7686
|
+
this.exchangeValidationService.validate(payload.exchangeName, "riskGlobalService validate");
|
|
7049
7687
|
});
|
|
7050
7688
|
/**
|
|
7051
7689
|
* Checks if a signal should be allowed based on risk limits.
|
|
@@ -15318,6 +15956,10 @@ class PartialGlobalService {
|
|
|
15318
15956
|
* Risk validation service for validating risk existence.
|
|
15319
15957
|
*/
|
|
15320
15958
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
15959
|
+
/**
|
|
15960
|
+
* Exchange validation service for validating exchange existence.
|
|
15961
|
+
*/
|
|
15962
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
15321
15963
|
/**
|
|
15322
15964
|
* Validates strategy and associated risk configuration.
|
|
15323
15965
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -15331,6 +15973,7 @@ class PartialGlobalService {
|
|
|
15331
15973
|
methodName,
|
|
15332
15974
|
});
|
|
15333
15975
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
15976
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
15334
15977
|
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
15335
15978
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
15336
15979
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
@@ -16292,6 +16935,10 @@ class BreakevenGlobalService {
|
|
|
16292
16935
|
* Risk validation service for validating risk existence.
|
|
16293
16936
|
*/
|
|
16294
16937
|
this.riskValidationService = inject(TYPES.riskValidationService);
|
|
16938
|
+
/**
|
|
16939
|
+
* Exchange validation service for validating exchange existence.
|
|
16940
|
+
*/
|
|
16941
|
+
this.exchangeValidationService = inject(TYPES.exchangeValidationService);
|
|
16295
16942
|
/**
|
|
16296
16943
|
* Validates strategy and associated risk configuration.
|
|
16297
16944
|
* Memoized to avoid redundant validations for the same strategy-exchange-frame combination.
|
|
@@ -16305,6 +16952,7 @@ class BreakevenGlobalService {
|
|
|
16305
16952
|
methodName,
|
|
16306
16953
|
});
|
|
16307
16954
|
this.strategyValidationService.validate(context.strategyName, methodName);
|
|
16955
|
+
this.exchangeValidationService.validate(context.exchangeName, methodName);
|
|
16308
16956
|
const { riskName, riskList } = this.strategySchemaService.get(context.strategyName);
|
|
16309
16957
|
riskName && this.riskValidationService.validate(riskName, methodName);
|
|
16310
16958
|
riskList && riskList.forEach((riskName) => this.riskValidationService.validate(riskName, methodName));
|
|
@@ -17618,6 +18266,7 @@ const CANCEL_METHOD_NAME = "strategy.cancel";
|
|
|
17618
18266
|
const PARTIAL_PROFIT_METHOD_NAME = "strategy.partialProfit";
|
|
17619
18267
|
const PARTIAL_LOSS_METHOD_NAME = "strategy.partialLoss";
|
|
17620
18268
|
const TRAILING_STOP_METHOD_NAME = "strategy.trailingStop";
|
|
18269
|
+
const TRAILING_PROFIT_METHOD_NAME = "strategy.trailingProfit";
|
|
17621
18270
|
const BREAKEVEN_METHOD_NAME = "strategy.breakeven";
|
|
17622
18271
|
/**
|
|
17623
18272
|
* Stops the strategy from generating new signals.
|
|
@@ -17785,21 +18434,23 @@ async function partialLoss(symbol, percentToClose) {
|
|
|
17785
18434
|
*
|
|
17786
18435
|
* @param symbol - Trading pair symbol
|
|
17787
18436
|
* @param percentShift - Percentage adjustment to SL distance (-100 to 100)
|
|
18437
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
17788
18438
|
* @returns Promise that resolves when trailing SL is updated
|
|
17789
18439
|
*
|
|
17790
18440
|
* @example
|
|
17791
18441
|
* ```typescript
|
|
17792
18442
|
* import { trailingStop } from "backtest-kit";
|
|
17793
18443
|
*
|
|
17794
|
-
* // LONG: entry=100, originalSL=90, distance=10
|
|
17795
|
-
* // Tighten stop by 50%: newSL = 100 -
|
|
17796
|
-
* await trailingStop("BTCUSDT", -50);
|
|
18444
|
+
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
18445
|
+
* // Tighten stop by 50%: newSL = 100 - 5% = 95
|
|
18446
|
+
* await trailingStop("BTCUSDT", -50, 102);
|
|
17797
18447
|
* ```
|
|
17798
18448
|
*/
|
|
17799
|
-
async function trailingStop(symbol, percentShift) {
|
|
18449
|
+
async function trailingStop(symbol, percentShift, currentPrice) {
|
|
17800
18450
|
bt.loggerService.info(TRAILING_STOP_METHOD_NAME, {
|
|
17801
18451
|
symbol,
|
|
17802
18452
|
percentShift,
|
|
18453
|
+
currentPrice,
|
|
17803
18454
|
});
|
|
17804
18455
|
if (!ExecutionContextService.hasContext()) {
|
|
17805
18456
|
throw new Error("trailingStop requires an execution context");
|
|
@@ -17809,7 +18460,46 @@ async function trailingStop(symbol, percentShift) {
|
|
|
17809
18460
|
}
|
|
17810
18461
|
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
17811
18462
|
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
17812
|
-
await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, { exchangeName, frameName, strategyName });
|
|
18463
|
+
await bt.strategyCoreService.trailingStop(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
18464
|
+
}
|
|
18465
|
+
/**
|
|
18466
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
18467
|
+
*
|
|
18468
|
+
* Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
|
|
18469
|
+
* Negative percentShift brings TP closer to entry, positive percentShift moves it further.
|
|
18470
|
+
* Once direction is set on first call, subsequent calls must continue in same direction.
|
|
18471
|
+
*
|
|
18472
|
+
* Automatically detects backtest/live mode from execution context.
|
|
18473
|
+
*
|
|
18474
|
+
* @param symbol - Trading pair symbol
|
|
18475
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
18476
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
18477
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
18478
|
+
*
|
|
18479
|
+
* @example
|
|
18480
|
+
* ```typescript
|
|
18481
|
+
* import { trailingProfit } from "backtest-kit";
|
|
18482
|
+
*
|
|
18483
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
18484
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
18485
|
+
* await trailingProfit("BTCUSDT", 50, 102);
|
|
18486
|
+
* ```
|
|
18487
|
+
*/
|
|
18488
|
+
async function trailingProfit(symbol, percentShift, currentPrice) {
|
|
18489
|
+
bt.loggerService.info(TRAILING_PROFIT_METHOD_NAME, {
|
|
18490
|
+
symbol,
|
|
18491
|
+
percentShift,
|
|
18492
|
+
currentPrice,
|
|
18493
|
+
});
|
|
18494
|
+
if (!ExecutionContextService.hasContext()) {
|
|
18495
|
+
throw new Error("trailingProfit requires an execution context");
|
|
18496
|
+
}
|
|
18497
|
+
if (!MethodContextService.hasContext()) {
|
|
18498
|
+
throw new Error("trailingProfit requires a method context");
|
|
18499
|
+
}
|
|
18500
|
+
const { backtest: isBacktest } = bt.executionContextService.context;
|
|
18501
|
+
const { exchangeName, frameName, strategyName } = bt.methodContextService.context;
|
|
18502
|
+
await bt.strategyCoreService.trailingProfit(isBacktest, symbol, percentShift, currentPrice, { exchangeName, frameName, strategyName });
|
|
17813
18503
|
}
|
|
17814
18504
|
/**
|
|
17815
18505
|
* Moves stop-loss to breakeven when price reaches threshold.
|
|
@@ -19751,10 +20441,12 @@ const BACKTEST_METHOD_NAME_TASK = "BacktestUtils.task";
|
|
|
19751
20441
|
const BACKTEST_METHOD_NAME_GET_STATUS = "BacktestUtils.getStatus";
|
|
19752
20442
|
const BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL = "BacktestUtils.getPendingSignal";
|
|
19753
20443
|
const BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL = "BacktestUtils.getScheduledSignal";
|
|
20444
|
+
const BACKTEST_METHOD_NAME_GET_BREAKEVEN = "BacktestUtils.getBreakeven";
|
|
19754
20445
|
const BACKTEST_METHOD_NAME_CANCEL = "BacktestUtils.cancel";
|
|
19755
20446
|
const BACKTEST_METHOD_NAME_PARTIAL_PROFIT = "BacktestUtils.partialProfit";
|
|
19756
20447
|
const BACKTEST_METHOD_NAME_PARTIAL_LOSS = "BacktestUtils.partialLoss";
|
|
19757
20448
|
const BACKTEST_METHOD_NAME_TRAILING_STOP = "BacktestUtils.trailingStop";
|
|
20449
|
+
const BACKTEST_METHOD_NAME_TRAILING_PROFIT = "BacktestUtils.trailingProfit";
|
|
19758
20450
|
const BACKTEST_METHOD_NAME_GET_DATA = "BacktestUtils.getData";
|
|
19759
20451
|
/**
|
|
19760
20452
|
* Internal task function that runs backtest and handles completion.
|
|
@@ -20121,6 +20813,7 @@ class BacktestUtils {
|
|
|
20121
20813
|
context,
|
|
20122
20814
|
});
|
|
20123
20815
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
20816
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
20124
20817
|
{
|
|
20125
20818
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20126
20819
|
riskName &&
|
|
@@ -20152,6 +20845,7 @@ class BacktestUtils {
|
|
|
20152
20845
|
context,
|
|
20153
20846
|
});
|
|
20154
20847
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
20848
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
20155
20849
|
{
|
|
20156
20850
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20157
20851
|
riskName &&
|
|
@@ -20161,6 +20855,47 @@ class BacktestUtils {
|
|
|
20161
20855
|
}
|
|
20162
20856
|
return await bt.strategyCoreService.getScheduledSignal(true, symbol, context);
|
|
20163
20857
|
};
|
|
20858
|
+
/**
|
|
20859
|
+
* Checks if breakeven threshold has been reached for the current pending signal.
|
|
20860
|
+
*
|
|
20861
|
+
* Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
|
|
20862
|
+
* to cover transaction costs (slippage + fees) and allow breakeven to be set.
|
|
20863
|
+
*
|
|
20864
|
+
* @param symbol - Trading pair symbol
|
|
20865
|
+
* @param currentPrice - Current market price to check against threshold
|
|
20866
|
+
* @param context - Execution context with strategyName, exchangeName, frameName
|
|
20867
|
+
* @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
|
|
20868
|
+
*
|
|
20869
|
+
* @example
|
|
20870
|
+
* ```typescript
|
|
20871
|
+
* const canBreakeven = await Backtest.getBreakeven("BTCUSDT", 100.5, {
|
|
20872
|
+
* strategyName: "my-strategy",
|
|
20873
|
+
* exchangeName: "binance",
|
|
20874
|
+
* frameName: "backtest_frame"
|
|
20875
|
+
* });
|
|
20876
|
+
* if (canBreakeven) {
|
|
20877
|
+
* console.log("Breakeven threshold reached");
|
|
20878
|
+
* await Backtest.breakeven("BTCUSDT", 100.5, context);
|
|
20879
|
+
* }
|
|
20880
|
+
* ```
|
|
20881
|
+
*/
|
|
20882
|
+
this.getBreakeven = async (symbol, currentPrice, context) => {
|
|
20883
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_GET_BREAKEVEN, {
|
|
20884
|
+
symbol,
|
|
20885
|
+
currentPrice,
|
|
20886
|
+
context,
|
|
20887
|
+
});
|
|
20888
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
|
|
20889
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
|
|
20890
|
+
{
|
|
20891
|
+
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20892
|
+
riskName &&
|
|
20893
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_BREAKEVEN);
|
|
20894
|
+
riskList &&
|
|
20895
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_GET_BREAKEVEN));
|
|
20896
|
+
}
|
|
20897
|
+
return await bt.strategyCoreService.getBreakeven(true, symbol, currentPrice, context);
|
|
20898
|
+
};
|
|
20164
20899
|
/**
|
|
20165
20900
|
* Stops the strategy from generating new signals.
|
|
20166
20901
|
*
|
|
@@ -20189,6 +20924,7 @@ class BacktestUtils {
|
|
|
20189
20924
|
context,
|
|
20190
20925
|
});
|
|
20191
20926
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_STOP);
|
|
20927
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_STOP);
|
|
20192
20928
|
{
|
|
20193
20929
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20194
20930
|
riskName &&
|
|
@@ -20228,6 +20964,7 @@ class BacktestUtils {
|
|
|
20228
20964
|
cancelId,
|
|
20229
20965
|
});
|
|
20230
20966
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_CANCEL);
|
|
20967
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_CANCEL);
|
|
20231
20968
|
{
|
|
20232
20969
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20233
20970
|
riskName &&
|
|
@@ -20271,6 +21008,7 @@ class BacktestUtils {
|
|
|
20271
21008
|
context,
|
|
20272
21009
|
});
|
|
20273
21010
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT);
|
|
21011
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_PROFIT);
|
|
20274
21012
|
{
|
|
20275
21013
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20276
21014
|
riskName &&
|
|
@@ -20314,6 +21052,7 @@ class BacktestUtils {
|
|
|
20314
21052
|
context,
|
|
20315
21053
|
});
|
|
20316
21054
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_PARTIAL_LOSS);
|
|
21055
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_PARTIAL_LOSS);
|
|
20317
21056
|
{
|
|
20318
21057
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20319
21058
|
riskName &&
|
|
@@ -20331,27 +21070,30 @@ class BacktestUtils {
|
|
|
20331
21070
|
*
|
|
20332
21071
|
* @param symbol - Trading pair symbol
|
|
20333
21072
|
* @param percentShift - Percentage adjustment to SL distance (-100 to 100)
|
|
21073
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
20334
21074
|
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
20335
21075
|
* @returns Promise that resolves when trailing SL is updated
|
|
20336
21076
|
*
|
|
20337
21077
|
* @example
|
|
20338
21078
|
* ```typescript
|
|
20339
|
-
* // LONG: entry=100, originalSL=90, distance=10
|
|
20340
|
-
* // Tighten stop by 50%: newSL = 100 -
|
|
20341
|
-
* await Backtest.trailingStop("BTCUSDT", -50, {
|
|
21079
|
+
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
21080
|
+
* // Tighten stop by 50%: newSL = 100 - 5% = 95
|
|
21081
|
+
* await Backtest.trailingStop("BTCUSDT", -50, 102, {
|
|
20342
21082
|
* exchangeName: "binance",
|
|
20343
21083
|
* frameName: "frame1",
|
|
20344
21084
|
* strategyName: "my-strategy"
|
|
20345
21085
|
* });
|
|
20346
21086
|
* ```
|
|
20347
21087
|
*/
|
|
20348
|
-
this.trailingStop = async (symbol, percentShift, context) => {
|
|
21088
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
|
|
20349
21089
|
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_STOP, {
|
|
20350
21090
|
symbol,
|
|
20351
21091
|
percentShift,
|
|
21092
|
+
currentPrice,
|
|
20352
21093
|
context,
|
|
20353
21094
|
});
|
|
20354
21095
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_STOP);
|
|
21096
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_STOP);
|
|
20355
21097
|
{
|
|
20356
21098
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20357
21099
|
riskName &&
|
|
@@ -20359,7 +21101,49 @@ class BacktestUtils {
|
|
|
20359
21101
|
riskList &&
|
|
20360
21102
|
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_STOP));
|
|
20361
21103
|
}
|
|
20362
|
-
await bt.strategyCoreService.trailingStop(true, symbol, percentShift, context);
|
|
21104
|
+
await bt.strategyCoreService.trailingStop(true, symbol, percentShift, currentPrice, context);
|
|
21105
|
+
};
|
|
21106
|
+
/**
|
|
21107
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
21108
|
+
*
|
|
21109
|
+
* Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
|
|
21110
|
+
* Negative percentShift brings TP closer to entry, positive percentShift moves it further.
|
|
21111
|
+
* Once direction is set on first call, subsequent calls must continue in same direction.
|
|
21112
|
+
*
|
|
21113
|
+
* @param symbol - Trading pair symbol
|
|
21114
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
21115
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
21116
|
+
* @param context - Execution context with strategyName, exchangeName, and frameName
|
|
21117
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
21118
|
+
*
|
|
21119
|
+
* @example
|
|
21120
|
+
* ```typescript
|
|
21121
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
21122
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
21123
|
+
* await Backtest.trailingProfit("BTCUSDT", 50, 102, {
|
|
21124
|
+
* exchangeName: "binance",
|
|
21125
|
+
* frameName: "frame1",
|
|
21126
|
+
* strategyName: "my-strategy"
|
|
21127
|
+
* });
|
|
21128
|
+
* ```
|
|
21129
|
+
*/
|
|
21130
|
+
this.trailingProfit = async (symbol, percentShift, currentPrice, context) => {
|
|
21131
|
+
bt.loggerService.info(BACKTEST_METHOD_NAME_TRAILING_PROFIT, {
|
|
21132
|
+
symbol,
|
|
21133
|
+
percentShift,
|
|
21134
|
+
currentPrice,
|
|
21135
|
+
context,
|
|
21136
|
+
});
|
|
21137
|
+
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_TRAILING_PROFIT);
|
|
21138
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_TRAILING_PROFIT);
|
|
21139
|
+
{
|
|
21140
|
+
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21141
|
+
riskName &&
|
|
21142
|
+
bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT);
|
|
21143
|
+
riskList &&
|
|
21144
|
+
riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, BACKTEST_METHOD_NAME_TRAILING_PROFIT));
|
|
21145
|
+
}
|
|
21146
|
+
await bt.strategyCoreService.trailingProfit(true, symbol, percentShift, currentPrice, context);
|
|
20363
21147
|
};
|
|
20364
21148
|
/**
|
|
20365
21149
|
* Moves stop-loss to breakeven when price reaches threshold.
|
|
@@ -20389,6 +21173,7 @@ class BacktestUtils {
|
|
|
20389
21173
|
context,
|
|
20390
21174
|
});
|
|
20391
21175
|
bt.strategyValidationService.validate(context.strategyName, "Backtest.breakeven");
|
|
21176
|
+
bt.exchangeValidationService.validate(context.exchangeName, "Backtest.breakeven");
|
|
20392
21177
|
{
|
|
20393
21178
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20394
21179
|
riskName &&
|
|
@@ -20422,6 +21207,7 @@ class BacktestUtils {
|
|
|
20422
21207
|
context,
|
|
20423
21208
|
});
|
|
20424
21209
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_DATA);
|
|
21210
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_DATA);
|
|
20425
21211
|
{
|
|
20426
21212
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20427
21213
|
riskName &&
|
|
@@ -20456,6 +21242,7 @@ class BacktestUtils {
|
|
|
20456
21242
|
context,
|
|
20457
21243
|
});
|
|
20458
21244
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_GET_REPORT);
|
|
21245
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_GET_REPORT);
|
|
20459
21246
|
{
|
|
20460
21247
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20461
21248
|
riskName &&
|
|
@@ -20498,6 +21285,7 @@ class BacktestUtils {
|
|
|
20498
21285
|
path,
|
|
20499
21286
|
});
|
|
20500
21287
|
bt.strategyValidationService.validate(context.strategyName, BACKTEST_METHOD_NAME_DUMP);
|
|
21288
|
+
bt.exchangeValidationService.validate(context.exchangeName, BACKTEST_METHOD_NAME_DUMP);
|
|
20501
21289
|
{
|
|
20502
21290
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20503
21291
|
riskName &&
|
|
@@ -20556,10 +21344,12 @@ const LIVE_METHOD_NAME_TASK = "LiveUtils.task";
|
|
|
20556
21344
|
const LIVE_METHOD_NAME_GET_STATUS = "LiveUtils.getStatus";
|
|
20557
21345
|
const LIVE_METHOD_NAME_GET_PENDING_SIGNAL = "LiveUtils.getPendingSignal";
|
|
20558
21346
|
const LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL = "LiveUtils.getScheduledSignal";
|
|
21347
|
+
const LIVE_METHOD_NAME_GET_BREAKEVEN = "LiveUtils.getBreakeven";
|
|
20559
21348
|
const LIVE_METHOD_NAME_CANCEL = "LiveUtils.cancel";
|
|
20560
21349
|
const LIVE_METHOD_NAME_PARTIAL_PROFIT = "LiveUtils.partialProfit";
|
|
20561
21350
|
const LIVE_METHOD_NAME_PARTIAL_LOSS = "LiveUtils.partialLoss";
|
|
20562
21351
|
const LIVE_METHOD_NAME_TRAILING_STOP = "LiveUtils.trailingStop";
|
|
21352
|
+
const LIVE_METHOD_NAME_TRAILING_PROFIT = "LiveUtils.trailingProfit";
|
|
20563
21353
|
/**
|
|
20564
21354
|
* Internal task function that runs live trading and handles completion.
|
|
20565
21355
|
* Consumes live trading results and updates instance state flags.
|
|
@@ -20897,6 +21687,7 @@ class LiveUtils {
|
|
|
20897
21687
|
context,
|
|
20898
21688
|
});
|
|
20899
21689
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
21690
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
20900
21691
|
{
|
|
20901
21692
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20902
21693
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_PENDING_SIGNAL);
|
|
@@ -20930,6 +21721,7 @@ class LiveUtils {
|
|
|
20930
21721
|
context,
|
|
20931
21722
|
});
|
|
20932
21723
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
21724
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
20933
21725
|
{
|
|
20934
21726
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20935
21727
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_SCHEDULED_SIGNAL);
|
|
@@ -20941,6 +21733,48 @@ class LiveUtils {
|
|
|
20941
21733
|
frameName: "",
|
|
20942
21734
|
});
|
|
20943
21735
|
};
|
|
21736
|
+
/**
|
|
21737
|
+
* Checks if breakeven threshold has been reached for the current pending signal.
|
|
21738
|
+
*
|
|
21739
|
+
* Uses the same formula as BREAKEVEN_FN to determine if price has moved far enough
|
|
21740
|
+
* to cover transaction costs (slippage + fees) and allow breakeven to be set.
|
|
21741
|
+
*
|
|
21742
|
+
* @param symbol - Trading pair symbol
|
|
21743
|
+
* @param currentPrice - Current market price to check against threshold
|
|
21744
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
21745
|
+
* @returns Promise<boolean> - true if breakeven threshold reached, false otherwise
|
|
21746
|
+
*
|
|
21747
|
+
* @example
|
|
21748
|
+
* ```typescript
|
|
21749
|
+
* const canBreakeven = await Live.getBreakeven("BTCUSDT", 100.5, {
|
|
21750
|
+
* strategyName: "my-strategy",
|
|
21751
|
+
* exchangeName: "binance"
|
|
21752
|
+
* });
|
|
21753
|
+
* if (canBreakeven) {
|
|
21754
|
+
* console.log("Breakeven threshold reached");
|
|
21755
|
+
* await Live.breakeven("BTCUSDT", 100.5, context);
|
|
21756
|
+
* }
|
|
21757
|
+
* ```
|
|
21758
|
+
*/
|
|
21759
|
+
this.getBreakeven = async (symbol, currentPrice, context) => {
|
|
21760
|
+
bt.loggerService.info(LIVE_METHOD_NAME_GET_BREAKEVEN, {
|
|
21761
|
+
symbol,
|
|
21762
|
+
currentPrice,
|
|
21763
|
+
context,
|
|
21764
|
+
});
|
|
21765
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
21766
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
21767
|
+
{
|
|
21768
|
+
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21769
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN);
|
|
21770
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_BREAKEVEN));
|
|
21771
|
+
}
|
|
21772
|
+
return await bt.strategyCoreService.getBreakeven(false, symbol, currentPrice, {
|
|
21773
|
+
strategyName: context.strategyName,
|
|
21774
|
+
exchangeName: context.exchangeName,
|
|
21775
|
+
frameName: "",
|
|
21776
|
+
});
|
|
21777
|
+
};
|
|
20944
21778
|
/**
|
|
20945
21779
|
* Stops the strategy from generating new signals.
|
|
20946
21780
|
*
|
|
@@ -20964,6 +21798,7 @@ class LiveUtils {
|
|
|
20964
21798
|
context,
|
|
20965
21799
|
});
|
|
20966
21800
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_STOP);
|
|
21801
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_STOP);
|
|
20967
21802
|
{
|
|
20968
21803
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
20969
21804
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_STOP);
|
|
@@ -21005,6 +21840,7 @@ class LiveUtils {
|
|
|
21005
21840
|
cancelId,
|
|
21006
21841
|
});
|
|
21007
21842
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_CANCEL);
|
|
21843
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_CANCEL);
|
|
21008
21844
|
{
|
|
21009
21845
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21010
21846
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_CANCEL);
|
|
@@ -21049,6 +21885,7 @@ class LiveUtils {
|
|
|
21049
21885
|
context,
|
|
21050
21886
|
});
|
|
21051
21887
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
21888
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
21052
21889
|
{
|
|
21053
21890
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21054
21891
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_PROFIT);
|
|
@@ -21093,6 +21930,7 @@ class LiveUtils {
|
|
|
21093
21930
|
context,
|
|
21094
21931
|
});
|
|
21095
21932
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
21933
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
21096
21934
|
{
|
|
21097
21935
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21098
21936
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_PARTIAL_LOSS);
|
|
@@ -21112,32 +21950,78 @@ class LiveUtils {
|
|
|
21112
21950
|
*
|
|
21113
21951
|
* @param symbol - Trading pair symbol
|
|
21114
21952
|
* @param percentShift - Percentage adjustment to SL distance (-100 to 100)
|
|
21953
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
21115
21954
|
* @param context - Execution context with strategyName and exchangeName
|
|
21116
21955
|
* @returns Promise that resolves when trailing SL is updated
|
|
21117
21956
|
*
|
|
21118
21957
|
* @example
|
|
21119
21958
|
* ```typescript
|
|
21120
|
-
* // LONG: entry=100, originalSL=90, distance=10
|
|
21121
|
-
* // Tighten stop by 50%: newSL = 100 -
|
|
21122
|
-
* await Live.trailingStop("BTCUSDT", -50, {
|
|
21959
|
+
* // LONG: entry=100, originalSL=90, distance=10%, currentPrice=102
|
|
21960
|
+
* // Tighten stop by 50%: newSL = 100 - 5% = 95
|
|
21961
|
+
* await Live.trailingStop("BTCUSDT", -50, 102, {
|
|
21123
21962
|
* exchangeName: "binance",
|
|
21124
21963
|
* strategyName: "my-strategy"
|
|
21125
21964
|
* });
|
|
21126
21965
|
* ```
|
|
21127
21966
|
*/
|
|
21128
|
-
this.trailingStop = async (symbol, percentShift, context) => {
|
|
21967
|
+
this.trailingStop = async (symbol, percentShift, currentPrice, context) => {
|
|
21129
21968
|
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_STOP, {
|
|
21130
21969
|
symbol,
|
|
21131
21970
|
percentShift,
|
|
21971
|
+
currentPrice,
|
|
21132
21972
|
context,
|
|
21133
21973
|
});
|
|
21134
21974
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
21975
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
21135
21976
|
{
|
|
21136
21977
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21137
21978
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP);
|
|
21138
21979
|
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_STOP));
|
|
21139
21980
|
}
|
|
21140
|
-
await bt.strategyCoreService.trailingStop(false, symbol, percentShift, {
|
|
21981
|
+
await bt.strategyCoreService.trailingStop(false, symbol, percentShift, currentPrice, {
|
|
21982
|
+
strategyName: context.strategyName,
|
|
21983
|
+
exchangeName: context.exchangeName,
|
|
21984
|
+
frameName: "",
|
|
21985
|
+
});
|
|
21986
|
+
};
|
|
21987
|
+
/**
|
|
21988
|
+
* Adjusts the trailing take-profit distance for an active pending signal.
|
|
21989
|
+
*
|
|
21990
|
+
* Updates the take-profit distance by a percentage adjustment relative to the original TP distance.
|
|
21991
|
+
* Negative percentShift brings TP closer to entry, positive percentShift moves it further.
|
|
21992
|
+
* Once direction is set on first call, subsequent calls must continue in same direction.
|
|
21993
|
+
*
|
|
21994
|
+
* @param symbol - Trading pair symbol
|
|
21995
|
+
* @param percentShift - Percentage adjustment to TP distance (-100 to 100)
|
|
21996
|
+
* @param currentPrice - Current market price to check for intrusion
|
|
21997
|
+
* @param context - Execution context with strategyName and exchangeName
|
|
21998
|
+
* @returns Promise that resolves when trailing TP is updated
|
|
21999
|
+
*
|
|
22000
|
+
* @example
|
|
22001
|
+
* ```typescript
|
|
22002
|
+
* // LONG: entry=100, originalTP=110, distance=10%, currentPrice=102
|
|
22003
|
+
* // Move TP further by 50%: newTP = 100 + 15% = 115
|
|
22004
|
+
* await Live.trailingProfit("BTCUSDT", 50, 102, {
|
|
22005
|
+
* exchangeName: "binance",
|
|
22006
|
+
* strategyName: "my-strategy"
|
|
22007
|
+
* });
|
|
22008
|
+
* ```
|
|
22009
|
+
*/
|
|
22010
|
+
this.trailingProfit = async (symbol, percentShift, currentPrice, context) => {
|
|
22011
|
+
bt.loggerService.info(LIVE_METHOD_NAME_TRAILING_PROFIT, {
|
|
22012
|
+
symbol,
|
|
22013
|
+
percentShift,
|
|
22014
|
+
currentPrice,
|
|
22015
|
+
context,
|
|
22016
|
+
});
|
|
22017
|
+
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
22018
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
22019
|
+
{
|
|
22020
|
+
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22021
|
+
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT);
|
|
22022
|
+
riskList && riskList.forEach((riskName) => bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_TRAILING_PROFIT));
|
|
22023
|
+
}
|
|
22024
|
+
await bt.strategyCoreService.trailingProfit(false, symbol, percentShift, currentPrice, {
|
|
21141
22025
|
strategyName: context.strategyName,
|
|
21142
22026
|
exchangeName: context.exchangeName,
|
|
21143
22027
|
frameName: "",
|
|
@@ -21171,6 +22055,7 @@ class LiveUtils {
|
|
|
21171
22055
|
context,
|
|
21172
22056
|
});
|
|
21173
22057
|
bt.strategyValidationService.validate(context.strategyName, "Live.breakeven");
|
|
22058
|
+
bt.exchangeValidationService.validate(context.exchangeName, "Live.breakeven");
|
|
21174
22059
|
{
|
|
21175
22060
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21176
22061
|
riskName && bt.riskValidationService.validate(riskName, "Live.breakeven");
|
|
@@ -21206,6 +22091,7 @@ class LiveUtils {
|
|
|
21206
22091
|
context,
|
|
21207
22092
|
});
|
|
21208
22093
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_DATA);
|
|
22094
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_DATA);
|
|
21209
22095
|
{
|
|
21210
22096
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21211
22097
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_DATA);
|
|
@@ -21238,6 +22124,7 @@ class LiveUtils {
|
|
|
21238
22124
|
context,
|
|
21239
22125
|
});
|
|
21240
22126
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_GET_REPORT);
|
|
22127
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_GET_REPORT);
|
|
21241
22128
|
{
|
|
21242
22129
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21243
22130
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_GET_REPORT);
|
|
@@ -21278,6 +22165,7 @@ class LiveUtils {
|
|
|
21278
22165
|
path,
|
|
21279
22166
|
});
|
|
21280
22167
|
bt.strategyValidationService.validate(context.strategyName, LIVE_METHOD_NAME_DUMP);
|
|
22168
|
+
bt.exchangeValidationService.validate(context.exchangeName, LIVE_METHOD_NAME_DUMP);
|
|
21281
22169
|
{
|
|
21282
22170
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21283
22171
|
riskName && bt.riskValidationService.validate(riskName, LIVE_METHOD_NAME_DUMP);
|
|
@@ -21371,6 +22259,7 @@ class ScheduleUtils {
|
|
|
21371
22259
|
backtest,
|
|
21372
22260
|
});
|
|
21373
22261
|
bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_DATA);
|
|
22262
|
+
bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_DATA);
|
|
21374
22263
|
{
|
|
21375
22264
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21376
22265
|
riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_DATA);
|
|
@@ -21399,6 +22288,7 @@ class ScheduleUtils {
|
|
|
21399
22288
|
backtest,
|
|
21400
22289
|
});
|
|
21401
22290
|
bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
22291
|
+
bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
21402
22292
|
{
|
|
21403
22293
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21404
22294
|
riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_GET_REPORT);
|
|
@@ -21431,6 +22321,7 @@ class ScheduleUtils {
|
|
|
21431
22321
|
path,
|
|
21432
22322
|
});
|
|
21433
22323
|
bt.strategyValidationService.validate(context.strategyName, SCHEDULE_METHOD_NAME_DUMP);
|
|
22324
|
+
bt.exchangeValidationService.validate(context.exchangeName, SCHEDULE_METHOD_NAME_DUMP);
|
|
21434
22325
|
{
|
|
21435
22326
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21436
22327
|
riskName && bt.riskValidationService.validate(riskName, SCHEDULE_METHOD_NAME_DUMP);
|
|
@@ -21519,6 +22410,7 @@ class Performance {
|
|
|
21519
22410
|
*/
|
|
21520
22411
|
static async getData(symbol, context, backtest = false) {
|
|
21521
22412
|
bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_DATA);
|
|
22413
|
+
bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_DATA);
|
|
21522
22414
|
{
|
|
21523
22415
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21524
22416
|
riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_DATA);
|
|
@@ -21551,6 +22443,7 @@ class Performance {
|
|
|
21551
22443
|
*/
|
|
21552
22444
|
static async getReport(symbol, context, backtest = false, columns) {
|
|
21553
22445
|
bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
22446
|
+
bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
21554
22447
|
{
|
|
21555
22448
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21556
22449
|
riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_GET_REPORT);
|
|
@@ -21580,6 +22473,7 @@ class Performance {
|
|
|
21580
22473
|
*/
|
|
21581
22474
|
static async dump(symbol, context, backtest = false, path = "./dump/performance", columns) {
|
|
21582
22475
|
bt.strategyValidationService.validate(context.strategyName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
22476
|
+
bt.exchangeValidationService.validate(context.exchangeName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
21583
22477
|
{
|
|
21584
22478
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
21585
22479
|
riskName && bt.riskValidationService.validate(riskName, PERFORMANCE_METHOD_NAME_DUMP);
|
|
@@ -22170,6 +23064,7 @@ class HeatUtils {
|
|
|
22170
23064
|
this.getData = async (context, backtest = false) => {
|
|
22171
23065
|
bt.loggerService.info(HEAT_METHOD_NAME_GET_DATA, { strategyName: context.strategyName });
|
|
22172
23066
|
bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_DATA);
|
|
23067
|
+
bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_DATA);
|
|
22173
23068
|
{
|
|
22174
23069
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22175
23070
|
riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_DATA);
|
|
@@ -22211,6 +23106,7 @@ class HeatUtils {
|
|
|
22211
23106
|
this.getReport = async (context, backtest = false, columns) => {
|
|
22212
23107
|
bt.loggerService.info(HEAT_METHOD_NAME_GET_REPORT, { strategyName: context.strategyName });
|
|
22213
23108
|
bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_GET_REPORT);
|
|
23109
|
+
bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_GET_REPORT);
|
|
22214
23110
|
{
|
|
22215
23111
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22216
23112
|
riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_GET_REPORT);
|
|
@@ -22249,6 +23145,7 @@ class HeatUtils {
|
|
|
22249
23145
|
this.dump = async (context, backtest = false, path, columns) => {
|
|
22250
23146
|
bt.loggerService.info(HEAT_METHOD_NAME_DUMP, { strategyName: context.strategyName, path });
|
|
22251
23147
|
bt.strategyValidationService.validate(context.strategyName, HEAT_METHOD_NAME_DUMP);
|
|
23148
|
+
bt.exchangeValidationService.validate(context.exchangeName, HEAT_METHOD_NAME_DUMP);
|
|
22252
23149
|
{
|
|
22253
23150
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22254
23151
|
riskName && bt.riskValidationService.validate(riskName, HEAT_METHOD_NAME_DUMP);
|
|
@@ -22581,6 +23478,7 @@ class PartialUtils {
|
|
|
22581
23478
|
this.getData = async (symbol, context, backtest = false) => {
|
|
22582
23479
|
bt.loggerService.info(PARTIAL_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
22583
23480
|
bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_DATA);
|
|
23481
|
+
bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_DATA);
|
|
22584
23482
|
{
|
|
22585
23483
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22586
23484
|
riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_DATA);
|
|
@@ -22630,6 +23528,7 @@ class PartialUtils {
|
|
|
22630
23528
|
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
22631
23529
|
bt.loggerService.info(PARTIAL_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
22632
23530
|
bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
23531
|
+
bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
22633
23532
|
{
|
|
22634
23533
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22635
23534
|
riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_GET_REPORT);
|
|
@@ -22672,6 +23571,7 @@ class PartialUtils {
|
|
|
22672
23571
|
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
22673
23572
|
bt.loggerService.info(PARTIAL_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
22674
23573
|
bt.strategyValidationService.validate(context.strategyName, PARTIAL_METHOD_NAME_DUMP);
|
|
23574
|
+
bt.exchangeValidationService.validate(context.exchangeName, PARTIAL_METHOD_NAME_DUMP);
|
|
22675
23575
|
{
|
|
22676
23576
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
22677
23577
|
riskName && bt.riskValidationService.validate(riskName, PARTIAL_METHOD_NAME_DUMP);
|
|
@@ -23861,6 +24761,7 @@ class BreakevenUtils {
|
|
|
23861
24761
|
this.getData = async (symbol, context, backtest = false) => {
|
|
23862
24762
|
bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_DATA, { symbol, strategyName: context.strategyName });
|
|
23863
24763
|
bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_DATA);
|
|
24764
|
+
bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_DATA);
|
|
23864
24765
|
{
|
|
23865
24766
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
23866
24767
|
riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_DATA);
|
|
@@ -23906,6 +24807,7 @@ class BreakevenUtils {
|
|
|
23906
24807
|
this.getReport = async (symbol, context, backtest = false, columns) => {
|
|
23907
24808
|
bt.loggerService.info(BREAKEVEN_METHOD_NAME_GET_REPORT, { symbol, strategyName: context.strategyName });
|
|
23908
24809
|
bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_GET_REPORT);
|
|
24810
|
+
bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_GET_REPORT);
|
|
23909
24811
|
{
|
|
23910
24812
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
23911
24813
|
riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_GET_REPORT);
|
|
@@ -23948,6 +24850,7 @@ class BreakevenUtils {
|
|
|
23948
24850
|
this.dump = async (symbol, context, backtest = false, path, columns) => {
|
|
23949
24851
|
bt.loggerService.info(BREAKEVEN_METHOD_NAME_DUMP, { symbol, strategyName: context.strategyName, path });
|
|
23950
24852
|
bt.strategyValidationService.validate(context.strategyName, BREAKEVEN_METHOD_NAME_DUMP);
|
|
24853
|
+
bt.exchangeValidationService.validate(context.exchangeName, BREAKEVEN_METHOD_NAME_DUMP);
|
|
23951
24854
|
{
|
|
23952
24855
|
const { riskName, riskList } = bt.strategySchemaService.get(context.strategyName);
|
|
23953
24856
|
riskName && bt.riskValidationService.validate(riskName, BREAKEVEN_METHOD_NAME_DUMP);
|
|
@@ -23973,4 +24876,4 @@ class BreakevenUtils {
|
|
|
23973
24876
|
*/
|
|
23974
24877
|
const Breakeven = new BreakevenUtils();
|
|
23975
24878
|
|
|
23976
|
-
export { Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, MethodContextService, Notification, Optimizer, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Risk, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, breakeven, cancel, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getColumns, getConfig, getDate, getDefaultColumns, getDefaultConfig, getMode, hasTradeContext, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenBreakeven, listenBreakevenOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenPing, listenPingOnce, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, partialLoss, partialProfit, setColumns, setConfig, setLogger, stop, trailingStop, validate };
|
|
24879
|
+
export { Backtest, Breakeven, Cache, Constant, Exchange, ExecutionContextService, Heat, Live, MethodContextService, Notification, Optimizer, Partial, Performance, PersistBase, PersistBreakevenAdapter, PersistPartialAdapter, PersistRiskAdapter, PersistScheduleAdapter, PersistSignalAdapter, PositionSize, Risk, Schedule, Walker, addExchange, addFrame, addOptimizer, addRisk, addSizing, addStrategy, addWalker, breakeven, cancel, dumpSignal, emitters, formatPrice, formatQuantity, getAveragePrice, getCandles, getColumns, getConfig, getDate, getDefaultColumns, getDefaultConfig, getMode, hasTradeContext, backtest as lib, listExchanges, listFrames, listOptimizers, listRisks, listSizings, listStrategies, listWalkers, listenBacktestProgress, listenBreakeven, listenBreakevenOnce, listenDoneBacktest, listenDoneBacktestOnce, listenDoneLive, listenDoneLiveOnce, listenDoneWalker, listenDoneWalkerOnce, listenError, listenExit, listenOptimizerProgress, listenPartialLoss, listenPartialLossOnce, listenPartialProfit, listenPartialProfitOnce, listenPerformance, listenPing, listenPingOnce, listenRisk, listenRiskOnce, listenSignal, listenSignalBacktest, listenSignalBacktestOnce, listenSignalLive, listenSignalLiveOnce, listenSignalOnce, listenValidation, listenWalker, listenWalkerComplete, listenWalkerOnce, listenWalkerProgress, partialLoss, partialProfit, setColumns, setConfig, setLogger, stop, trailingProfit, trailingStop, validate };
|