diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 38875daf5..16e412a32 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -284,11 +284,11 @@ class AwesomeStrategy(IStrategy): # evaluate highest to lowest, so that highest possible stop is used if current_profit > 0.40: - return stoploss_from_open(0.25, current_profit) + return stoploss_from_open(0.25, current_profit, is_short=trade.is_short) elif current_profit > 0.25: - return stoploss_from_open(0.15, current_profit) + return stoploss_from_open(0.15, current_profit, is_short=trade.is_short) elif current_profit > 0.20: - return stoploss_from_open(0.07, current_profit) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) # return maximum stoploss value, keeping current stoploss price unchanged return 1 diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index 21654f2b7..f43386615 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -791,7 +791,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati Say the open price was $100, and `current_price` is $121 (`current_profit` will be `0.21`). - If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. + If we want a stop price at 7% above the open price we can call `stoploss_from_open(0.07, current_profit, False)` which will return `0.1157024793`. 11.57% below $121 is $107, which is the same as 7% above $100. ``` python @@ -811,7 +811,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati # once the profit has risen above 10%, keep the stoploss at 7% above the open price if current_profit > 0.10: - return stoploss_from_open(0.07, current_profit) + return stoploss_from_open(0.07, current_profit, is_short=trade.is_short) return 1 @@ -832,7 +832,7 @@ In some situations it may be confusing to deal with stops relative to current ra ??? Example "Returning a stoploss using absolute price from the custom stoploss function" - If we want to trail a stop price at 2xATR below current proce we can call `stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate)`. + If we want to trail a stop price at 2xATR below current proce we can call `stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate, is_short=trade.is_short)`. ``` python @@ -852,7 +852,7 @@ In some situations it may be confusing to deal with stops relative to current ra current_rate: float, current_profit: float, **kwargs) -> float: dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) candle = dataframe.iloc[-1].squeeze() - return stoploss_from_absolute(current_rate - (candle['atr'] * 2), current_rate) + return stoploss_from_absolute(current_rate - (candle['atr'] * 2, is_short=trade.is_short), current_rate) ``` diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 97a8ec960..f07c14e24 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -69,7 +69,7 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, def stoploss_from_open( open_relative_stop: float, current_profit: float, - for_short: bool = False + is_short: bool = False ) -> float: """ @@ -84,15 +84,15 @@ def stoploss_from_open( :param open_relative_stop: Desired stop loss percentage relative to open price :param current_profit: The current profit percentage - :param for_short: When true, perform the calculation for short instead of long + :param is_short: When true, perform the calculation for short instead of long :return: Stop loss value relative to current price """ # formula is undefined for current_profit -1 (longs) or 1 (shorts), return maximum value - if (current_profit == -1 and not for_short) or (for_short and current_profit == 1): + if (current_profit == -1 and not is_short) or (is_short and current_profit == 1): return 1 - if for_short is True: + if is_short is True: stoploss = -1+((1-open_relative_stop)/(1-current_profit)) else: stoploss = 1-((1+open_relative_stop)/(1+current_profit)) @@ -102,9 +102,8 @@ def stoploss_from_open( return max(stoploss, 0.0) -def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: +def stoploss_from_absolute(stop_rate: float, current_rate: float, is_short: bool = False) -> float: """ - TODO-lev: Update this method with "is_short" formula Given current price and desired stop price, return a stop loss value that is relative to current price. @@ -115,6 +114,7 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: :param stop_rate: Stop loss price. :param current_rate: Current asset price. + :param is_short: When true, perform the calculation for short instead of long :return: Positive stop loss value relative to current price """ @@ -123,6 +123,10 @@ def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: return 1 stoploss = 1 - (stop_rate / current_rate) + if is_short: + stoploss = -stoploss - # negative stoploss values indicate the requested stop price is higher than the current price - return max(stoploss, 0.0) + # negative stoploss values indicate the requested stop price is higher/lower + # (long/short) than the current price + # shorts can yield stoploss values higher than 1, so limit that as well + return max(min(stoploss, 1.0), 0.0) diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 6c933d2f1..c52a02ab9 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -153,11 +153,22 @@ def test_stoploss_from_open(): def test_stoploss_from_absolute(): - assert stoploss_from_absolute(90, 100) == 1 - (90 / 100) - assert stoploss_from_absolute(100, 100) == 0 - assert stoploss_from_absolute(110, 100) == 0 - assert stoploss_from_absolute(100, 0) == 1 - assert stoploss_from_absolute(0, 100) == 1 + assert pytest.approx(stoploss_from_absolute(90, 100)) == 1 - (90 / 100) + assert pytest.approx(stoploss_from_absolute(90, 100)) == 0.1 + assert pytest.approx(stoploss_from_absolute(95, 100)) == 0.05 + assert pytest.approx(stoploss_from_absolute(100, 100)) == 0 + assert pytest.approx(stoploss_from_absolute(110, 100)) == 0 + assert pytest.approx(stoploss_from_absolute(100, 0)) == 1 + assert pytest.approx(stoploss_from_absolute(0, 100)) == 1 + + assert pytest.approx(stoploss_from_absolute(90, 100, True)) == 0 + assert pytest.approx(stoploss_from_absolute(100, 100, True)) == 0 + assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110/100)) + assert pytest.approx(stoploss_from_absolute(110, 100, True)) == 0.1 + assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05 + assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1 + assert pytest.approx(stoploss_from_absolute(0, 100, True)) == 0 + assert pytest.approx(stoploss_from_absolute(100, 1, True)) == 1 # TODO-lev: @pytest.mark.parametrize('candle_type', ['mark', ''])