From 40cd478c6d28e8a2668f33f811a659794121a93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Sat, 22 Jan 2022 18:01:02 +0100 Subject: [PATCH 1/4] Calculate stoploss_from_absolute for shorts --- freqtrade/strategy/strategy_helper.py | 12 ++++++++---- tests/strategy/test_strategy_helpers.py | 7 +++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 97a8ec960..2ae0e8cf0 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -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..244fa25bf 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -159,6 +159,13 @@ def test_stoploss_from_absolute(): assert stoploss_from_absolute(100, 0) == 1 assert stoploss_from_absolute(0, 100) == 1 + assert stoploss_from_absolute(90, 100, True) == 0 + assert stoploss_from_absolute(100, 100, True) == 0 + assert stoploss_from_absolute(110, 100, True) == -(1 - (110/100)) + assert stoploss_from_absolute(100, 0, True) == 1 + assert stoploss_from_absolute(0, 100, True) == 0 + assert stoploss_from_absolute(100, 1, True) == 1 + # TODO-lev: @pytest.mark.parametrize('candle_type', ['mark', '']) def test_informative_decorator(mocker, default_conf): From 17ae6a0c78ac771ffe76b7d15ba4612f483ac43c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Sat, 22 Jan 2022 18:01:56 +0100 Subject: [PATCH 2/4] Harmonize short parameter name in stoploss_from_open() --- freqtrade/strategy/strategy_helper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 2ae0e8cf0..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)) From 4389ce1a8f856b640577648391eba2287b2830a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Jan 2022 19:12:28 +0100 Subject: [PATCH 3/4] Update helpers documentation for is_short --- docs/strategy-callbacks.md | 6 +++--- docs/strategy-customization.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 2e1c484ca..7bb20236b 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -283,11 +283,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 e90d87c4a..c1948b570 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) ``` From 325fd8a7802fd533247edda120f928576f93c76f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Jan 2022 19:15:42 +0100 Subject: [PATCH 4/4] Add test with absolute values --- tests/strategy/test_strategy_helpers.py | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 244fa25bf..c52a02ab9 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -153,18 +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 stoploss_from_absolute(90, 100, True) == 0 - assert stoploss_from_absolute(100, 100, True) == 0 - assert stoploss_from_absolute(110, 100, True) == -(1 - (110/100)) - assert stoploss_from_absolute(100, 0, True) == 1 - assert stoploss_from_absolute(0, 100, True) == 0 - assert stoploss_from_absolute(100, 1, True) == 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', ''])