From 092780df9d48de631bc09ea9d1b093c7f3e21ed0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 18 Aug 2021 04:19:17 -0600 Subject: [PATCH] condensed strategy methods down to 2 --- freqtrade/edge/edge_positioning.py | 10 +- freqtrade/enums/signaltype.py | 2 +- freqtrade/optimize/backtesting.py | 9 +- freqtrade/optimize/hyperopt.py | 13 +- freqtrade/resolvers/strategy_resolver.py | 13 +- freqtrade/rpc/api_server/uvicorn_threaded.py | 2 +- freqtrade/strategy/interface.py | 87 +++--- freqtrade/strategy/strategy_helper.py | 9 +- freqtrade/templates/sample_hyperopt.py | 237 ++++++---------- .../templates/sample_hyperopt_advanced.py | 233 ++++++--------- freqtrade/templates/sample_strategy.py | 41 +-- tests/optimize/hyperopts/default_hyperopt.py | 267 ++++++------------ tests/optimize/test_backtest_detail.py | 4 +- tests/optimize/test_backtesting.py | 20 +- tests/optimize/test_hyperopt.py | 40 ++- tests/rpc/test_rpc_apiserver.py | 2 +- tests/strategy/strats/default_strategy.py | 44 +-- .../strategy/strats/hyperoptable_strategy.py | 50 ++-- tests/strategy/strats/legacy_strategy.py | 30 -- tests/strategy/test_default_strategy.py | 27 +- tests/strategy/test_interface.py | 32 +-- tests/strategy/test_strategy_loading.py | 52 +--- 22 files changed, 451 insertions(+), 773 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index b366059da..9c1dd4d24 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -167,14 +167,12 @@ class Edge: pair_data = pair_data.sort_values(by=['date']) pair_data = pair_data.reset_index(drop=True) - df_analyzed = self.strategy.advise_exit( - dataframe=self.strategy.advise_enter( + df_analyzed = self.strategy.advise_sell( + dataframe=self.strategy.advise_buy( dataframe=pair_data, - metadata={'pair': pair}, - is_short=False + metadata={'pair': pair} ), - metadata={'pair': pair}, - is_short=False + metadata={'pair': pair} )[headers].copy() trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range) diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index ffba5ee90..fcebd9f0e 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -16,4 +16,4 @@ class SignalTagType(Enum): Enum for signal columns """ BUY_TAG = "buy_tag" - SELL_TAG = "sell_tag" + SHORT_TAG = "short_tag" diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 550ceecd8..cce3b6a0d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -231,8 +231,13 @@ class Backtesting: if has_buy_tag: pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist - df_analyzed = self.strategy.advise_exit( - self.strategy.advise_enter(pair_data, {'pair': pair}), {'pair': pair}).copy() + df_analyzed = self.strategy.advise_sell( + self.strategy.advise_buy( + pair_data, + {'pair': pair} + ), + {'pair': pair} + ).copy() # Trim startup period from analyzed dataframe df_analyzed = trim_dataframe(df_analyzed, self.timerange, startup_candles=self.required_startup) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 4c07419b8..5c627df35 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -110,7 +110,7 @@ class Hyperopt: self.backtesting.strategy.advise_indicators = ( # type: ignore self.custom_hyperopt.populate_indicators) # type: ignore if hasattr(self.custom_hyperopt, 'populate_buy_trend'): - self.backtesting.strategy.advise_enter = ( # type: ignore + self.backtesting.strategy.advise_buy = ( # type: ignore self.custom_hyperopt.populate_buy_trend) # type: ignore if hasattr(self.custom_hyperopt, 'populate_sell_trend'): self.backtesting.strategy.advise_sell = ( # type: ignore @@ -283,14 +283,15 @@ class Hyperopt: params_dict = self._get_params_dict(self.dimensions, raw_params) # Apply parameters - # TODO-lev: These don't take a side, how can I pass is_short=True/False to it if HyperoptTools.has_space(self.config, 'buy'): - self.backtesting.strategy.advise_enter = ( # type: ignore - self.custom_hyperopt.buy_strategy_generator(params_dict)) + self.backtesting.strategy.advise_buy = ( # type: ignore + self.custom_hyperopt.buy_strategy_generator(params_dict) + ) if HyperoptTools.has_space(self.config, 'sell'): - self.backtesting.strategy.advise_exit = ( # type: ignore - self.custom_hyperopt.sell_strategy_generator(params_dict)) + self.backtesting.strategy.advise_sell = ( # type: ignore + self.custom_hyperopt.sell_strategy_generator(params_dict) + ) if HyperoptTools.has_space(self.config, 'protection'): for attr_name, attr in self.backtesting.strategy.enumerate_parameters('protection'): diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 38a5b4850..afb5916f1 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -202,14 +202,11 @@ class StrategyResolver(IResolver): strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) - strategy._short_fun_len = len(getfullargspec(strategy.populate_short_trend).args) - strategy._exit_short_fun_len = len( - getfullargspec(strategy.populate_exit_short_trend).args) - if any(x == 2 for x in [strategy._populate_fun_len, - strategy._buy_fun_len, - strategy._sell_fun_len, - strategy._short_fun_len, - strategy._exit_short_fun_len]): + if any(x == 2 for x in [ + strategy._populate_fun_len, + strategy._buy_fun_len, + strategy._sell_fun_len + ]): strategy.INTERFACE_VERSION = 1 return strategy diff --git a/freqtrade/rpc/api_server/uvicorn_threaded.py b/freqtrade/rpc/api_server/uvicorn_threaded.py index 7d76d52ed..2f72cb74c 100644 --- a/freqtrade/rpc/api_server/uvicorn_threaded.py +++ b/freqtrade/rpc/api_server/uvicorn_threaded.py @@ -44,5 +44,5 @@ class UvicornServer(uvicorn.Server): time.sleep(1e-3) def cleanup(self): - self.should_sell = True + self.should_exit = True self.thread.join() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 26ad2fcd4..b56a54d14 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -62,8 +62,6 @@ class IStrategy(ABC, HyperStrategyMixin): _populate_fun_len: int = 0 _buy_fun_len: int = 0 _sell_fun_len: int = 0 - _short_fun_len: int = 0 - _exit_short_fun_len: int = 0 _ft_params_from_file: Dict = {} # associated minimal roi minimal_roi: Dict @@ -145,7 +143,7 @@ class IStrategy(ABC, HyperStrategyMixin): return dataframe @abstractmethod - def populate_enter_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame @@ -155,7 +153,7 @@ class IStrategy(ABC, HyperStrategyMixin): return dataframe @abstractmethod - def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame @@ -166,7 +164,7 @@ class IStrategy(ABC, HyperStrategyMixin): def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: """ - Check enter timeout function callback. + Check buy timeout function callback. This method can be used to override the enter-timeout. It is called whenever a limit buy/short order has been created, and is not yet fully filled. @@ -184,7 +182,7 @@ class IStrategy(ABC, HyperStrategyMixin): def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: """ - Check exit timeout function callback. + Check sell timeout function callback. This method can be used to override the exit-timeout. It is called whenever a (long) limit sell order or (short) limit buy has been created, and is not yet fully filled. @@ -396,10 +394,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ logger.debug("TA Analysis Launched") dataframe = self.advise_indicators(dataframe, metadata) - dataframe = self.advise_enter(dataframe, metadata, is_short=False) - dataframe = self.advise_exit(dataframe, metadata, is_short=False) - dataframe = self.advise_enter(dataframe, metadata, is_short=True) - dataframe = self.advise_exit(dataframe, metadata, is_short=True) + dataframe = self.advise_buy(dataframe, metadata) + dataframe = self.advise_sell(dataframe, metadata) return dataframe def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -426,7 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug("Skipping TA Analysis for already analyzed candle") dataframe['buy'] = 0 dataframe['sell'] = 0 - dataframe['short'] = 0 + dataframe['enter_short'] = 0 dataframe['exit_short'] = 0 dataframe['buy_tag'] = None dataframe['short_tag'] = None @@ -572,8 +568,8 @@ class IStrategy(ABC, HyperStrategyMixin): else: return False - def should_sell(self, trade: Trade, rate: float, date: datetime, enter: bool, - exit: bool, low: float = None, high: float = None, + def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, + sell: bool, low: float = None, high: float = None, force_stoploss: float = 0) -> SellCheckTuple: """ This function evaluates if one of the conditions required to trigger a sell/exit_short @@ -597,7 +593,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_profit = trade.calc_profit_ratio(current_rate) # if enter signal and ignore_roi is set, we don't need to evaluate min_roi. - roi_reached = (not (enter and self.ignore_roi_if_buy_signal) + roi_reached = (not (buy and self.ignore_roi_if_buy_signal) and self.min_roi_reached(trade=trade, current_profit=current_profit, current_time=date)) @@ -610,8 +606,8 @@ class IStrategy(ABC, HyperStrategyMixin): if (self.sell_profit_only and current_profit <= self.sell_profit_offset): # sell_profit_only and profit doesn't reach the offset - ignore sell signal pass - elif self.use_sell_signal and not enter: - if exit: + elif self.use_sell_signal and not buy: + if sell: sell_signal = SellType.SELL_SIGNAL else: trade_type = "exit_short" if trade.is_short else "sell" @@ -759,7 +755,7 @@ class IStrategy(ABC, HyperStrategyMixin): def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: """ Populates indicators for given candle (OHLCV) data (for multiple pairs) - Does not run advise_enter or advise_exit! + Does not run advise_buy or advise_sell! Used by optimize operations only, not during dry / live runs. Using .copy() to get a fresh copy of the dataframe for every strategy run. Has positive effects on memory usage for whatever reason - also when @@ -784,12 +780,7 @@ class IStrategy(ABC, HyperStrategyMixin): else: return self.populate_indicators(dataframe, metadata) - def advise_enter( - self, - dataframe: DataFrame, - metadata: dict, - is_short: bool = False - ) -> DataFrame: + def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy/short signal for the given dataframe This method should not be overridden. @@ -798,27 +789,17 @@ class IStrategy(ABC, HyperStrategyMixin): currently traded pair :return: DataFrame with buy column """ - (type, fun_len) = ( - ("short", self._short_fun_len) - if is_short else - ("buy", self._buy_fun_len) - ) - logger.debug(f"Populating {type} signals for pair {metadata.get('pair')}.") + logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.") - if fun_len == 2: + if self._buy_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) - return self.populate_enter_trend(dataframe) # type: ignore + return self.populate_buy_trend(dataframe) # type: ignore else: - return self.populate_enter_trend(dataframe, metadata) + return self.populate_buy_trend(dataframe, metadata) - def advise_exit( - self, - dataframe: DataFrame, - metadata: dict, - is_short: bool = False - ) -> DataFrame: + def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the sell/exit_short signal for the given dataframe This method should not be overridden. @@ -828,16 +809,26 @@ class IStrategy(ABC, HyperStrategyMixin): :return: DataFrame with sell column """ - (type, fun_len) = ( - ("exit_short", self._exit_short_fun_len) - if is_short else - ("sell", self._sell_fun_len) - ) - - logger.debug(f"Populating {type} signals for pair {metadata.get('pair')}.") - if fun_len == 2: + logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.") + if self._sell_fun_len == 2: warnings.warn("deprecated - check out the Sample strategy to see " "the current function headers!", DeprecationWarning) - return self.populate_exit_trend(dataframe) # type: ignore + return self.populate_sell_trend(dataframe) # type: ignore else: - return self.populate_exit_trend(dataframe, metadata) + return self.populate_sell_trend(dataframe, metadata) + + def leverage(self, pair: str, current_time: datetime, current_rate: float, + proposed_leverage: float, max_leverage: float, + **kwargs) -> float: + """ + Customize leverage for each new trade. This method is not called when edge module is + enabled. + + :param pair: Pair that's currently analyzed + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param proposed_leverage: A leverage proposed by the bot. + :param max_leverage: Max leverage allowed on this pair + :return: A stake size, which is between min_stake and max_stake. + """ + return proposed_leverage diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index e7dbfbac7..9c4d2bf2d 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -1,5 +1,6 @@ import pandas as pd +from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_minutes @@ -83,7 +84,13 @@ def stoploss_from_open( if current_profit == -1: return 1 - stoploss = 1-((1+open_relative_stop)/(1+current_profit)) # TODO-lev: Is this right? + 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)) + 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: diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index 6707ec8d4..c39558108 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -46,7 +46,7 @@ class SampleHyperOpt(IHyperOpt): """ @staticmethod - def indicator_space() -> List[Dimension]: + def buy_indicator_space() -> List[Dimension]: """ Define your Hyperopt space for searching buy strategy parameters. """ @@ -55,11 +55,16 @@ class SampleHyperOpt(IHyperOpt): Integer(15, 45, name='fastd-value'), Integer(20, 50, name='adx-value'), Integer(20, 40, name='rsi-value'), + Integer(75, 90, name='short-mfi-value'), + Integer(55, 85, name='short-fastd-value'), + Integer(50, 80, name='short-adx-value'), + Integer(60, 80, name='short-rsi-value'), Categorical([True, False], name='mfi-enabled'), Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') + Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger'), + ] @staticmethod @@ -71,39 +76,61 @@ class SampleHyperOpt(IHyperOpt): """ Buy strategy Hyperopt will build and use. """ - conditions = [] + long_conditions = [] + short_conditions = [] # GUARDS AND TRENDS if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] < params['mfi-value']) + long_conditions.append(dataframe['mfi'] < params['mfi-value']) + short_conditions.append(dataframe['mfi'] > params['short-mfi-value']) if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] < params['fastd-value']) + long_conditions.append(dataframe['fastd'] < params['fastd-value']) + short_conditions.append(dataframe['fastd'] > params['short-fastd-value']) if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] > params['adx-value']) + long_conditions.append(dataframe['adx'] > params['adx-value']) + short_conditions.append(dataframe['adx'] < params['short-adx-value']) if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] < params['rsi-value']) + long_conditions.append(dataframe['rsi'] < params['rsi-value']) + short_conditions.append(dataframe['rsi'] > params['short-rsi-value']) # TRIGGERS if 'trigger' in params: - if params['trigger'] == 'bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + if params['trigger'] == 'boll': + long_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + short_conditions.append(dataframe['close'] > dataframe['bb_upperband']) if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macd'], dataframe['macdsignal'] + long_conditions.append(qtpylib.crossed_above( + dataframe['macd'], + dataframe['macdsignal'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['macd'], + dataframe['macdsignal'] )) if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['close'], dataframe['sar'] + long_conditions.append(qtpylib.crossed_above( + dataframe['close'], + dataframe['sar'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['close'], + dataframe['sar'] )) # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) + long_conditions.append(dataframe['volume'] > 0) + short_conditions.append(dataframe['volume'] > 0) - if conditions: + if long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, long_conditions), 'buy'] = 1 + if short_conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, short_conditions), + 'enter_short'] = 1 + return dataframe return populate_buy_trend @@ -118,13 +145,19 @@ class SampleHyperOpt(IHyperOpt): Integer(50, 100, name='sell-fastd-value'), Integer(50, 100, name='sell-adx-value'), Integer(60, 100, name='sell-rsi-value'), + Integer(1, 25, name='exit-short-mfi-value'), + Integer(1, 50, name='exit-short-fastd-value'), + Integer(1, 50, name='exit-short-adx-value'), + Integer(1, 40, name='exit-short-rsi-value'), Categorical([True, False], name='sell-mfi-enabled'), Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-rsi-enabled'), - Categorical(['sell-bb_upper', + Categorical(['sell-boll', 'sell-macd_cross_signal', - 'sell-sar_reversal'], name='sell-trigger') + 'sell-sar_reversal'], + name='sell-trigger' + ), ] @staticmethod @@ -136,161 +169,61 @@ class SampleHyperOpt(IHyperOpt): """ Sell strategy Hyperopt will build and use. """ - conditions = [] + exit_long_conditions = [] + exit_short_conditions = [] # GUARDS AND TRENDS if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: - conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_short_conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: - conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_short_conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) if 'sell-adx-enabled' in params and params['sell-adx-enabled']: - conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_short_conditions.append(dataframe['adx'] > params['exit-short-adx-value']) if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: - conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_short_conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) # TRIGGERS if 'sell-trigger' in params: - if params['sell-trigger'] == 'sell-bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) + if params['sell-trigger'] == 'sell-boll': + exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband']) + exit_short_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['sell-trigger'] == 'sell-macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macdsignal'], dataframe['macd'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['macdsignal'], + dataframe['macd'] + )) + exit_short_conditions.append(qtpylib.crossed_below( + dataframe['macdsignal'], + dataframe['macd'] )) if params['sell-trigger'] == 'sell-sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['sar'], dataframe['close'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['sar'], + dataframe['close'] + )) + exit_short_conditions.append(qtpylib.crossed_below( + dataframe['sar'], + dataframe['close'] )) # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) + exit_long_conditions.append(dataframe['volume'] > 0) + exit_short_conditions.append(dataframe['volume'] > 0) - if conditions: + if exit_long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, exit_long_conditions), 'sell'] = 1 - return dataframe - - return populate_sell_trend - - @staticmethod - def short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the short strategy parameters to be used by Hyperopt. - """ - def populate_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Buy strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] > params['mfi-value']) - if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] > params['fastd-value']) - if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] < params['adx-value']) - if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] > params['rsi-value']) - - # TRIGGERS - if 'trigger' in params: - if params['trigger'] == 'bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) - if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macd'], dataframe['macdsignal'] - )) - if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['close'], dataframe['sar'] - )) - - if conditions: + if exit_short_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'short'] = 1 - - return dataframe - - return populate_short_trend - - @staticmethod - def short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching short strategy parameters. - """ - return [ - Integer(75, 90, name='mfi-value'), - Integer(55, 85, name='fastd-value'), - Integer(50, 80, name='adx-value'), - Integer(60, 80, name='rsi-value'), - Categorical([True, False], name='mfi-enabled'), - Categorical([True, False], name='fastd-enabled'), - Categorical([True, False], name='adx-enabled'), - Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_upper', 'macd_cross_signal', 'sar_reversal'], name='trigger') - ] - - @staticmethod - def exit_short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the exit_short strategy parameters to be used by Hyperopt. - """ - def populate_exit_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Exit_short strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'exit-short-mfi-enabled' in params and params['exit-short-mfi-enabled']: - conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) - if 'exit-short-fastd-enabled' in params and params['exit-short-fastd-enabled']: - conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) - if 'exit-short-adx-enabled' in params and params['exit-short-adx-enabled']: - conditions.append(dataframe['adx'] > params['exit-short-adx-value']) - if 'exit-short-rsi-enabled' in params and params['exit-short-rsi-enabled']: - conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) - - # TRIGGERS - if 'exit-short-trigger' in params: - if params['exit-short-trigger'] == 'exit-short-bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) - if params['exit-short-trigger'] == 'exit-short-macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macdsignal'], dataframe['macd'] - )) - if params['exit-short-trigger'] == 'exit-short-sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['sar'], dataframe['close'] - )) - - if conditions: - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, exit_short_conditions), 'exit_short'] = 1 return dataframe - return populate_exit_short_trend - - @staticmethod - def exit_short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching exit short strategy parameters. - """ - return [ - Integer(1, 25, name='exit_short-mfi-value'), - Integer(1, 50, name='exit_short-fastd-value'), - Integer(1, 50, name='exit_short-adx-value'), - Integer(1, 40, name='exit_short-rsi-value'), - Categorical([True, False], name='exit_short-mfi-enabled'), - Categorical([True, False], name='exit_short-fastd-enabled'), - Categorical([True, False], name='exit_short-adx-enabled'), - Categorical([True, False], name='exit_short-rsi-enabled'), - Categorical(['exit_short-bb_lower', - 'exit_short-macd_cross_signal', - 'exit_short-sar_reversal'], name='exit_short-trigger') - ] + return populate_sell_trend diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index cee343bb6..feb617aae 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -70,11 +70,15 @@ class AdvancedSampleHyperOpt(IHyperOpt): Integer(15, 45, name='fastd-value'), Integer(20, 50, name='adx-value'), Integer(20, 40, name='rsi-value'), + Integer(75, 90, name='short-mfi-value'), + Integer(55, 85, name='short-fastd-value'), + Integer(50, 80, name='short-adx-value'), + Integer(60, 80, name='short-rsi-value'), Categorical([True, False], name='mfi-enabled'), Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') + Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger') ] @staticmethod @@ -86,38 +90,60 @@ class AdvancedSampleHyperOpt(IHyperOpt): """ Buy strategy Hyperopt will build and use """ - conditions = [] + long_conditions = [] + short_conditions = [] # GUARDS AND TRENDS if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] < params['mfi-value']) + long_conditions.append(dataframe['mfi'] < params['mfi-value']) + short_conditions.append(dataframe['mfi'] > params['short-mfi-value']) if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] < params['fastd-value']) + long_conditions.append(dataframe['fastd'] < params['fastd-value']) + short_conditions.append(dataframe['fastd'] > params['short-fastd-value']) if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] > params['adx-value']) + long_conditions.append(dataframe['adx'] > params['adx-value']) + short_conditions.append(dataframe['adx'] < params['short-adx-value']) if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] < params['rsi-value']) + long_conditions.append(dataframe['rsi'] < params['rsi-value']) + short_conditions.append(dataframe['rsi'] > params['short-rsi-value']) # TRIGGERS if 'trigger' in params: - if params['trigger'] == 'bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + if params['trigger'] == 'boll': + long_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + short_conditions.append(dataframe['close'] > dataframe['bb_upperband']) if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macd'], dataframe['macdsignal'] + long_conditions.append(qtpylib.crossed_above( + dataframe['macd'], + dataframe['macdsignal'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['macd'], + dataframe['macdsignal'] )) if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['close'], dataframe['sar'] + long_conditions.append(qtpylib.crossed_above( + dataframe['close'], + dataframe['sar'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['close'], + dataframe['sar'] )) # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) + long_conditions.append(dataframe['volume'] > 0) + short_conditions.append(dataframe['volume'] > 0) - if conditions: + if long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, long_conditions), 'buy'] = 1 + if short_conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, short_conditions), + 'enter_short'] = 1 + return dataframe return populate_buy_trend @@ -132,13 +158,18 @@ class AdvancedSampleHyperOpt(IHyperOpt): Integer(50, 100, name='sell-fastd-value'), Integer(50, 100, name='sell-adx-value'), Integer(60, 100, name='sell-rsi-value'), + Integer(1, 25, name='exit_short-mfi-value'), + Integer(1, 50, name='exit_short-fastd-value'), + Integer(1, 50, name='exit_short-adx-value'), + Integer(1, 40, name='exit_short-rsi-value'), Categorical([True, False], name='sell-mfi-enabled'), Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-rsi-enabled'), - Categorical(['sell-bb_upper', + Categorical(['sell-boll', 'sell-macd_cross_signal', - 'sell-sar_reversal'], name='sell-trigger') + 'sell-sar_reversal'], + name='sell-trigger') ] @staticmethod @@ -151,163 +182,63 @@ class AdvancedSampleHyperOpt(IHyperOpt): Sell strategy Hyperopt will build and use """ # print(params) - conditions = [] + exit_long_conditions = [] + exit_short_conditions = [] # GUARDS AND TRENDS if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: - conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_short_conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: - conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_short_conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) if 'sell-adx-enabled' in params and params['sell-adx-enabled']: - conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_short_conditions.append(dataframe['adx'] > params['exit-short-adx-value']) if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: - conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_short_conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) # TRIGGERS if 'sell-trigger' in params: - if params['sell-trigger'] == 'sell-bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) + if params['sell-trigger'] == 'sell-boll': + exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband']) + exit_short_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['sell-trigger'] == 'sell-macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macdsignal'], dataframe['macd'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['macdsignal'], + dataframe['macd'] + )) + exit_long_conditions.append(qtpylib.crossed_below( + dataframe['macdsignal'], + dataframe['macd'] )) if params['sell-trigger'] == 'sell-sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['sar'], dataframe['close'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['sar'], + dataframe['close'] + )) + exit_long_conditions.append(qtpylib.crossed_below( + dataframe['sar'], + dataframe['close'] )) # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) + exit_long_conditions.append(dataframe['volume'] > 0) + exit_short_conditions.append(dataframe['volume'] > 0) - if conditions: + if exit_long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, exit_long_conditions), 'sell'] = 1 - return dataframe - - return populate_sell_trend - - @staticmethod - def short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the short strategy parameters to be used by Hyperopt. - """ - def populate_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Buy strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] > params['mfi-value']) - if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] > params['fastd-value']) - if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] < params['adx-value']) - if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] > params['rsi-value']) - - # TRIGGERS - if 'trigger' in params: - if params['trigger'] == 'bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) - if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macd'], dataframe['macdsignal'] - )) - if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['close'], dataframe['sar'] - )) - - if conditions: + if exit_short_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'short'] = 1 - - return dataframe - - return populate_short_trend - - @staticmethod - def short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching short strategy parameters. - """ - return [ - Integer(75, 90, name='mfi-value'), - Integer(55, 85, name='fastd-value'), - Integer(50, 80, name='adx-value'), - Integer(60, 80, name='rsi-value'), - Categorical([True, False], name='mfi-enabled'), - Categorical([True, False], name='fastd-enabled'), - Categorical([True, False], name='adx-enabled'), - Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_upper', 'macd_cross_signal', 'sar_reversal'], name='trigger') - ] - - @staticmethod - def exit_short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the exit_short strategy parameters to be used by Hyperopt. - """ - def populate_exit_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Exit_short strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'exit-short-mfi-enabled' in params and params['exit-short-mfi-enabled']: - conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) - if 'exit-short-fastd-enabled' in params and params['exit-short-fastd-enabled']: - conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) - if 'exit-short-adx-enabled' in params and params['exit-short-adx-enabled']: - conditions.append(dataframe['adx'] > params['exit-short-adx-value']) - if 'exit-short-rsi-enabled' in params and params['exit-short-rsi-enabled']: - conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) - - # TRIGGERS - if 'exit-short-trigger' in params: - if params['exit-short-trigger'] == 'exit-short-bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) - if params['exit-short-trigger'] == 'exit-short-macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macdsignal'], dataframe['macd'] - )) - if params['exit-short-trigger'] == 'exit-short-sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['sar'], dataframe['close'] - )) - - if conditions: - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, exit_short_conditions), 'exit_short'] = 1 return dataframe - return populate_exit_short_trend - - @staticmethod - def exit_short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching exit short strategy parameters. - """ - return [ - Integer(1, 25, name='exit_short-mfi-value'), - Integer(1, 50, name='exit_short-fastd-value'), - Integer(1, 50, name='exit_short-adx-value'), - Integer(1, 40, name='exit_short-rsi-value'), - Categorical([True, False], name='exit_short-mfi-enabled'), - Categorical([True, False], name='exit_short-fastd-enabled'), - Categorical([True, False], name='exit_short-adx-enabled'), - Categorical([True, False], name='exit_short-rsi-enabled'), - Categorical(['exit_short-bb_lower', - 'exit_short-macd_cross_signal', - 'exit_short-sar_reversal'], name='exit_short-trigger') - ] + return populate_sell_trend @staticmethod def generate_roi_table(params: Dict) -> Dict[int, float]: diff --git a/freqtrade/templates/sample_strategy.py b/freqtrade/templates/sample_strategy.py index 3e73d3134..b2d130059 100644 --- a/freqtrade/templates/sample_strategy.py +++ b/freqtrade/templates/sample_strategy.py @@ -29,7 +29,7 @@ class SampleStrategy(IStrategy): You must keep: - the lib in the section "Do not remove these libs" - - the methods: populate_indicators, populate_buy_trend, populate_sell_trend, populate_short_trend, populate_exit_short_trend + - the methods: populate_indicators, populate_buy_trend, populate_sell_trend You should keep: - timeframe, minimal_roi, stoploss, trailing_* """ @@ -356,6 +356,16 @@ class SampleStrategy(IStrategy): ), 'buy'] = 1 + dataframe.loc[ + ( + # Signal: RSI crosses above 70 + (qtpylib.crossed_above(dataframe['rsi'], self.short_rsi.value)) & + (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle + (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling + (dataframe['volume'] > 0) # Make sure Volume is not 0 + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -374,38 +384,13 @@ class SampleStrategy(IStrategy): (dataframe['volume'] > 0) # Make sure Volume is not 0 ), 'sell'] = 1 - return dataframe - def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the short signal for the given dataframe - :param dataframe: DataFrame populated with indicators - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with short column - """ - dataframe.loc[ - ( - # Signal: RSI crosses above 70 - (qtpylib.crossed_above(dataframe['rsi'], self.short_rsi.value)) & - (dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle - (dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling - (dataframe['volume'] > 0) # Make sure Volume is not 0 - ), - 'short'] = 1 - return dataframe - - def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the exit_short signal for the given dataframe - :param dataframe: DataFrame populated with indicators - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with exit_short column - """ dataframe.loc[ ( # Signal: RSI crosses above 30 (qtpylib.crossed_above(dataframe['rsi'], self.exit_short_rsi.value)) & - (dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle + # Guard: tema below BB middle + (dataframe['tema'] <= dataframe['bb_middleband']) & (dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising (dataframe['volume'] > 0) # Make sure Volume is not 0 ), diff --git a/tests/optimize/hyperopts/default_hyperopt.py b/tests/optimize/hyperopts/default_hyperopt.py index cc8771d1b..df39188e0 100644 --- a/tests/optimize/hyperopts/default_hyperopt.py +++ b/tests/optimize/hyperopts/default_hyperopt.py @@ -54,36 +54,57 @@ class DefaultHyperOpt(IHyperOpt): """ Buy strategy Hyperopt will build and use. """ - conditions = [] + long_conditions = [] + short_conditions = [] # GUARDS AND TRENDS if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] < params['mfi-value']) + long_conditions.append(dataframe['mfi'] < params['mfi-value']) + short_conditions.append(dataframe['mfi'] > params['short-mfi-value']) if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] < params['fastd-value']) + long_conditions.append(dataframe['fastd'] < params['fastd-value']) + short_conditions.append(dataframe['fastd'] > params['short-fastd-value']) if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] > params['adx-value']) + long_conditions.append(dataframe['adx'] > params['adx-value']) + short_conditions.append(dataframe['adx'] < params['short-adx-value']) if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] < params['rsi-value']) + long_conditions.append(dataframe['rsi'] < params['rsi-value']) + short_conditions.append(dataframe['rsi'] > params['short-rsi-value']) # TRIGGERS if 'trigger' in params: - if params['trigger'] == 'bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + if params['trigger'] == 'boll': + long_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) + short_conditions.append(dataframe['close'] > dataframe['bb_upperband']) if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macd'], dataframe['macdsignal'] + long_conditions.append(qtpylib.crossed_above( + dataframe['macd'], + dataframe['macdsignal'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['macd'], + dataframe['macdsignal'] )) if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['close'], dataframe['sar'] + long_conditions.append(qtpylib.crossed_above( + dataframe['close'], + dataframe['sar'] + )) + short_conditions.append(qtpylib.crossed_below( + dataframe['close'], + dataframe['sar'] )) - if conditions: + if long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, long_conditions), 'buy'] = 1 + if short_conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, short_conditions), + 'enter_short'] = 1 + return dataframe return populate_buy_trend @@ -98,71 +119,15 @@ class DefaultHyperOpt(IHyperOpt): Integer(15, 45, name='fastd-value'), Integer(20, 50, name='adx-value'), Integer(20, 40, name='rsi-value'), + Integer(75, 90, name='short-mfi-value'), + Integer(55, 85, name='short-fastd-value'), + Integer(50, 80, name='short-adx-value'), + Integer(60, 80, name='short-rsi-value'), Categorical([True, False], name='mfi-enabled'), Categorical([True, False], name='fastd-enabled'), Categorical([True, False], name='adx-enabled'), Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') - ] - - @staticmethod - def short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the short strategy parameters to be used by Hyperopt. - """ - def populate_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Buy strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'mfi-enabled' in params and params['mfi-enabled']: - conditions.append(dataframe['mfi'] > params['mfi-value']) - if 'fastd-enabled' in params and params['fastd-enabled']: - conditions.append(dataframe['fastd'] > params['fastd-value']) - if 'adx-enabled' in params and params['adx-enabled']: - conditions.append(dataframe['adx'] < params['adx-value']) - if 'rsi-enabled' in params and params['rsi-enabled']: - conditions.append(dataframe['rsi'] > params['rsi-value']) - - # TRIGGERS - if 'trigger' in params: - if params['trigger'] == 'bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) - if params['trigger'] == 'macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macd'], dataframe['macdsignal'] - )) - if params['trigger'] == 'sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['close'], dataframe['sar'] - )) - - if conditions: - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'short'] = 1 - - return dataframe - - return populate_short_trend - - @staticmethod - def short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching short strategy parameters. - """ - return [ - Integer(75, 90, name='mfi-value'), - Integer(55, 85, name='fastd-value'), - Integer(50, 80, name='adx-value'), - Integer(60, 80, name='rsi-value'), - Categorical([True, False], name='mfi-enabled'), - Categorical([True, False], name='fastd-enabled'), - Categorical([True, False], name='adx-enabled'), - Categorical([True, False], name='rsi-enabled'), - Categorical(['bb_upper', 'macd_cross_signal', 'sar_reversal'], name='trigger') + Categorical(['boll', 'macd_cross_signal', 'sar_reversal'], name='trigger') ] @staticmethod @@ -174,83 +139,61 @@ class DefaultHyperOpt(IHyperOpt): """ Sell strategy Hyperopt will build and use. """ - conditions = [] + exit_long_conditions = [] + exit_short_conditions = [] # GUARDS AND TRENDS if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']: - conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_long_conditions.append(dataframe['mfi'] > params['sell-mfi-value']) + exit_short_conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']: - conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_long_conditions.append(dataframe['fastd'] > params['sell-fastd-value']) + exit_short_conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) if 'sell-adx-enabled' in params and params['sell-adx-enabled']: - conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_long_conditions.append(dataframe['adx'] < params['sell-adx-value']) + exit_short_conditions.append(dataframe['adx'] > params['exit-short-adx-value']) if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']: - conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_long_conditions.append(dataframe['rsi'] > params['sell-rsi-value']) + exit_short_conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) # TRIGGERS if 'sell-trigger' in params: - if params['sell-trigger'] == 'sell-bb_upper': - conditions.append(dataframe['close'] > dataframe['bb_upperband']) + if params['sell-trigger'] == 'sell-boll': + exit_long_conditions.append(dataframe['close'] > dataframe['bb_upperband']) + exit_short_conditions.append(dataframe['close'] < dataframe['bb_lowerband']) if params['sell-trigger'] == 'sell-macd_cross_signal': - conditions.append(qtpylib.crossed_above( - dataframe['macdsignal'], dataframe['macd'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['macdsignal'], + dataframe['macd'] + )) + exit_short_conditions.append(qtpylib.crossed_below( + dataframe['macdsignal'], + dataframe['macd'] )) if params['sell-trigger'] == 'sell-sar_reversal': - conditions.append(qtpylib.crossed_above( - dataframe['sar'], dataframe['close'] + exit_long_conditions.append(qtpylib.crossed_above( + dataframe['sar'], + dataframe['close'] + )) + exit_short_conditions.append(qtpylib.crossed_below( + dataframe['sar'], + dataframe['close'] )) - if conditions: + if exit_long_conditions: dataframe.loc[ - reduce(lambda x, y: x & y, conditions), + reduce(lambda x, y: x & y, exit_long_conditions), 'sell'] = 1 + if exit_short_conditions: + dataframe.loc[ + reduce(lambda x, y: x & y, exit_short_conditions), + 'exit-short'] = 1 + return dataframe return populate_sell_trend - @staticmethod - def exit_short_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the exit_short strategy parameters to be used by Hyperopt. - """ - def populate_exit_short_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Exit_short strategy Hyperopt will build and use. - """ - conditions = [] - - # GUARDS AND TRENDS - if 'exit-short-mfi-enabled' in params and params['exit-short-mfi-enabled']: - conditions.append(dataframe['mfi'] < params['exit-short-mfi-value']) - if 'exit-short-fastd-enabled' in params and params['exit-short-fastd-enabled']: - conditions.append(dataframe['fastd'] < params['exit-short-fastd-value']) - if 'exit-short-adx-enabled' in params and params['exit-short-adx-enabled']: - conditions.append(dataframe['adx'] > params['exit-short-adx-value']) - if 'exit-short-rsi-enabled' in params and params['exit-short-rsi-enabled']: - conditions.append(dataframe['rsi'] < params['exit-short-rsi-value']) - - # TRIGGERS - if 'exit-short-trigger' in params: - if params['exit-short-trigger'] == 'exit-short-bb_lower': - conditions.append(dataframe['close'] < dataframe['bb_lowerband']) - if params['exit-short-trigger'] == 'exit-short-macd_cross_signal': - conditions.append(qtpylib.crossed_below( - dataframe['macdsignal'], dataframe['macd'] - )) - if params['exit-short-trigger'] == 'exit-short-sar_reversal': - conditions.append(qtpylib.crossed_below( - dataframe['sar'], dataframe['close'] - )) - - if conditions: - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'exit_short'] = 1 - - return dataframe - - return populate_exit_short_trend - @staticmethod def sell_indicator_space() -> List[Dimension]: """ @@ -261,32 +204,18 @@ class DefaultHyperOpt(IHyperOpt): Integer(50, 100, name='sell-fastd-value'), Integer(50, 100, name='sell-adx-value'), Integer(60, 100, name='sell-rsi-value'), + Integer(1, 25, name='exit-short-mfi-value'), + Integer(1, 50, name='exit-short-fastd-value'), + Integer(1, 50, name='exit-short-adx-value'), + Integer(1, 40, name='exit-short-rsi-value'), Categorical([True, False], name='sell-mfi-enabled'), Categorical([True, False], name='sell-fastd-enabled'), Categorical([True, False], name='sell-adx-enabled'), Categorical([True, False], name='sell-rsi-enabled'), - Categorical(['sell-bb_upper', + Categorical(['sell-boll', 'sell-macd_cross_signal', - 'sell-sar_reversal'], name='sell-trigger') - ] - - @staticmethod - def exit_short_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching exit short strategy parameters. - """ - return [ - Integer(1, 25, name='exit_short-mfi-value'), - Integer(1, 50, name='exit_short-fastd-value'), - Integer(1, 50, name='exit_short-adx-value'), - Integer(1, 40, name='exit_short-rsi-value'), - Categorical([True, False], name='exit_short-mfi-enabled'), - Categorical([True, False], name='exit_short-fastd-enabled'), - Categorical([True, False], name='exit_short-adx-enabled'), - Categorical([True, False], name='exit_short-rsi-enabled'), - Categorical(['exit_short-bb_lower', - 'exit_short-macd_cross_signal', - 'exit_short-sar_reversal'], name='exit_short-trigger') + 'sell-sar_reversal'], + name='sell-trigger') ] def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -304,6 +233,15 @@ class DefaultHyperOpt(IHyperOpt): ), 'buy'] = 1 + dataframe.loc[ + ( + (dataframe['close'] > dataframe['bb_upperband']) & + (dataframe['mfi'] < 84) & + (dataframe['adx'] > 75) & + (dataframe['rsi'] < 79) + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -321,31 +259,6 @@ class DefaultHyperOpt(IHyperOpt): ), 'sell'] = 1 - return dataframe - - def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators. Should be a copy of same method from strategy. - Must align to populate_indicators in this file. - Only used when --spaces does not include short space. - """ - dataframe.loc[ - ( - (dataframe['close'] > dataframe['bb_upperband']) & - (dataframe['mfi'] < 84) & - (dataframe['adx'] > 75) & - (dataframe['rsi'] < 79) - ), - 'buy'] = 1 - - return dataframe - - def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators. Should be a copy of same method from strategy. - Must align to populate_indicators in this file. - Only used when --spaces does not include exit_short space. - """ dataframe.loc[ ( (qtpylib.crossed_below( @@ -353,6 +266,6 @@ class DefaultHyperOpt(IHyperOpt): )) & (dataframe['fastd'] < 46) ), - 'sell'] = 1 + 'exit_short'] = 1 return dataframe diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index 0205369ba..e5c037f3e 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -597,8 +597,8 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) backtesting.required_startup = 0 - backtesting.strategy.advise_enter = lambda a, m: frame - backtesting.strategy.advise_exit = lambda a, m: frame + backtesting.strategy.advise_buy = lambda a, m: frame + backtesting.strategy.advise_sell = lambda a, m: frame backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss caplog.set_level(logging.DEBUG) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index afbfcb1c2..deaaf9f2f 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -290,8 +290,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: assert backtesting.config == default_conf assert backtesting.timeframe == '5m' assert callable(backtesting.strategy.ohlcvdata_to_dataframe) - assert callable(backtesting.strategy.advise_enter) - assert callable(backtesting.strategy.advise_exit) + assert callable(backtesting.strategy.advise_buy) + assert callable(backtesting.strategy.advise_sell) assert isinstance(backtesting.strategy.dp, DataProvider) get_fee.assert_called() assert backtesting.fee == 0.5 @@ -700,8 +700,8 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir): backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_enter = fun # Override - backtesting.strategy.advise_exit = fun # Override + backtesting.strategy.advise_buy = fun # Override + backtesting.strategy.advise_sell = fun # Override result = backtesting.backtest(**backtest_conf) assert result['results'].empty @@ -716,8 +716,8 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir): backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_enter = fun # Override - backtesting.strategy.advise_exit = fun # Override + backtesting.strategy.advise_buy = fun # Override + backtesting.strategy.advise_sell = fun # Override result = backtesting.backtest(**backtest_conf) assert result['results'].empty @@ -731,8 +731,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): backtesting = Backtesting(default_conf) backtesting.required_startup = 0 backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_enter = _trend_alternate # Override - backtesting.strategy.advise_exit = _trend_alternate # Override + backtesting.strategy.advise_buy = _trend_alternate # Override + backtesting.strategy.advise_sell = _trend_alternate # Override result = backtesting.backtest(**backtest_conf) # 200 candles in backtest data # won't buy on first (shifted by 1) @@ -777,8 +777,8 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) - backtesting.strategy.advise_enter = _trend_alternate_hold # Override - backtesting.strategy.advise_exit = _trend_alternate_hold # Override + backtesting.strategy.advise_buy = _trend_alternate_hold # Override + backtesting.strategy.advise_sell = _trend_alternate_hold # Override processed = backtesting.strategy.ohlcvdata_to_dataframe(data) min_date, max_date = get_timerange(processed) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 855a752ac..333cea971 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -366,8 +366,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: # Should be called for historical candle data assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_exit") - assert hasattr(hyperopt.backtesting.strategy, "advise_enter") + assert hasattr(hyperopt.backtesting.strategy, "advise_sell") + assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -451,6 +451,10 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: 'fastd-value': 20, 'mfi-value': 20, 'rsi-value': 20, + 'short-adx-value': 80, + 'short-fastd-value': 80, + 'short-mfi-value': 80, + 'short-rsi-value': 80, 'adx-enabled': True, 'fastd-enabled': True, 'mfi-enabled': True, @@ -476,6 +480,10 @@ def test_sell_strategy_generator(hyperopt, testdatadir) -> None: 'sell-fastd-value': 75, 'sell-mfi-value': 80, 'sell-rsi-value': 20, + 'exit-short-adx-value': 80, + 'exit-short-fastd-value': 25, + 'exit-short-mfi-value': 20, + 'exit-short-rsi-value': 80, 'sell-adx-enabled': True, 'sell-fastd-enabled': True, 'sell-mfi-enabled': True, @@ -534,6 +542,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: 'fastd-value': 35, 'mfi-value': 0, 'rsi-value': 0, + 'short-adx-value': 100, + 'short-fastd-value': 65, + 'short-mfi-value': 100, + 'short-rsi-value': 100, 'adx-enabled': False, 'fastd-enabled': True, 'mfi-enabled': False, @@ -543,6 +555,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: 'sell-fastd-value': 75, 'sell-mfi-value': 0, 'sell-rsi-value': 0, + 'exit-short-adx-value': 100, + 'exit-short-fastd-value': 25, + 'exit-short-mfi-value': 100, + 'exit-short-rsi-value': 100, 'sell-adx-enabled': False, 'sell-fastd-enabled': True, 'sell-mfi-enabled': False, @@ -569,12 +585,16 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: ), 'params_details': {'buy': {'adx-enabled': False, 'adx-value': 0, + 'short-adx-value': 100, 'fastd-enabled': True, 'fastd-value': 35, + 'short-fastd-value': 65, 'mfi-enabled': False, 'mfi-value': 0, + 'short-mfi-value': 100, 'rsi-enabled': False, 'rsi-value': 0, + 'short-rsi-value': 100, 'trigger': 'macd_cross_signal'}, 'roi': {"0": 0.12000000000000001, "20.0": 0.02, @@ -583,12 +603,16 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: 'protection': {}, 'sell': {'sell-adx-enabled': False, 'sell-adx-value': 0, + 'exit-short-adx-value': 100, 'sell-fastd-enabled': True, 'sell-fastd-value': 75, + 'exit-short-fastd-value': 25, 'sell-mfi-enabled': False, 'sell-mfi-value': 0, + 'exit-short-mfi-value': 100, 'sell-rsi-enabled': False, 'sell-rsi-value': 0, + 'exit-short-rsi-value': 100, 'sell-trigger': 'macd_cross_signal'}, 'stoploss': {'stoploss': -0.4}, 'trailing': {'trailing_only_offset_is_reached': False, @@ -825,8 +849,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_exit") - assert hasattr(hyperopt.backtesting.strategy, "advise_enter") + assert hasattr(hyperopt.backtesting.strategy, "advise_sell") + assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -906,8 +930,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: assert dumper.called assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_exit") - assert hasattr(hyperopt.backtesting.strategy, "advise_enter") + assert hasattr(hyperopt.backtesting.strategy, "advise_sell") + assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") @@ -960,8 +984,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: assert dumper.called assert dumper.call_count == 1 assert dumper2.call_count == 1 - assert hasattr(hyperopt.backtesting.strategy, "advise_exit") - assert hasattr(hyperopt.backtesting.strategy, "advise_enter") + assert hasattr(hyperopt.backtesting.strategy, "advise_sell") + assert hasattr(hyperopt.backtesting.strategy, "advise_buy") assert hasattr(hyperopt, "max_open_trades") assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] assert hasattr(hyperopt, "position_stacking") diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 439a99e2f..1517b6fcc 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -264,7 +264,7 @@ def test_api_UvicornServer(mocker): assert thread_mock.call_count == 1 s.cleanup() - assert s.should_sell is True + assert s.should_exit is True def test_api_UvicornServer_run(mocker): diff --git a/tests/strategy/strats/default_strategy.py b/tests/strategy/strats/default_strategy.py index 3e5695a99..be373e0ee 100644 --- a/tests/strategy/strats/default_strategy.py +++ b/tests/strategy/strats/default_strategy.py @@ -130,6 +130,19 @@ class DefaultStrategy(IStrategy): ), 'buy'] = 1 + dataframe.loc[ + ( + (dataframe['rsi'] > 65) & + (dataframe['fastd'] > 65) & + (dataframe['adx'] < 70) & + (dataframe['plus_di'] < 0.5) # TODO-lev: What to do here + ) | + ( + (dataframe['adx'] < 35) & + (dataframe['plus_di'] < 0.5) # TODO-lev: What to do here + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -153,37 +166,7 @@ class DefaultStrategy(IStrategy): (dataframe['minus_di'] > 0.5) ), 'sell'] = 1 - return dataframe - def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the short signal for the given dataframe - :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with short column - """ - dataframe.loc[ - ( - (dataframe['rsi'] > 65) & - (dataframe['fastd'] > 65) & - (dataframe['adx'] < 70) & - (dataframe['plus_di'] < 0.5) # TODO-lev: What to do here - ) | - ( - (dataframe['adx'] < 35) & - (dataframe['plus_di'] < 0.5) # TODO-lev: What to do here - ), - 'short'] = 1 - - return dataframe - - def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the exit_short signal for the given dataframe - :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with exit_short column - """ dataframe.loc[ ( ( @@ -198,4 +181,5 @@ class DefaultStrategy(IStrategy): (dataframe['minus_di'] < 0.5) # TODO-lev: what to do here ), 'exit_short'] = 1 + return dataframe diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 8d428b33d..e45ba03f0 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -60,7 +60,7 @@ class HyperoptableStrategy(IStrategy): 'sell_minusdi': 0.4 } - short_params = { + enter_short_params = { 'short_rsi': 65, } @@ -87,8 +87,8 @@ class HyperoptableStrategy(IStrategy): }) return prot - short_rsi = IntParameter([50, 100], default=70, space='sell') - short_plusdi = RealParameter(low=0, high=1, default=0.5, space='sell') + enter_short_rsi = IntParameter([50, 100], default=70, space='sell') + enter_short_plusdi = RealParameter(low=0, high=1, default=0.5, space='sell') exit_short_rsi = IntParameter(low=0, high=50, default=30, space='buy') exit_short_minusdi = DecimalParameter(low=0, high=1, default=0.4999, decimals=3, space='buy', load=False) @@ -175,6 +175,19 @@ class HyperoptableStrategy(IStrategy): ), 'buy'] = 1 + dataframe.loc[ + ( + (dataframe['rsi'] > self.enter_short_rsi.value) & + (dataframe['fastd'] > 65) & + (dataframe['adx'] < 70) & + (dataframe['plus_di'] < self.enter_short_plusdi.value) + ) | + ( + (dataframe['adx'] < 35) & + (dataframe['plus_di'] < self.enter_short_plusdi.value) + ), + 'enter_short'] = 1 + return dataframe def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -198,37 +211,7 @@ class HyperoptableStrategy(IStrategy): (dataframe['minus_di'] > self.sell_minusdi.value) ), 'sell'] = 1 - return dataframe - def populate_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the short signal for the given dataframe - :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with short column - """ - dataframe.loc[ - ( - (dataframe['rsi'] > self.short_rsi.value) & - (dataframe['fastd'] > 65) & - (dataframe['adx'] < 70) & - (dataframe['plus_di'] < self.short_plusdi.value) - ) | - ( - (dataframe['adx'] < 35) & - (dataframe['plus_di'] < self.short_plusdi.value) - ), - 'short'] = 1 - - return dataframe - - def populate_exit_short_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Based on TA indicators, populates the exit_short signal for the given dataframe - :param dataframe: DataFrame - :param metadata: Additional information, like the currently traded pair - :return: DataFrame with exit_short column - """ dataframe.loc[ ( ( @@ -243,4 +226,5 @@ class HyperoptableStrategy(IStrategy): (dataframe['minus_di'] < self.exit_short_minusdi.value) ), 'exit_short'] = 1 + return dataframe diff --git a/tests/strategy/strats/legacy_strategy.py b/tests/strategy/strats/legacy_strategy.py index a5531b42f..20f24d6a3 100644 --- a/tests/strategy/strats/legacy_strategy.py +++ b/tests/strategy/strats/legacy_strategy.py @@ -84,35 +84,5 @@ class TestStrategyLegacy(IStrategy): (dataframe['volume'] > 0) ), 'sell'] = 1 - return dataframe - - def populate_short_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 30) & - (dataframe['tema'] > dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'buy'] = 1 return dataframe - - def populate_exit_short_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 70) & - (dataframe['tema'] < dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'sell'] = 1 - return dataframe diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 420cf8f46..42b1cc0a0 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -14,8 +14,6 @@ def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'populate_indicators') assert hasattr(DefaultStrategy, 'populate_buy_trend') assert hasattr(DefaultStrategy, 'populate_sell_trend') - assert hasattr(DefaultStrategy, 'populate_short_trend') - assert hasattr(DefaultStrategy, 'populate_exit_short_trend') def test_default_strategy(result, fee): @@ -29,10 +27,6 @@ def test_default_strategy(result, fee): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame - # TODO-lev: I think these two should be commented out in the strategy by default - # TODO-lev: so they can be tested, but the tests can't really remain - assert type(strategy.populate_short_trend(indicators, metadata)) is DataFrame - assert type(strategy.populate_exit_short_trend(indicators, metadata)) is DataFrame trade = Trade( open_rate=19_000, @@ -43,28 +37,11 @@ def test_default_strategy(result, fee): assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', - is_short=False, current_time=datetime.utcnow()) is True - + current_time=datetime.utcnow()) is True assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, rate=20000, time_in_force='gtc', sell_reason='roi', - is_short=False, current_time=datetime.utcnow()) is True + current_time=datetime.utcnow()) is True # TODO-lev: Test for shorts? assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(), current_rate=20_000, current_profit=0.05) == strategy.stoploss - - short_trade = Trade( - open_rate=21_000, - amount=0.1, - pair='ETH/BTC', - fee_open=fee.return_value - ) - - assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, - rate=20000, time_in_force='gtc', - is_short=True, current_time=datetime.utcnow()) is True - - assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=short_trade, order_type='limit', - amount=0.1, rate=20000, time_in_force='gtc', - sell_reason='roi', is_short=True, - current_time=datetime.utcnow()) is True diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 1e47575dc..7b7354bda 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -482,20 +482,20 @@ def test_custom_sell(default_conf, fee, caplog) -> None: def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) - enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x) - exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x) + buy_mock = MagicMock(side_effect=lambda x, meta: x) + sell_mock = MagicMock(side_effect=lambda x, meta: x) mocker.patch.multiple( 'freqtrade.strategy.interface.IStrategy', advise_indicators=ind_mock, - advise_enter=enter_mock, - advise_exit=exit_mock, + advise_buy=buy_mock, + advise_sell=sell_mock, ) strategy = DefaultStrategy({}) strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) assert ind_mock.call_count == 1 - assert enter_mock.call_count == 2 - assert enter_mock.call_count == 2 + assert buy_mock.call_count == 1 + assert buy_mock.call_count == 1 assert log_has('TA Analysis Launched', caplog) assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) @@ -504,8 +504,8 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 2 - assert enter_mock.call_count == 4 - assert enter_mock.call_count == 4 + assert buy_mock.call_count == 2 + assert buy_mock.call_count == 2 assert log_has('TA Analysis Launched', caplog) assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) @@ -513,13 +513,13 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None: caplog.set_level(logging.DEBUG) ind_mock = MagicMock(side_effect=lambda x, meta: x) - enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x) - exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x) + buy_mock = MagicMock(side_effect=lambda x, meta: x) + sell_mock = MagicMock(side_effect=lambda x, meta: x) mocker.patch.multiple( 'freqtrade.strategy.interface.IStrategy', advise_indicators=ind_mock, - advise_enter=enter_mock, - advise_exit=exit_mock, + advise_buy=buy_mock, + advise_sell=sell_mock, ) strategy = DefaultStrategy({}) @@ -532,8 +532,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> assert 'close' in ret.columns assert isinstance(ret, DataFrame) assert ind_mock.call_count == 1 - assert enter_mock.call_count == 2 # Once for buy, once for short - assert enter_mock.call_count == 2 + assert buy_mock.call_count == 1 + assert buy_mock.call_count == 1 assert log_has('TA Analysis Launched', caplog) assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) caplog.clear() @@ -541,8 +541,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'}) # No analysis happens as process_only_new_candles is true assert ind_mock.call_count == 1 - assert enter_mock.call_count == 2 - assert enter_mock.call_count == 2 + assert buy_mock.call_count == 1 + assert buy_mock.call_count == 1 # only skipped analyze adds buy and sell columns, otherwise it's all mocked assert 'buy' in ret.columns assert 'sell' in ret.columns diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 2cf77b172..8f8a71097 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -117,16 +117,12 @@ def test_strategy(result, default_conf): df_indicators = strategy.advise_indicators(result, metadata=metadata) assert 'adx' in df_indicators - dataframe = strategy.advise_enter(df_indicators, metadata=metadata, is_short=False) + dataframe = strategy.advise_buy(df_indicators, metadata=metadata) assert 'buy' in dataframe.columns + assert 'enter_short' in dataframe.columns - dataframe = strategy.advise_exit(df_indicators, metadata=metadata, is_short=False) + dataframe = strategy.advise_sell(df_indicators, metadata=metadata) assert 'sell' in dataframe.columns - - dataframe = strategy.advise_enter(df_indicators, metadata=metadata, is_short=True) - assert 'short' in dataframe.columns - - dataframe = strategy.advise_exit(df_indicators, metadata=metadata, is_short=True) assert 'exit_short' in dataframe.columns @@ -352,7 +348,7 @@ def test_deprecate_populate_indicators(result, default_conf): with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") - strategy.advise_enter(indicators, {'pair': 'ETH/BTC'}, is_short=False) # TODO-lev + strategy.advise_buy(indicators, {'pair': 'ETH/BTC'}) assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - check out the Sample strategy to see the current function headers!" \ @@ -361,7 +357,7 @@ def test_deprecate_populate_indicators(result, default_conf): with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") - strategy.advise_exit(indicators, {'pair': 'ETH_BTC'}, is_short=False) # TODO-lev + strategy.advise_sell(indicators, {'pair': 'ETH_BTC'}) assert len(w) == 1 assert issubclass(w[-1].category, DeprecationWarning) assert "deprecated - check out the Sample strategy to see the current function headers!" \ @@ -381,8 +377,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert strategy._populate_fun_len == 2 assert strategy._buy_fun_len == 2 assert strategy._sell_fun_len == 2 - # assert strategy._short_fun_len == 2 - # assert strategy._exit_short_fun_len == 2 assert strategy.INTERFACE_VERSION == 1 assert strategy.timeframe == '5m' assert strategy.ticker_interval == '5m' @@ -391,22 +385,14 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert isinstance(indicator_df, DataFrame) assert 'adx' in indicator_df.columns - buydf = strategy.advise_enter(result, metadata=metadata, is_short=False) + buydf = strategy.advise_buy(result, metadata=metadata) assert isinstance(buydf, DataFrame) assert 'buy' in buydf.columns - selldf = strategy.advise_exit(result, metadata=metadata, is_short=False) + selldf = strategy.advise_sell(result, metadata=metadata) assert isinstance(selldf, DataFrame) assert 'sell' in selldf - # shortdf = strategy.advise_enter(result, metadata=metadata, is_short=True) - # assert isinstance(shortdf, DataFrame) - # assert 'short' in shortdf.columns - - # exit_shortdf = strategy.advise_exit(result, metadata=metadata, is_short=True) - # assert isinstance(exit_shortdf, DataFrame) - # assert 'exit_short' in exit_shortdf - assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", caplog) @@ -420,26 +406,18 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf): assert strategy._populate_fun_len == 3 assert strategy._buy_fun_len == 3 assert strategy._sell_fun_len == 3 - assert strategy._short_fun_len == 3 - assert strategy._exit_short_fun_len == 3 assert strategy.INTERFACE_VERSION == 2 indicator_df = strategy.advise_indicators(result, metadata=metadata) assert isinstance(indicator_df, DataFrame) assert 'adx' in indicator_df.columns - buydf = strategy.advise_enter(result, metadata=metadata, is_short=False) - assert isinstance(buydf, DataFrame) - assert 'buy' in buydf.columns + enterdf = strategy.advise_buy(result, metadata=metadata) + assert isinstance(enterdf, DataFrame) + assert 'buy' in enterdf.columns + assert 'enter_short' in enterdf.columns - selldf = strategy.advise_exit(result, metadata=metadata, is_short=False) - assert isinstance(selldf, DataFrame) - assert 'sell' in selldf - - shortdf = strategy.advise_enter(result, metadata=metadata, is_short=True) - assert isinstance(shortdf, DataFrame) - assert 'short' in shortdf.columns - - exit_shortdf = strategy.advise_exit(result, metadata=metadata, is_short=True) - assert isinstance(exit_shortdf, DataFrame) - assert 'exit_short' in exit_shortdf + exitdf = strategy.advise_sell(result, metadata=metadata) + assert isinstance(exitdf, DataFrame) + assert 'sell' in exitdf + assert 'exit_short' in exitdf.columns