From d28287880c2fdafa7f5851b8c4b9f0e830e68421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Rodr=C3=ADguez?= Date: Sat, 15 Jan 2022 04:30:30 +0100 Subject: [PATCH] Add support for shorts in strategy.stoploss_from_open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guillermo Rodríguez --- freqtrade/strategy/strategy_helper.py | 21 ++++----- tests/strategy/test_strategy_helpers.py | 60 +++++++++++++++---------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index 126a9c6c5..a87239a71 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -1,6 +1,5 @@ import pandas as pd -from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes @@ -81,30 +80,26 @@ def stoploss_from_open( The requested stop can be positive for a stop above the open price, or negative for a stop below the open price. The return value is always >= 0. - Returns 0 if the resulting stop price would be above the current price. + Returns 0 if the resulting stop price would be above/below (longs/shorts) the current price :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 :return: Stop loss value relative to current price """ - # formula is undefined for current_profit -1, return maximum value - if current_profit == -1: + # 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): return 1 if for_short is True: - # TODO-lev: How would this be calculated for short - raise OperationalException( - "Freqtrade hasn't figured out how to calculated stoploss on shorts") - # stoploss = 1-((1+open_relative_stop)/(1+current_profit)) + stoploss = -1+((1-open_relative_stop)/(1-current_profit)) else: stoploss = 1-((1+open_relative_stop)/(1+current_profit)) - # negative stoploss values indicate the requested stop price is higher than the current price - if for_short: - return min(stoploss, 0.0) - else: - return max(stoploss, 0.0) + # negative stoploss values indicate the requested stop price is higher/lower + # (long/short) than the current price + return max(stoploss, 0.0) def stoploss_from_absolute(stop_rate: float, current_rate: float) -> float: diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index a1b6f57d5..ef4b7f4e2 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -109,33 +109,47 @@ def test_stoploss_from_open(): [1, 100, 30], [100, 10000, 30], ] - current_profit_range = [-0.99, 2, 30] + # profit range for long is [-1, inf] while for shorts is [-inf, 1] + current_profit_range_dict = {'long': [-0.99, 2, 30], 'short': [-2.0, 0.99, 30]} desired_stop_range = [-0.50, 0.50, 30] - for open_range in open_price_ranges: - for open_price in np.linspace(*open_range): - for desired_stop in np.linspace(*desired_stop_range): + for side, current_profit_range in current_profit_range_dict.items(): + for open_range in open_price_ranges: + for open_price in np.linspace(*open_range): + for desired_stop in np.linspace(*desired_stop_range): - # -1 is not a valid current_profit, should return 1 - assert stoploss_from_open(desired_stop, -1) == 1 - - for current_profit in np.linspace(*current_profit_range): - current_price = open_price * (1 + current_profit) - expected_stop_price = open_price * (1 + desired_stop) - - stoploss = stoploss_from_open(desired_stop, current_profit) - - assert stoploss >= 0 - assert stoploss <= 1 - - stop_price = current_price * (1 - stoploss) - - # there is no correct answer if the expected stop price is above - # the current price - if expected_stop_price > current_price: - assert stoploss == 0 + if side == 'long': + # -1 is not a valid current_profit, should return 1 + assert stoploss_from_open(desired_stop, -1) == 1 else: - assert isclose(stop_price, expected_stop_price, rel_tol=0.00001) + # 1 is not a valid current_profit for shorts, should return 1 + assert stoploss_from_open(desired_stop, 1, True) == 1 + + for current_profit in np.linspace(*current_profit_range): + if side == 'long': + current_price = open_price * (1 + current_profit) + expected_stop_price = open_price * (1 + desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit) + stop_price = current_price * (1 - stoploss) + else: + current_price = open_price * (1 - current_profit) + expected_stop_price = open_price * (1 - desired_stop) + stoploss = stoploss_from_open(desired_stop, current_profit, True) + stop_price = current_price * (1 + stoploss) + + assert stoploss >= 0 + # Technically the formula can yield values greater than 1 for shorts + # eventhough it doesn't make sense because the position would be liquidated + if side == 'long': + assert stoploss <= 1 + + # there is no correct answer if the expected stop price is above + # the current price + if (side == 'long' and expected_stop_price > current_price) \ + or (side == 'short' and expected_stop_price < current_price): + assert stoploss == 0 + else: + assert isclose(stop_price, expected_stop_price, rel_tol=0.00001) def test_stoploss_from_absolute():