Added short and exit_short to strategy
This commit is contained in:
		| @@ -167,8 +167,15 @@ class Edge: | |||||||
|             pair_data = pair_data.sort_values(by=['date']) |             pair_data = pair_data.sort_values(by=['date']) | ||||||
|             pair_data = pair_data.reset_index(drop=True) |             pair_data = pair_data.reset_index(drop=True) | ||||||
|  |  | ||||||
|             df_analyzed = self.strategy.advise_sell( |             df_analyzed = self.strategy.advise_exit( | ||||||
|                 self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy() |                 dataframe=self.strategy.advise_enter( | ||||||
|  |                     dataframe=pair_data, | ||||||
|  |                     metadata={'pair': pair}, | ||||||
|  |                     is_short=False | ||||||
|  |                 ), | ||||||
|  |                 metadata={'pair': pair}, | ||||||
|  |                 is_short=False | ||||||
|  |             )[headers].copy() | ||||||
|  |  | ||||||
|             trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range) |             trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ class SignalType(Enum): | |||||||
|     """ |     """ | ||||||
|     BUY = "buy" |     BUY = "buy" | ||||||
|     SELL = "sell" |     SELL = "sell" | ||||||
|  |     SHORT = "short" | ||||||
|  |     EXIT_SHORT = "exit_short" | ||||||
|  |  | ||||||
|  |  | ||||||
| class SignalTagType(Enum): | class SignalTagType(Enum): | ||||||
| @@ -14,3 +16,4 @@ class SignalTagType(Enum): | |||||||
|     Enum for signal columns |     Enum for signal columns | ||||||
|     """ |     """ | ||||||
|     BUY_TAG = "buy_tag" |     BUY_TAG = "buy_tag" | ||||||
|  |     SELL_TAG = "sell_tag" | ||||||
|   | |||||||
| @@ -231,8 +231,8 @@ class Backtesting: | |||||||
|                 if has_buy_tag: |                 if has_buy_tag: | ||||||
|                     pair_data.loc[:, 'buy_tag'] = None  # cleanup if buy_tag is exist |                     pair_data.loc[:, 'buy_tag'] = None  # cleanup if buy_tag is exist | ||||||
|  |  | ||||||
|             df_analyzed = self.strategy.advise_sell( |             df_analyzed = self.strategy.advise_exit( | ||||||
|                 self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() |                 self.strategy.advise_enter(pair_data, {'pair': pair}), {'pair': pair}).copy() | ||||||
|             # Trim startup period from analyzed dataframe |             # Trim startup period from analyzed dataframe | ||||||
|             df_analyzed = trim_dataframe(df_analyzed, self.timerange, |             df_analyzed = trim_dataframe(df_analyzed, self.timerange, | ||||||
|                                          startup_candles=self.required_startup) |                                          startup_candles=self.required_startup) | ||||||
|   | |||||||
| @@ -110,7 +110,7 @@ class Hyperopt: | |||||||
|                 self.backtesting.strategy.advise_indicators = (  # type: ignore |                 self.backtesting.strategy.advise_indicators = (  # type: ignore | ||||||
|                     self.custom_hyperopt.populate_indicators)  # type: ignore |                     self.custom_hyperopt.populate_indicators)  # type: ignore | ||||||
|             if hasattr(self.custom_hyperopt, 'populate_buy_trend'): |             if hasattr(self.custom_hyperopt, 'populate_buy_trend'): | ||||||
|                 self.backtesting.strategy.advise_buy = (  # type: ignore |                 self.backtesting.strategy.advise_enter = (  # type: ignore | ||||||
|                     self.custom_hyperopt.populate_buy_trend)  # type: ignore |                     self.custom_hyperopt.populate_buy_trend)  # type: ignore | ||||||
|             if hasattr(self.custom_hyperopt, 'populate_sell_trend'): |             if hasattr(self.custom_hyperopt, 'populate_sell_trend'): | ||||||
|                 self.backtesting.strategy.advise_sell = (  # type: ignore |                 self.backtesting.strategy.advise_sell = (  # type: ignore | ||||||
| @@ -283,12 +283,13 @@ class Hyperopt: | |||||||
|         params_dict = self._get_params_dict(self.dimensions, raw_params) |         params_dict = self._get_params_dict(self.dimensions, raw_params) | ||||||
|  |  | ||||||
|         # Apply parameters |         # 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'): |         if HyperoptTools.has_space(self.config, 'buy'): | ||||||
|             self.backtesting.strategy.advise_buy = (  # type: ignore |             self.backtesting.strategy.advise_enter = (  # type: ignore | ||||||
|                 self.custom_hyperopt.buy_strategy_generator(params_dict)) |                 self.custom_hyperopt.buy_strategy_generator(params_dict)) | ||||||
|  |  | ||||||
|         if HyperoptTools.has_space(self.config, 'sell'): |         if HyperoptTools.has_space(self.config, 'sell'): | ||||||
|             self.backtesting.strategy.advise_sell = (  # type: ignore |             self.backtesting.strategy.advise_exit = (  # type: ignore | ||||||
|                 self.custom_hyperopt.sell_strategy_generator(params_dict)) |                 self.custom_hyperopt.sell_strategy_generator(params_dict)) | ||||||
|  |  | ||||||
|         if HyperoptTools.has_space(self.config, 'protection'): |         if HyperoptTools.has_space(self.config, 'protection'): | ||||||
|   | |||||||
| @@ -51,6 +51,7 @@ class HyperOptResolver(IResolver): | |||||||
|         if not hasattr(hyperopt, 'populate_sell_trend'): |         if not hasattr(hyperopt, 'populate_sell_trend'): | ||||||
|             logger.info("Hyperopt class does not provide populate_sell_trend() method. " |             logger.info("Hyperopt class does not provide populate_sell_trend() method. " | ||||||
|                         "Using populate_sell_trend from the strategy.") |                         "Using populate_sell_trend from the strategy.") | ||||||
|  |         # TODO-lev: Short equivelents? | ||||||
|         return hyperopt |         return hyperopt | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -202,9 +202,14 @@ class StrategyResolver(IResolver): | |||||||
|             strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) |             strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) | ||||||
|             strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) |             strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) | ||||||
|             strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_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, |             if any(x == 2 for x in [strategy._populate_fun_len, | ||||||
|                                     strategy._buy_fun_len, |                                     strategy._buy_fun_len, | ||||||
|                                     strategy._sell_fun_len]): |                                     strategy._sell_fun_len, | ||||||
|  |                                     strategy._short_fun_len, | ||||||
|  |                                     strategy._exit_short_fun_len]): | ||||||
|                 strategy.INTERFACE_VERSION = 1 |                 strategy.INTERFACE_VERSION = 1 | ||||||
|  |  | ||||||
|             return strategy |             return strategy | ||||||
|   | |||||||
| @@ -44,5 +44,5 @@ class UvicornServer(uvicorn.Server): | |||||||
|             time.sleep(1e-3) |             time.sleep(1e-3) | ||||||
|  |  | ||||||
|     def cleanup(self): |     def cleanup(self): | ||||||
|         self.should_exit = True |         self.should_sell = True | ||||||
|         self.thread.join() |         self.thread.join() | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ from freqtrade.exceptions import OperationalException | |||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  | # TODO-lev: This file | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseParameter(ABC): | class BaseParameter(ABC): | ||||||
|     """ |     """ | ||||||
|   | |||||||
| @@ -62,6 +62,8 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     _populate_fun_len: int = 0 |     _populate_fun_len: int = 0 | ||||||
|     _buy_fun_len: int = 0 |     _buy_fun_len: int = 0 | ||||||
|     _sell_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 = {} |     _ft_params_from_file: Dict = {} | ||||||
|     # associated minimal roi |     # associated minimal roi | ||||||
|     minimal_roi: Dict |     minimal_roi: Dict | ||||||
| @@ -135,7 +137,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Populate indicators that will be used in the Buy and Sell strategy |         Populate indicators that will be used in the Buy, Sell, Short, Exit_short strategy | ||||||
|         :param dataframe: DataFrame with data from the exchange |         :param dataframe: DataFrame with data from the exchange | ||||||
|         :param metadata: Additional information, like the currently traded pair |         :param metadata: Additional information, like the currently traded pair | ||||||
|         :return: a Dataframe with all mandatory indicators for the strategies |         :return: a Dataframe with all mandatory indicators for the strategies | ||||||
| @@ -143,7 +145,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         return dataframe |         return dataframe | ||||||
|  |  | ||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def populate_enter_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Based on TA indicators, populates the buy signal for the given dataframe |         Based on TA indicators, populates the buy signal for the given dataframe | ||||||
|         :param dataframe: DataFrame |         :param dataframe: DataFrame | ||||||
| @@ -153,7 +155,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         return dataframe |         return dataframe | ||||||
|  |  | ||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Based on TA indicators, populates the sell signal for the given dataframe |         Based on TA indicators, populates the sell signal for the given dataframe | ||||||
|         :param dataframe: DataFrame |         :param dataframe: DataFrame | ||||||
| @@ -164,9 +166,9 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|  |  | ||||||
|     def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: |     def check_buy_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: | ||||||
|         """ |         """ | ||||||
|         Check buy timeout function callback. |         Check enter timeout function callback. | ||||||
|         This method can be used to override the buy-timeout. |         This method can be used to override the enter-timeout. | ||||||
|         It is called whenever a limit buy order has been created, |         It is called whenever a limit buy/short order has been created, | ||||||
|         and is not yet fully filled. |         and is not yet fully filled. | ||||||
|         Configuration options in `unfilledtimeout` will be verified before this, |         Configuration options in `unfilledtimeout` will be verified before this, | ||||||
|         so ensure to set these timeouts high enough. |         so ensure to set these timeouts high enough. | ||||||
| @@ -176,16 +178,16 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         :param trade: trade object. |         :param trade: trade object. | ||||||
|         :param order: Order dictionary as returned from CCXT. |         :param order: Order dictionary as returned from CCXT. | ||||||
|         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. |         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. | ||||||
|         :return bool: When True is returned, then the buy-order is cancelled. |         :return bool: When True is returned, then the buy/short-order is cancelled. | ||||||
|         """ |         """ | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|     def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: |     def check_sell_timeout(self, pair: str, trade: Trade, order: dict, **kwargs) -> bool: | ||||||
|         """ |         """ | ||||||
|         Check sell timeout function callback. |         Check exit timeout function callback. | ||||||
|         This method can be used to override the sell-timeout. |         This method can be used to override the exit-timeout. | ||||||
|         It is called whenever a limit sell order has been created, |         It is called whenever a (long) limit sell order or (short) limit buy | ||||||
|         and is not yet fully filled. |         has been created, and is not yet fully filled. | ||||||
|         Configuration options in `unfilledtimeout` will be verified before this, |         Configuration options in `unfilledtimeout` will be verified before this, | ||||||
|         so ensure to set these timeouts high enough. |         so ensure to set these timeouts high enough. | ||||||
|  |  | ||||||
| @@ -194,7 +196,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         :param trade: trade object. |         :param trade: trade object. | ||||||
|         :param order: Order dictionary as returned from CCXT. |         :param order: Order dictionary as returned from CCXT. | ||||||
|         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. |         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. | ||||||
|         :return bool: When True is returned, then the sell-order is cancelled. |         :return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled. | ||||||
|         """ |         """ | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
| @@ -210,7 +212,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, |     def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, | ||||||
|                             time_in_force: str, current_time: datetime, **kwargs) -> bool: |                             time_in_force: str, current_time: datetime, **kwargs) -> bool: | ||||||
|         """ |         """ | ||||||
|         Called right before placing a buy order. |         Called right before placing a buy/short order. | ||||||
|         Timing for this function is critical, so avoid doing heavy computations or |         Timing for this function is critical, so avoid doing heavy computations or | ||||||
|         network requests in this method. |         network requests in this method. | ||||||
|  |  | ||||||
| @@ -218,7 +220,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|  |  | ||||||
|         When not implemented by a strategy, returns True (always confirming). |         When not implemented by a strategy, returns True (always confirming). | ||||||
|  |  | ||||||
|         :param pair: Pair that's about to be bought. |         :param pair: Pair that's about to be bought/shorted. | ||||||
|         :param order_type: Order type (as configured in order_types). usually limit or market. |         :param order_type: Order type (as configured in order_types). usually limit or market. | ||||||
|         :param amount: Amount in target (quote) currency that's going to be traded. |         :param amount: Amount in target (quote) currency that's going to be traded. | ||||||
|         :param rate: Rate that's going to be used when using limit orders |         :param rate: Rate that's going to be used when using limit orders | ||||||
| @@ -234,7 +236,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|                            rate: float, time_in_force: str, sell_reason: str, |                            rate: float, time_in_force: str, sell_reason: str, | ||||||
|                            current_time: datetime, **kwargs) -> bool: |                            current_time: datetime, **kwargs) -> bool: | ||||||
|         """ |         """ | ||||||
|         Called right before placing a regular sell order. |         Called right before placing a regular sell/exit_short order. | ||||||
|         Timing for this function is critical, so avoid doing heavy computations or |         Timing for this function is critical, so avoid doing heavy computations or | ||||||
|         network requests in this method. |         network requests in this method. | ||||||
|  |  | ||||||
| @@ -242,18 +244,18 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|  |  | ||||||
|         When not implemented by a strategy, returns True (always confirming). |         When not implemented by a strategy, returns True (always confirming). | ||||||
|  |  | ||||||
|         :param pair: Pair that's about to be sold. |         :param pair: Pair for trade that's about to be exited. | ||||||
|         :param trade: trade object. |         :param trade: trade object. | ||||||
|         :param order_type: Order type (as configured in order_types). usually limit or market. |         :param order_type: Order type (as configured in order_types). usually limit or market. | ||||||
|         :param amount: Amount in quote currency. |         :param amount: Amount in quote currency. | ||||||
|         :param rate: Rate that's going to be used when using limit orders |         :param rate: Rate that's going to be used when using limit orders | ||||||
|         :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). |         :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). | ||||||
|         :param sell_reason: Sell reason. |         :param sell_reason: Exit reason. | ||||||
|             Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', |             Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', | ||||||
|                            'sell_signal', 'force_sell', 'emergency_sell'] |                            'sell_signal', 'force_sell', 'emergency_sell'] | ||||||
|         :param current_time: datetime object, containing the current datetime |         :param current_time: datetime object, containing the current datetime | ||||||
|         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. |         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. | ||||||
|         :return bool: When True is returned, then the sell-order is placed on the exchange. |         :return bool: When True, then the sell-order/exit_short-order is placed on the exchange. | ||||||
|             False aborts the process |             False aborts the process | ||||||
|         """ |         """ | ||||||
|         return True |         return True | ||||||
| @@ -283,15 +285,15 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, |     def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, | ||||||
|                     current_profit: float, **kwargs) -> Optional[Union[str, bool]]: |                     current_profit: float, **kwargs) -> Optional[Union[str, bool]]: | ||||||
|         """ |         """ | ||||||
|         Custom sell signal logic indicating that specified position should be sold. Returning a |         Custom exit signal logic indicating that specified position should be sold. Returning a | ||||||
|         string or True from this method is equal to setting sell signal on a candle at specified |         string or True from this method is equal to setting exit signal on a candle at specified | ||||||
|         time. This method is not called when sell signal is set. |         time. This method is not called when exit signal is set. | ||||||
|  |  | ||||||
|         This method should be overridden to create sell signals that depend on trade parameters. For |         This method should be overridden to create exit signals that depend on trade parameters. For | ||||||
|         example you could implement a sell relative to the candle when the trade was opened, |         example you could implement an exit relative to the candle when the trade was opened, | ||||||
|         or a custom 1:2 risk-reward ROI. |         or a custom 1:2 risk-reward ROI. | ||||||
|  |  | ||||||
|         Custom sell reason max length is 64. Exceeding characters will be removed. |         Custom exit reason max length is 64. Exceeding characters will be removed. | ||||||
|  |  | ||||||
|         :param pair: Pair that's currently analyzed |         :param pair: Pair that's currently analyzed | ||||||
|         :param trade: trade object. |         :param trade: trade object. | ||||||
| @@ -299,7 +301,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         :param current_rate: Rate, calculated based on pricing settings in ask_strategy. |         :param current_rate: Rate, calculated based on pricing settings in ask_strategy. | ||||||
|         :param current_profit: Current profit (as ratio), calculated based on current_rate. |         :param current_profit: Current profit (as ratio), calculated based on current_rate. | ||||||
|         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. |         :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. | ||||||
|         :return: To execute sell, return a string with custom sell reason or True. Otherwise return |         :return: To execute exit, return a string with custom sell reason or True. Otherwise return | ||||||
|         None or False. |         None or False. | ||||||
|         """ |         """ | ||||||
|         return None |         return None | ||||||
| @@ -371,7 +373,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         Checks if a pair is currently locked |         Checks if a pair is currently locked | ||||||
|         The 2nd, optional parameter ensures that locks are applied until the new candle arrives, |         The 2nd, optional parameter ensures that locks are applied until the new candle arrives, | ||||||
|         and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap |         and not stop at 14:00:00 - while the next candle arrives at 14:00:02 leaving a gap | ||||||
|         of 2 seconds for a buy to happen on an old signal. |         of 2 seconds for a buy/short to happen on an old signal. | ||||||
|         :param pair: "Pair to check" |         :param pair: "Pair to check" | ||||||
|         :param candle_date: Date of the last candle. Optional, defaults to current date |         :param candle_date: Date of the last candle. Optional, defaults to current date | ||||||
|         :returns: locking state of the pair in question. |         :returns: locking state of the pair in question. | ||||||
| @@ -387,15 +389,17 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Parses the given candle (OHLCV) data and returns a populated DataFrame |         Parses the given candle (OHLCV) data and returns a populated DataFrame | ||||||
|         add several TA indicators and buy signal to it |         add several TA indicators and buy/short signal to it | ||||||
|         :param dataframe: Dataframe containing data from exchange |         :param dataframe: Dataframe containing data from exchange | ||||||
|         :param metadata: Metadata dictionary with additional data (e.g. 'pair') |         :param metadata: Metadata dictionary with additional data (e.g. 'pair') | ||||||
|         :return: DataFrame of candle (OHLCV) data with indicator data and signals added |         :return: DataFrame of candle (OHLCV) data with indicator data and signals added | ||||||
|         """ |         """ | ||||||
|         logger.debug("TA Analysis Launched") |         logger.debug("TA Analysis Launched") | ||||||
|         dataframe = self.advise_indicators(dataframe, metadata) |         dataframe = self.advise_indicators(dataframe, metadata) | ||||||
|         dataframe = self.advise_buy(dataframe, metadata) |         dataframe = self.advise_enter(dataframe, metadata, is_short=False) | ||||||
|         dataframe = self.advise_sell(dataframe, metadata) |         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) | ||||||
|         return dataframe |         return dataframe | ||||||
|  |  | ||||||
|     def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
| @@ -422,7 +426,10 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|             logger.debug("Skipping TA Analysis for already analyzed candle") |             logger.debug("Skipping TA Analysis for already analyzed candle") | ||||||
|             dataframe['buy'] = 0 |             dataframe['buy'] = 0 | ||||||
|             dataframe['sell'] = 0 |             dataframe['sell'] = 0 | ||||||
|  |             dataframe['short'] = 0 | ||||||
|  |             dataframe['exit_short'] = 0 | ||||||
|             dataframe['buy_tag'] = None |             dataframe['buy_tag'] = None | ||||||
|  |             dataframe['short_tag'] = None | ||||||
|  |  | ||||||
|         # Other Defs in strategy that want to be called every loop here |         # Other Defs in strategy that want to be called every loop here | ||||||
|         # twitter_sell = self.watch_twitter_feed(dataframe, metadata) |         # twitter_sell = self.watch_twitter_feed(dataframe, metadata) | ||||||
| @@ -482,6 +489,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         if dataframe is None: |         if dataframe is None: | ||||||
|             message = "No dataframe returned (return statement missing?)." |             message = "No dataframe returned (return statement missing?)." | ||||||
|         elif 'buy' not in dataframe: |         elif 'buy' not in dataframe: | ||||||
|  |             # TODO-lev: Something? | ||||||
|             message = "Buy column not set." |             message = "Buy column not set." | ||||||
|         elif df_len != len(dataframe): |         elif df_len != len(dataframe): | ||||||
|             message = message_template.format("length") |             message = message_template.format("length") | ||||||
| @@ -499,15 +507,18 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         self, |         self, | ||||||
|         pair: str, |         pair: str, | ||||||
|         timeframe: str, |         timeframe: str, | ||||||
|         dataframe: DataFrame |         dataframe: DataFrame, | ||||||
|  |         is_short: bool = False | ||||||
|     ) -> Tuple[bool, bool, Optional[str]]: |     ) -> Tuple[bool, bool, Optional[str]]: | ||||||
|         """ |         """ | ||||||
|         Calculates current signal based based on the buy / sell columns of the dataframe. |         Calculates current signal based based on the buy/short or sell/exit_short | ||||||
|         Used by Bot to get the signal to buy or sell |         columns of the dataframe. | ||||||
|  |         Used by Bot to get the signal to buy, sell, short, or exit_short | ||||||
|         :param pair: pair in format ANT/BTC |         :param pair: pair in format ANT/BTC | ||||||
|         :param timeframe: timeframe to use |         :param timeframe: timeframe to use | ||||||
|         :param dataframe: Analyzed dataframe to get signal from. |         :param dataframe: Analyzed dataframe to get signal from. | ||||||
|         :return: (Buy, Sell) A bool-tuple indicating buy/sell signal |         :return: (Buy, Sell)/(Short, Exit_short) A bool-tuple indicating | ||||||
|  |         (buy/sell)/(short/exit_short) signal | ||||||
|         """ |         """ | ||||||
|         if not isinstance(dataframe, DataFrame) or dataframe.empty: |         if not isinstance(dataframe, DataFrame) or dataframe.empty: | ||||||
|             logger.warning(f'Empty candle (OHLCV) data for pair {pair}') |             logger.warning(f'Empty candle (OHLCV) data for pair {pair}') | ||||||
| @@ -528,42 +539,49 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|             ) |             ) | ||||||
|             return False, False, None |             return False, False, None | ||||||
|  |  | ||||||
|         buy = latest[SignalType.BUY.value] == 1 |         (enter_type, enter_tag) = ( | ||||||
|  |             (SignalType.SHORT, SignalTagType.SHORT_TAG) | ||||||
|  |             if is_short else | ||||||
|  |             (SignalType.BUY, SignalTagType.BUY_TAG) | ||||||
|  |         ) | ||||||
|  |         exit_type = SignalType.EXIT_SHORT if is_short else SignalType.SELL | ||||||
|  |  | ||||||
|         sell = False |         enter = latest[enter_type.value] == 1 | ||||||
|         if SignalType.SELL.value in latest: |  | ||||||
|             sell = latest[SignalType.SELL.value] == 1 |  | ||||||
|  |  | ||||||
|         buy_tag = latest.get(SignalTagType.BUY_TAG.value, None) |         exit = False | ||||||
|  |         if exit_type.value in latest: | ||||||
|  |             exit = latest[exit_type.value] == 1 | ||||||
|  |  | ||||||
|         logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', |         enter_tag_value = latest.get(enter_tag.value, None) | ||||||
|                      latest['date'], pair, str(buy), str(sell)) |  | ||||||
|  |         logger.debug(f'trigger: %s (pair=%s) {enter_type.value}=%s {exit_type.value}=%s', | ||||||
|  |                      latest['date'], pair, str(enter), str(exit)) | ||||||
|         timeframe_seconds = timeframe_to_seconds(timeframe) |         timeframe_seconds = timeframe_to_seconds(timeframe) | ||||||
|         if self.ignore_expired_candle(latest_date=latest_date, |         if self.ignore_expired_candle(latest_date=latest_date, | ||||||
|                                       current_time=datetime.now(timezone.utc), |                                       current_time=datetime.now(timezone.utc), | ||||||
|                                       timeframe_seconds=timeframe_seconds, |                                       timeframe_seconds=timeframe_seconds, | ||||||
|                                       buy=buy): |                                       enter=enter): | ||||||
|             return False, sell, buy_tag |             return False, exit, enter_tag_value | ||||||
|         return buy, sell, buy_tag |         return enter, exit, enter_tag_value | ||||||
|  |  | ||||||
|     def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, |     def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, | ||||||
|                               timeframe_seconds: int, buy: bool): |                               timeframe_seconds: int, enter: bool): | ||||||
|         if self.ignore_buying_expired_candle_after and buy: |         if self.ignore_buying_expired_candle_after and enter: | ||||||
|             time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) |             time_delta = current_time - (latest_date + timedelta(seconds=timeframe_seconds)) | ||||||
|             return time_delta.total_seconds() > self.ignore_buying_expired_candle_after |             return time_delta.total_seconds() > self.ignore_buying_expired_candle_after | ||||||
|         else: |         else: | ||||||
|             return False |             return False | ||||||
|  |  | ||||||
|     def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool, |     def should_sell(self, trade: Trade, rate: float, date: datetime, enter: bool, | ||||||
|                     sell: bool, low: float = None, high: float = None, |                     exit: bool, low: float = None, high: float = None, | ||||||
|                     force_stoploss: float = 0) -> SellCheckTuple: |                     force_stoploss: float = 0) -> SellCheckTuple: | ||||||
|         """ |         """ | ||||||
|         This function evaluates if one of the conditions required to trigger a sell |         This function evaluates if one of the conditions required to trigger a sell/exit_short | ||||||
|         has been reached, which can either be a stop-loss, ROI or sell-signal. |         has been reached, which can either be a stop-loss, ROI or exit-signal. | ||||||
|         :param low: Only used during backtesting to simulate stoploss |         :param low: Only used during backtesting to simulate (long)stoploss/(short)ROI | ||||||
|         :param high: Only used during backtesting, to simulate ROI |         :param high: Only used during backtesting, to simulate (short)stoploss/(long)ROI | ||||||
|         :param force_stoploss: Externally provided stoploss |         :param force_stoploss: Externally provided stoploss | ||||||
|         :return: True if trade should be sold, False otherwise |         :return: True if trade should be exited, False otherwise | ||||||
|         """ |         """ | ||||||
|         current_rate = rate |         current_rate = rate | ||||||
|         current_profit = trade.calc_profit_ratio(current_rate) |         current_profit = trade.calc_profit_ratio(current_rate) | ||||||
| @@ -578,8 +596,8 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         current_rate = high or rate |         current_rate = high or rate | ||||||
|         current_profit = trade.calc_profit_ratio(current_rate) |         current_profit = trade.calc_profit_ratio(current_rate) | ||||||
|  |  | ||||||
|         # if buy signal and ignore_roi is set, we don't need to evaluate min_roi. |         # if enter signal and ignore_roi is set, we don't need to evaluate min_roi. | ||||||
|         roi_reached = (not (buy and self.ignore_roi_if_buy_signal) |         roi_reached = (not (enter and self.ignore_roi_if_buy_signal) | ||||||
|                        and self.min_roi_reached(trade=trade, current_profit=current_profit, |                        and self.min_roi_reached(trade=trade, current_profit=current_profit, | ||||||
|                                                 current_time=date)) |                                                 current_time=date)) | ||||||
|  |  | ||||||
| @@ -592,10 +610,11 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         if (self.sell_profit_only and current_profit <= self.sell_profit_offset): |         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 |             # sell_profit_only and profit doesn't reach the offset - ignore sell signal | ||||||
|             pass |             pass | ||||||
|         elif self.use_sell_signal and not buy: |         elif self.use_sell_signal and not enter: | ||||||
|             if sell: |             if exit: | ||||||
|                 sell_signal = SellType.SELL_SIGNAL |                 sell_signal = SellType.SELL_SIGNAL | ||||||
|             else: |             else: | ||||||
|  |                 trade_type = "exit_short" if trade.is_short else "sell" | ||||||
|                 custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)( |                 custom_reason = strategy_safe_wrapper(self.custom_sell, default_retval=False)( | ||||||
|                     pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate, |                     pair=trade.pair, trade=trade, current_time=date, current_rate=current_rate, | ||||||
|                     current_profit=current_profit) |                     current_profit=current_profit) | ||||||
| @@ -603,18 +622,18 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|                     sell_signal = SellType.CUSTOM_SELL |                     sell_signal = SellType.CUSTOM_SELL | ||||||
|                     if isinstance(custom_reason, str): |                     if isinstance(custom_reason, str): | ||||||
|                         if len(custom_reason) > CUSTOM_SELL_MAX_LENGTH: |                         if len(custom_reason) > CUSTOM_SELL_MAX_LENGTH: | ||||||
|                             logger.warning(f'Custom sell reason returned from custom_sell is too ' |                             logger.warning(f'Custom {trade_type} reason returned from ' | ||||||
|                                            f'long and was trimmed to {CUSTOM_SELL_MAX_LENGTH} ' |                                            f'custom_{trade_type} is too long and was trimmed' | ||||||
|                                            f'characters.') |                                            f'to {CUSTOM_SELL_MAX_LENGTH} characters.') | ||||||
|                             custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH] |                             custom_reason = custom_reason[:CUSTOM_SELL_MAX_LENGTH] | ||||||
|                     else: |                     else: | ||||||
|                         custom_reason = None |                         custom_reason = None | ||||||
|             # TODO: return here if sell-signal should be favored over ROI |             # TODO: return here if exit-signal should be favored over ROI | ||||||
|  |  | ||||||
|         # Start evaluations |         # Start evaluations | ||||||
|         # Sequence: |         # Sequence: | ||||||
|         # ROI (if not stoploss) |         # ROI (if not stoploss) | ||||||
|         # Sell-signal |         # Exit-signal | ||||||
|         # Stoploss |         # Stoploss | ||||||
|         if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: |         if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS: | ||||||
|             logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI") |             logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI") | ||||||
| @@ -632,7 +651,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|             return stoplossflag |             return stoplossflag | ||||||
|  |  | ||||||
|         # This one is noisy, commented out... |         # This one is noisy, commented out... | ||||||
|         # logger.debug(f"{trade.pair} - No sell signal.") |         # logger.debug(f"{trade.pair} - No exit signal.") | ||||||
|         return SellCheckTuple(sell_type=SellType.NONE) |         return SellCheckTuple(sell_type=SellType.NONE) | ||||||
|  |  | ||||||
|     def stop_loss_reached(self, current_rate: float, trade: Trade, |     def stop_loss_reached(self, current_rate: float, trade: Trade, | ||||||
| @@ -641,7 +660,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|                           high: float = None) -> SellCheckTuple: |                           high: float = None) -> SellCheckTuple: | ||||||
|         """ |         """ | ||||||
|         Based on current profit of the trade and configured (trailing) stoploss, |         Based on current profit of the trade and configured (trailing) stoploss, | ||||||
|         decides to sell or not |         decides to exit or not | ||||||
|         :param current_profit: current profit as ratio |         :param current_profit: current profit as ratio | ||||||
|         :param low: Low value of this candle, only set in backtesting |         :param low: Low value of this candle, only set in backtesting | ||||||
|         :param high: High value of this candle, only set in backtesting |         :param high: High value of this candle, only set in backtesting | ||||||
| @@ -651,7 +670,12 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         # Initiate stoploss with open_rate. Does nothing if stoploss is already set. |         # Initiate stoploss with open_rate. Does nothing if stoploss is already set. | ||||||
|         trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) |         trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True) | ||||||
|  |  | ||||||
|         if self.use_custom_stoploss and trade.stop_loss < (low or current_rate): |         dir_correct = ( | ||||||
|  |             trade.stop_loss < (low or current_rate) and not trade.is_short or | ||||||
|  |             trade.stop_loss > (low or current_rate) and trade.is_short | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         if self.use_custom_stoploss and dir_correct: | ||||||
|             stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None |             stop_loss_value = strategy_safe_wrapper(self.custom_stoploss, default_retval=None | ||||||
|                                                     )(pair=trade.pair, trade=trade, |                                                     )(pair=trade.pair, trade=trade, | ||||||
|                                                       current_time=current_time, |                                                       current_time=current_time, | ||||||
| @@ -735,7 +759,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|     def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: |     def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]: | ||||||
|         """ |         """ | ||||||
|         Populates indicators for given candle (OHLCV) data (for multiple pairs) |         Populates indicators for given candle (OHLCV) data (for multiple pairs) | ||||||
|         Does not run advise_buy or advise_sell! |         Does not run advise_enter or advise_exit! | ||||||
|         Used by optimize operations only, not during dry / live runs. |         Used by optimize operations only, not during dry / live runs. | ||||||
|         Using .copy() to get a fresh copy of the dataframe for every strategy run. |         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 |         Has positive effects on memory usage for whatever reason - also when | ||||||
| @@ -746,7 +770,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|  |  | ||||||
|     def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Populate indicators that will be used in the Buy and Sell strategy |         Populate indicators that will be used in the Buy, Sell, short, exit_short strategy | ||||||
|         This method should not be overridden. |         This method should not be overridden. | ||||||
|         :param dataframe: Dataframe with data from the exchange |         :param dataframe: Dataframe with data from the exchange | ||||||
|         :param metadata: Additional information, like the currently traded pair |         :param metadata: Additional information, like the currently traded pair | ||||||
| @@ -760,37 +784,60 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         else: |         else: | ||||||
|             return self.populate_indicators(dataframe, metadata) |             return self.populate_indicators(dataframe, metadata) | ||||||
|  |  | ||||||
|     def advise_buy(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def advise_enter( | ||||||
|  |         self, | ||||||
|  |         dataframe: DataFrame, | ||||||
|  |         metadata: dict, | ||||||
|  |         is_short: bool = False | ||||||
|  |     ) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Based on TA indicators, populates the buy signal for the given dataframe |         Based on TA indicators, populates the buy/short signal for the given dataframe | ||||||
|         This method should not be overridden. |         This method should not be overridden. | ||||||
|         :param dataframe: DataFrame |         :param dataframe: DataFrame | ||||||
|         :param metadata: Additional information dictionary, with details like the |         :param metadata: Additional information dictionary, with details like the | ||||||
|             currently traded pair |             currently traded pair | ||||||
|         :return: DataFrame with buy column |         :return: DataFrame with buy column | ||||||
|         """ |         """ | ||||||
|         logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.") |         (type, fun_len) = ( | ||||||
|  |             ("short", self._short_fun_len) | ||||||
|  |             if is_short else | ||||||
|  |             ("buy", self._buy_fun_len) | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         if self._buy_fun_len == 2: |         logger.debug(f"Populating {type} signals for pair {metadata.get('pair')}.") | ||||||
|  |  | ||||||
|  |         if fun_len == 2: | ||||||
|             warnings.warn("deprecated - check out the Sample strategy to see " |             warnings.warn("deprecated - check out the Sample strategy to see " | ||||||
|                           "the current function headers!", DeprecationWarning) |                           "the current function headers!", DeprecationWarning) | ||||||
|             return self.populate_buy_trend(dataframe)  # type: ignore |             return self.populate_enter_trend(dataframe)  # type: ignore | ||||||
|         else: |         else: | ||||||
|             return self.populate_buy_trend(dataframe, metadata) |             return self.populate_enter_trend(dataframe, metadata) | ||||||
|  |  | ||||||
|     def advise_sell(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def advise_exit( | ||||||
|  |         self, | ||||||
|  |         dataframe: DataFrame, | ||||||
|  |         metadata: dict, | ||||||
|  |         is_short: bool = False | ||||||
|  |     ) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Based on TA indicators, populates the sell signal for the given dataframe |         Based on TA indicators, populates the sell/exit_short signal for the given dataframe | ||||||
|         This method should not be overridden. |         This method should not be overridden. | ||||||
|         :param dataframe: DataFrame |         :param dataframe: DataFrame | ||||||
|         :param metadata: Additional information dictionary, with details like the |         :param metadata: Additional information dictionary, with details like the | ||||||
|             currently traded pair |             currently traded pair | ||||||
|         :return: DataFrame with sell column |         :return: DataFrame with sell column | ||||||
|         """ |         """ | ||||||
|         logger.debug(f"Populating sell signals for pair {metadata.get('pair')}.") |  | ||||||
|         if self._sell_fun_len == 2: |         (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: | ||||||
|             warnings.warn("deprecated - check out the Sample strategy to see " |             warnings.warn("deprecated - check out the Sample strategy to see " | ||||||
|                           "the current function headers!", DeprecationWarning) |                           "the current function headers!", DeprecationWarning) | ||||||
|             return self.populate_sell_trend(dataframe)  # type: ignore |             return self.populate_exit_trend(dataframe)  # type: ignore | ||||||
|         else: |         else: | ||||||
|             return self.populate_sell_trend(dataframe, metadata) |             return self.populate_exit_trend(dataframe, metadata) | ||||||
|   | |||||||
| @@ -58,7 +58,11 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, | |||||||
|     return dataframe |     return dataframe | ||||||
|  |  | ||||||
|  |  | ||||||
| def stoploss_from_open(open_relative_stop: float, current_profit: float) -> float: | def stoploss_from_open( | ||||||
|  |     open_relative_stop: float, | ||||||
|  |     current_profit: float, | ||||||
|  |     for_short: bool = False | ||||||
|  | ) -> float: | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     Given the current profit, and a desired stop loss value relative to the open price, |     Given the current profit, and a desired stop loss value relative to the open price, | ||||||
| @@ -72,14 +76,17 @@ def stoploss_from_open(open_relative_stop: float, current_profit: float) -> floa | |||||||
|  |  | ||||||
|     :param open_relative_stop: Desired stop loss percentage relative to open price |     :param open_relative_stop: Desired stop loss percentage relative to open price | ||||||
|     :param current_profit: The current profit percentage |     :param current_profit: The current profit percentage | ||||||
|     :return: Positive stop loss value relative to current price |     :return: Stop loss value relative to current price | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|     # formula is undefined for current_profit -1, return maximum value |     # formula is undefined for current_profit -1, return maximum value | ||||||
|     if current_profit == -1: |     if current_profit == -1: | ||||||
|         return 1 |         return 1 | ||||||
|  |  | ||||||
|     stoploss = 1-((1+open_relative_stop)/(1+current_profit)) |     stoploss = 1-((1+open_relative_stop)/(1+current_profit))  # TODO-lev: Is this right? | ||||||
|  |  | ||||||
|     # negative stoploss values indicate the requested stop price is higher than the current price |     # negative stoploss values indicate the requested stop price is higher than the current price | ||||||
|     return max(stoploss, 0.0) |     if for_short: | ||||||
|  |         return min(stoploss, 0.0) | ||||||
|  |     else: | ||||||
|  |         return max(stoploss, 0.0) | ||||||
|   | |||||||
| @@ -172,3 +172,125 @@ class SampleHyperOpt(IHyperOpt): | |||||||
|             return dataframe |             return dataframe | ||||||
|  |  | ||||||
|         return populate_sell_trend |         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: | ||||||
|  |                 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), | ||||||
|  |                     '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') | ||||||
|  |         ] | ||||||
|   | |||||||
| @@ -187,9 +187,132 @@ class AdvancedSampleHyperOpt(IHyperOpt): | |||||||
|  |  | ||||||
|         return populate_sell_trend |         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: | ||||||
|  |                 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), | ||||||
|  |                     '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') | ||||||
|  |         ] | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def generate_roi_table(params: Dict) -> Dict[int, float]: |     def generate_roi_table(params: Dict) -> Dict[int, float]: | ||||||
|         """ |         """ | ||||||
|  |         # TODO-lev? | ||||||
|         Generate the ROI table that will be used by Hyperopt |         Generate the ROI table that will be used by Hyperopt | ||||||
|  |  | ||||||
|         This implementation generates the default legacy Freqtrade ROI tables. |         This implementation generates the default legacy Freqtrade ROI tables. | ||||||
| @@ -211,6 +334,7 @@ class AdvancedSampleHyperOpt(IHyperOpt): | |||||||
|     @staticmethod |     @staticmethod | ||||||
|     def roi_space() -> List[Dimension]: |     def roi_space() -> List[Dimension]: | ||||||
|         """ |         """ | ||||||
|  |         # TODO-lev? | ||||||
|         Values to search for each ROI steps |         Values to search for each ROI steps | ||||||
|  |  | ||||||
|         Override it if you need some different ranges for the parameters in the |         Override it if you need some different ranges for the parameters in the | ||||||
| @@ -231,6 +355,7 @@ class AdvancedSampleHyperOpt(IHyperOpt): | |||||||
|     @staticmethod |     @staticmethod | ||||||
|     def stoploss_space() -> List[Dimension]: |     def stoploss_space() -> List[Dimension]: | ||||||
|         """ |         """ | ||||||
|  |         # TODO-lev? | ||||||
|         Stoploss Value to search |         Stoploss Value to search | ||||||
|  |  | ||||||
|         Override it if you need some different range for the parameter in the |         Override it if you need some different range for the parameter in the | ||||||
| @@ -243,6 +368,7 @@ class AdvancedSampleHyperOpt(IHyperOpt): | |||||||
|     @staticmethod |     @staticmethod | ||||||
|     def trailing_space() -> List[Dimension]: |     def trailing_space() -> List[Dimension]: | ||||||
|         """ |         """ | ||||||
|  |         # TODO-lev? | ||||||
|         Create a trailing stoploss space. |         Create a trailing stoploss space. | ||||||
|  |  | ||||||
|         You may override it in your custom Hyperopt class. |         You may override it in your custom Hyperopt class. | ||||||
|   | |||||||
| @@ -29,7 +29,7 @@ class SampleStrategy(IStrategy): | |||||||
|  |  | ||||||
|     You must keep: |     You must keep: | ||||||
|     - the lib in the section "Do not remove these libs" |     - the lib in the section "Do not remove these libs" | ||||||
|     - the methods: populate_indicators, populate_buy_trend, populate_sell_trend |     - the methods: populate_indicators, populate_buy_trend, populate_sell_trend, populate_short_trend, populate_exit_short_trend | ||||||
|     You should keep: |     You should keep: | ||||||
|     - timeframe, minimal_roi, stoploss, trailing_* |     - timeframe, minimal_roi, stoploss, trailing_* | ||||||
|     """ |     """ | ||||||
| @@ -58,6 +58,8 @@ class SampleStrategy(IStrategy): | |||||||
|     # Hyperoptable parameters |     # Hyperoptable parameters | ||||||
|     buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) |     buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) | ||||||
|     sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) |     sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True) | ||||||
|  |     short_rsi = IntParameter(low=51, high=100, default=70, space='sell', optimize=True, load=True) | ||||||
|  |     exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True) | ||||||
|  |  | ||||||
|     # Optimal timeframe for the strategy. |     # Optimal timeframe for the strategy. | ||||||
|     timeframe = '5m' |     timeframe = '5m' | ||||||
| @@ -373,3 +375,40 @@ class SampleStrategy(IStrategy): | |||||||
|             ), |             ), | ||||||
|             'sell'] = 1 |             'sell'] = 1 | ||||||
|         return dataframe |         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 | ||||||
|  |                 (dataframe['tema'] > dataframe['tema'].shift(1)) &  # Guard: tema is raising | ||||||
|  |                 (dataframe['volume'] > 0)  # Make sure Volume is not 0 | ||||||
|  |             ), | ||||||
|  |             'exit_short'] = 1 | ||||||
|  |  | ||||||
|  |         return dataframe | ||||||
|   | |||||||
| @@ -105,6 +105,66 @@ class DefaultHyperOpt(IHyperOpt): | |||||||
|             Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger') |             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') | ||||||
|  |         ] | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def sell_strategy_generator(params: Dict[str, Any]) -> Callable: |     def sell_strategy_generator(params: Dict[str, Any]) -> Callable: | ||||||
|         """ |         """ | ||||||
| @@ -148,6 +208,49 @@ class DefaultHyperOpt(IHyperOpt): | |||||||
|  |  | ||||||
|         return populate_sell_trend |         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 |     @staticmethod | ||||||
|     def sell_indicator_space() -> List[Dimension]: |     def sell_indicator_space() -> List[Dimension]: | ||||||
|         """ |         """ | ||||||
| @@ -167,6 +270,25 @@ class DefaultHyperOpt(IHyperOpt): | |||||||
|                          'sell-sar_reversal'], name='sell-trigger') |                          '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') | ||||||
|  |         ] | ||||||
|  |  | ||||||
|     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: |     def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: | ||||||
|         """ |         """ | ||||||
|         Based on TA indicators. Should be a copy of same method from strategy. |         Based on TA indicators. Should be a copy of same method from strategy. | ||||||
| @@ -200,3 +322,37 @@ class DefaultHyperOpt(IHyperOpt): | |||||||
|             'sell'] = 1 |             'sell'] = 1 | ||||||
|  |  | ||||||
|         return dataframe |         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( | ||||||
|  |                     dataframe['macdsignal'], dataframe['macd'] | ||||||
|  |                 )) & | ||||||
|  |                 (dataframe['fastd'] < 46) | ||||||
|  |             ), | ||||||
|  |             'sell'] = 1 | ||||||
|  |  | ||||||
|  |         return dataframe | ||||||
|   | |||||||
| @@ -597,8 +597,8 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None: | |||||||
|     backtesting = Backtesting(default_conf) |     backtesting = Backtesting(default_conf) | ||||||
|     backtesting._set_strategy(backtesting.strategylist[0]) |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|     backtesting.required_startup = 0 |     backtesting.required_startup = 0 | ||||||
|     backtesting.strategy.advise_buy = lambda a, m: frame |     backtesting.strategy.advise_enter = lambda a, m: frame | ||||||
|     backtesting.strategy.advise_sell = lambda a, m: frame |     backtesting.strategy.advise_exit = lambda a, m: frame | ||||||
|     backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss |     backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss | ||||||
|     caplog.set_level(logging.DEBUG) |     caplog.set_level(logging.DEBUG) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -290,8 +290,8 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: | |||||||
|     assert backtesting.config == default_conf |     assert backtesting.config == default_conf | ||||||
|     assert backtesting.timeframe == '5m' |     assert backtesting.timeframe == '5m' | ||||||
|     assert callable(backtesting.strategy.ohlcvdata_to_dataframe) |     assert callable(backtesting.strategy.ohlcvdata_to_dataframe) | ||||||
|     assert callable(backtesting.strategy.advise_buy) |     assert callable(backtesting.strategy.advise_enter) | ||||||
|     assert callable(backtesting.strategy.advise_sell) |     assert callable(backtesting.strategy.advise_exit) | ||||||
|     assert isinstance(backtesting.strategy.dp, DataProvider) |     assert isinstance(backtesting.strategy.dp, DataProvider) | ||||||
|     get_fee.assert_called() |     get_fee.assert_called() | ||||||
|     assert backtesting.fee == 0.5 |     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) |     backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) | ||||||
|     backtesting = Backtesting(default_conf) |     backtesting = Backtesting(default_conf) | ||||||
|     backtesting._set_strategy(backtesting.strategylist[0]) |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|     backtesting.strategy.advise_buy = fun  # Override |     backtesting.strategy.advise_enter = fun  # Override | ||||||
|     backtesting.strategy.advise_sell = fun  # Override |     backtesting.strategy.advise_exit = fun  # Override | ||||||
|     result = backtesting.backtest(**backtest_conf) |     result = backtesting.backtest(**backtest_conf) | ||||||
|     assert result['results'].empty |     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) |     backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir) | ||||||
|     backtesting = Backtesting(default_conf) |     backtesting = Backtesting(default_conf) | ||||||
|     backtesting._set_strategy(backtesting.strategylist[0]) |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|     backtesting.strategy.advise_buy = fun  # Override |     backtesting.strategy.advise_enter = fun  # Override | ||||||
|     backtesting.strategy.advise_sell = fun  # Override |     backtesting.strategy.advise_exit = fun  # Override | ||||||
|     result = backtesting.backtest(**backtest_conf) |     result = backtesting.backtest(**backtest_conf) | ||||||
|     assert result['results'].empty |     assert result['results'].empty | ||||||
|  |  | ||||||
| @@ -731,8 +731,8 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir): | |||||||
|     backtesting = Backtesting(default_conf) |     backtesting = Backtesting(default_conf) | ||||||
|     backtesting.required_startup = 0 |     backtesting.required_startup = 0 | ||||||
|     backtesting._set_strategy(backtesting.strategylist[0]) |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|     backtesting.strategy.advise_buy = _trend_alternate  # Override |     backtesting.strategy.advise_enter = _trend_alternate  # Override | ||||||
|     backtesting.strategy.advise_sell = _trend_alternate  # Override |     backtesting.strategy.advise_exit = _trend_alternate  # Override | ||||||
|     result = backtesting.backtest(**backtest_conf) |     result = backtesting.backtest(**backtest_conf) | ||||||
|     # 200 candles in backtest data |     # 200 candles in backtest data | ||||||
|     # won't buy on first (shifted by 1) |     # 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 = Backtesting(default_conf) | ||||||
|     backtesting._set_strategy(backtesting.strategylist[0]) |     backtesting._set_strategy(backtesting.strategylist[0]) | ||||||
|     backtesting.strategy.advise_buy = _trend_alternate_hold  # Override |     backtesting.strategy.advise_enter = _trend_alternate_hold  # Override | ||||||
|     backtesting.strategy.advise_sell = _trend_alternate_hold  # Override |     backtesting.strategy.advise_exit = _trend_alternate_hold  # Override | ||||||
|  |  | ||||||
|     processed = backtesting.strategy.ohlcvdata_to_dataframe(data) |     processed = backtesting.strategy.ohlcvdata_to_dataframe(data) | ||||||
|     min_date, max_date = get_timerange(processed) |     min_date, max_date = get_timerange(processed) | ||||||
|   | |||||||
| @@ -25,6 +25,9 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, | |||||||
| from .hyperopts.default_hyperopt import DefaultHyperOpt | from .hyperopts.default_hyperopt import DefaultHyperOpt | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # TODO-lev: This file | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: | def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: | ||||||
|     patched_configuration_load_config_file(mocker, default_conf) |     patched_configuration_load_config_file(mocker, default_conf) | ||||||
|  |  | ||||||
| @@ -363,8 +366,8 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: | |||||||
|     # Should be called for historical candle data |     # Should be called for historical candle data | ||||||
|     assert dumper.call_count == 1 |     assert dumper.call_count == 1 | ||||||
|     assert dumper2.call_count == 1 |     assert dumper2.call_count == 1 | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_sell") |     assert hasattr(hyperopt.backtesting.strategy, "advise_exit") | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_buy") |     assert hasattr(hyperopt.backtesting.strategy, "advise_enter") | ||||||
|     assert hasattr(hyperopt, "max_open_trades") |     assert hasattr(hyperopt, "max_open_trades") | ||||||
|     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] |     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] | ||||||
|     assert hasattr(hyperopt, "position_stacking") |     assert hasattr(hyperopt, "position_stacking") | ||||||
| @@ -822,8 +825,8 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non | |||||||
|     assert dumper.call_count == 1 |     assert dumper.call_count == 1 | ||||||
|     assert dumper2.call_count == 1 |     assert dumper2.call_count == 1 | ||||||
|  |  | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_sell") |     assert hasattr(hyperopt.backtesting.strategy, "advise_exit") | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_buy") |     assert hasattr(hyperopt.backtesting.strategy, "advise_enter") | ||||||
|     assert hasattr(hyperopt, "max_open_trades") |     assert hasattr(hyperopt, "max_open_trades") | ||||||
|     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] |     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] | ||||||
|     assert hasattr(hyperopt, "position_stacking") |     assert hasattr(hyperopt, "position_stacking") | ||||||
| @@ -903,8 +906,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: | |||||||
|     assert dumper.called |     assert dumper.called | ||||||
|     assert dumper.call_count == 1 |     assert dumper.call_count == 1 | ||||||
|     assert dumper2.call_count == 1 |     assert dumper2.call_count == 1 | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_sell") |     assert hasattr(hyperopt.backtesting.strategy, "advise_exit") | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_buy") |     assert hasattr(hyperopt.backtesting.strategy, "advise_enter") | ||||||
|     assert hasattr(hyperopt, "max_open_trades") |     assert hasattr(hyperopt, "max_open_trades") | ||||||
|     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] |     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] | ||||||
|     assert hasattr(hyperopt, "position_stacking") |     assert hasattr(hyperopt, "position_stacking") | ||||||
| @@ -957,8 +960,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: | |||||||
|     assert dumper.called |     assert dumper.called | ||||||
|     assert dumper.call_count == 1 |     assert dumper.call_count == 1 | ||||||
|     assert dumper2.call_count == 1 |     assert dumper2.call_count == 1 | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_sell") |     assert hasattr(hyperopt.backtesting.strategy, "advise_exit") | ||||||
|     assert hasattr(hyperopt.backtesting.strategy, "advise_buy") |     assert hasattr(hyperopt.backtesting.strategy, "advise_enter") | ||||||
|     assert hasattr(hyperopt, "max_open_trades") |     assert hasattr(hyperopt, "max_open_trades") | ||||||
|     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] |     assert hyperopt.max_open_trades == hyperopt_conf['max_open_trades'] | ||||||
|     assert hasattr(hyperopt, "position_stacking") |     assert hasattr(hyperopt, "position_stacking") | ||||||
|   | |||||||
| @@ -264,7 +264,7 @@ def test_api_UvicornServer(mocker): | |||||||
|     assert thread_mock.call_count == 1 |     assert thread_mock.call_count == 1 | ||||||
|  |  | ||||||
|     s.cleanup() |     s.cleanup() | ||||||
|     assert s.should_exit is True |     assert s.should_sell is True | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_api_UvicornServer_run(mocker): | def test_api_UvicornServer_run(mocker): | ||||||
|   | |||||||
| @@ -154,3 +154,48 @@ class DefaultStrategy(IStrategy): | |||||||
|             ), |             ), | ||||||
|             'sell'] = 1 |             'sell'] = 1 | ||||||
|         return dataframe |         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[ | ||||||
|  |             ( | ||||||
|  |                 ( | ||||||
|  |                     (qtpylib.crossed_below(dataframe['rsi'], 30)) | | ||||||
|  |                     (qtpylib.crossed_below(dataframe['fastd'], 30)) | ||||||
|  |                 ) & | ||||||
|  |                 (dataframe['adx'] < 90) & | ||||||
|  |                 (dataframe['minus_di'] < 0)  # TODO-lev: what to do here | ||||||
|  |             ) | | ||||||
|  |             ( | ||||||
|  |                 (dataframe['adx'] > 30) & | ||||||
|  |                 (dataframe['minus_di'] < 0.5)   # TODO-lev: what to do here | ||||||
|  |             ), | ||||||
|  |             'exit_short'] = 1 | ||||||
|  |         return dataframe | ||||||
|   | |||||||
| @@ -60,6 +60,15 @@ class HyperoptableStrategy(IStrategy): | |||||||
|         'sell_minusdi': 0.4 |         'sell_minusdi': 0.4 | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     short_params = { | ||||||
|  |         'short_rsi': 65, | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     exit_short_params = { | ||||||
|  |         'exit_short_rsi': 26, | ||||||
|  |         'exit_short_minusdi': 0.6 | ||||||
|  |     } | ||||||
|  |  | ||||||
|     buy_rsi = IntParameter([0, 50], default=30, space='buy') |     buy_rsi = IntParameter([0, 50], default=30, space='buy') | ||||||
|     buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') |     buy_plusdi = RealParameter(low=0, high=1, default=0.5, space='buy') | ||||||
|     sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') |     sell_rsi = IntParameter(low=50, high=100, default=70, space='sell') | ||||||
| @@ -78,6 +87,12 @@ class HyperoptableStrategy(IStrategy): | |||||||
|             }) |             }) | ||||||
|         return prot |         return prot | ||||||
|  |  | ||||||
|  |     short_rsi = IntParameter([50, 100], default=70, space='sell') | ||||||
|  |     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) | ||||||
|  |  | ||||||
|     def informative_pairs(self): |     def informative_pairs(self): | ||||||
|         """ |         """ | ||||||
|         Define additional, informative pair/interval combinations to be cached from the exchange. |         Define additional, informative pair/interval combinations to be cached from the exchange. | ||||||
| @@ -167,7 +182,7 @@ class HyperoptableStrategy(IStrategy): | |||||||
|         Based on TA indicators, populates the sell signal for the given dataframe |         Based on TA indicators, populates the sell signal for the given dataframe | ||||||
|         :param dataframe: DataFrame |         :param dataframe: DataFrame | ||||||
|         :param metadata: Additional information, like the currently traded pair |         :param metadata: Additional information, like the currently traded pair | ||||||
|         :return: DataFrame with buy column |         :return: DataFrame with sell column | ||||||
|         """ |         """ | ||||||
|         dataframe.loc[ |         dataframe.loc[ | ||||||
|             ( |             ( | ||||||
| @@ -184,3 +199,48 @@ class HyperoptableStrategy(IStrategy): | |||||||
|             ), |             ), | ||||||
|             'sell'] = 1 |             'sell'] = 1 | ||||||
|         return dataframe |         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[ | ||||||
|  |             ( | ||||||
|  |                 ( | ||||||
|  |                     (qtpylib.crossed_below(dataframe['rsi'], self.exit_short_rsi.value)) | | ||||||
|  |                     (qtpylib.crossed_below(dataframe['fastd'], 30)) | ||||||
|  |                 ) & | ||||||
|  |                 (dataframe['adx'] < 90) & | ||||||
|  |                 (dataframe['minus_di'] < 0)  # TODO-lev: What should this be | ||||||
|  |             ) | | ||||||
|  |             ( | ||||||
|  |                 (dataframe['adx'] < 30) & | ||||||
|  |                 (dataframe['minus_di'] < self.exit_short_minusdi.value) | ||||||
|  |             ), | ||||||
|  |             'exit_short'] = 1 | ||||||
|  |         return dataframe | ||||||
|   | |||||||
| @@ -85,3 +85,34 @@ class TestStrategyLegacy(IStrategy): | |||||||
|             ), |             ), | ||||||
|             'sell'] = 1 |             'sell'] = 1 | ||||||
|         return dataframe |         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 | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ def test_default_strategy_structure(): | |||||||
|     assert hasattr(DefaultStrategy, 'populate_indicators') |     assert hasattr(DefaultStrategy, 'populate_indicators') | ||||||
|     assert hasattr(DefaultStrategy, 'populate_buy_trend') |     assert hasattr(DefaultStrategy, 'populate_buy_trend') | ||||||
|     assert hasattr(DefaultStrategy, 'populate_sell_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): | def test_default_strategy(result, fee): | ||||||
| @@ -27,6 +29,10 @@ def test_default_strategy(result, fee): | |||||||
|     assert type(indicators) is DataFrame |     assert type(indicators) is DataFrame | ||||||
|     assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame |     assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame | ||||||
|     assert type(strategy.populate_sell_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( |     trade = Trade( | ||||||
|         open_rate=19_000, |         open_rate=19_000, | ||||||
| @@ -37,10 +43,28 @@ def test_default_strategy(result, fee): | |||||||
|  |  | ||||||
|     assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, |     assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1, | ||||||
|                                         rate=20000, time_in_force='gtc', |                                         rate=20000, time_in_force='gtc', | ||||||
|                                         current_time=datetime.utcnow()) is True |                                         is_short=False, current_time=datetime.utcnow()) is True | ||||||
|  |  | ||||||
|     assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, |     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', |                                        rate=20000, time_in_force='gtc', sell_reason='roi', | ||||||
|                                        current_time=datetime.utcnow()) is True |                                        is_short=False, current_time=datetime.utcnow()) is True | ||||||
|  |  | ||||||
|  |     # TODO-lev: Test for shorts? | ||||||
|     assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(), |     assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(), | ||||||
|                                     current_rate=20_000, current_profit=0.05) == strategy.stoploss |                                     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 | ||||||
|   | |||||||
| @@ -156,17 +156,21 @@ def test_ignore_expired_candle(default_conf): | |||||||
|     # Add 1 candle length as the "latest date" defines candle open. |     # Add 1 candle length as the "latest date" defines candle open. | ||||||
|     current_time = latest_date + timedelta(seconds=80 + 300) |     current_time = latest_date + timedelta(seconds=80 + 300) | ||||||
|  |  | ||||||
|     assert strategy.ignore_expired_candle(latest_date=latest_date, |     assert strategy.ignore_expired_candle( | ||||||
|                                           current_time=current_time, |         latest_date=latest_date, | ||||||
|                                           timeframe_seconds=300, |         current_time=current_time, | ||||||
|                                           buy=True) is True |         timeframe_seconds=300, | ||||||
|  |         enter=True | ||||||
|  |     ) is True | ||||||
|  |  | ||||||
|     current_time = latest_date + timedelta(seconds=30 + 300) |     current_time = latest_date + timedelta(seconds=30 + 300) | ||||||
|  |  | ||||||
|     assert not strategy.ignore_expired_candle(latest_date=latest_date, |     assert not strategy.ignore_expired_candle( | ||||||
|                                               current_time=current_time, |         latest_date=latest_date, | ||||||
|                                               timeframe_seconds=300, |         current_time=current_time, | ||||||
|                                               buy=True) is True |         timeframe_seconds=300, | ||||||
|  |         enter=True | ||||||
|  |     ) is True | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_assert_df_raise(mocker, caplog, ohlcv_history): | def test_assert_df_raise(mocker, caplog, ohlcv_history): | ||||||
| @@ -478,20 +482,20 @@ def test_custom_sell(default_conf, fee, caplog) -> None: | |||||||
| def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: | def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: | ||||||
|     caplog.set_level(logging.DEBUG) |     caplog.set_level(logging.DEBUG) | ||||||
|     ind_mock = MagicMock(side_effect=lambda x, meta: x) |     ind_mock = MagicMock(side_effect=lambda x, meta: x) | ||||||
|     buy_mock = MagicMock(side_effect=lambda x, meta: x) |     enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x) | ||||||
|     sell_mock = MagicMock(side_effect=lambda x, meta: x) |     exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.strategy.interface.IStrategy', |         'freqtrade.strategy.interface.IStrategy', | ||||||
|         advise_indicators=ind_mock, |         advise_indicators=ind_mock, | ||||||
|         advise_buy=buy_mock, |         advise_enter=enter_mock, | ||||||
|         advise_sell=sell_mock, |         advise_exit=exit_mock, | ||||||
|  |  | ||||||
|     ) |     ) | ||||||
|     strategy = DefaultStrategy({}) |     strategy = DefaultStrategy({}) | ||||||
|     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) |     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) | ||||||
|     assert ind_mock.call_count == 1 |     assert ind_mock.call_count == 1 | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2 | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2 | ||||||
|  |  | ||||||
|     assert log_has('TA Analysis Launched', caplog) |     assert log_has('TA Analysis Launched', caplog) | ||||||
|     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) |     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) | ||||||
| @@ -500,8 +504,8 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: | |||||||
|     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) |     strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'}) | ||||||
|     # No analysis happens as process_only_new_candles is true |     # No analysis happens as process_only_new_candles is true | ||||||
|     assert ind_mock.call_count == 2 |     assert ind_mock.call_count == 2 | ||||||
|     assert buy_mock.call_count == 2 |     assert enter_mock.call_count == 4 | ||||||
|     assert buy_mock.call_count == 2 |     assert enter_mock.call_count == 4 | ||||||
|     assert log_has('TA Analysis Launched', caplog) |     assert log_has('TA Analysis Launched', caplog) | ||||||
|     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) |     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) | ||||||
|  |  | ||||||
| @@ -509,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: | def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> None: | ||||||
|     caplog.set_level(logging.DEBUG) |     caplog.set_level(logging.DEBUG) | ||||||
|     ind_mock = MagicMock(side_effect=lambda x, meta: x) |     ind_mock = MagicMock(side_effect=lambda x, meta: x) | ||||||
|     buy_mock = MagicMock(side_effect=lambda x, meta: x) |     enter_mock = MagicMock(side_effect=lambda x, meta, is_short: x) | ||||||
|     sell_mock = MagicMock(side_effect=lambda x, meta: x) |     exit_mock = MagicMock(side_effect=lambda x, meta, is_short: x) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.strategy.interface.IStrategy', |         'freqtrade.strategy.interface.IStrategy', | ||||||
|         advise_indicators=ind_mock, |         advise_indicators=ind_mock, | ||||||
|         advise_buy=buy_mock, |         advise_enter=enter_mock, | ||||||
|         advise_sell=sell_mock, |         advise_exit=exit_mock, | ||||||
|  |  | ||||||
|     ) |     ) | ||||||
|     strategy = DefaultStrategy({}) |     strategy = DefaultStrategy({}) | ||||||
| @@ -528,8 +532,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> | |||||||
|     assert 'close' in ret.columns |     assert 'close' in ret.columns | ||||||
|     assert isinstance(ret, DataFrame) |     assert isinstance(ret, DataFrame) | ||||||
|     assert ind_mock.call_count == 1 |     assert ind_mock.call_count == 1 | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2  # Once for buy, once for short | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2 | ||||||
|     assert log_has('TA Analysis Launched', caplog) |     assert log_has('TA Analysis Launched', caplog) | ||||||
|     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) |     assert not log_has('Skipping TA Analysis for already analyzed candle', caplog) | ||||||
|     caplog.clear() |     caplog.clear() | ||||||
| @@ -537,8 +541,8 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) -> | |||||||
|     ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'}) |     ret = strategy._analyze_ticker_internal(ohlcv_history, {'pair': 'ETH/BTC'}) | ||||||
|     # No analysis happens as process_only_new_candles is true |     # No analysis happens as process_only_new_candles is true | ||||||
|     assert ind_mock.call_count == 1 |     assert ind_mock.call_count == 1 | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2 | ||||||
|     assert buy_mock.call_count == 1 |     assert enter_mock.call_count == 2 | ||||||
|     # only skipped analyze adds buy and sell columns, otherwise it's all mocked |     # only skipped analyze adds buy and sell columns, otherwise it's all mocked | ||||||
|     assert 'buy' in ret.columns |     assert 'buy' in ret.columns | ||||||
|     assert 'sell' in ret.columns |     assert 'sell' in ret.columns | ||||||
| @@ -743,10 +747,10 @@ def test_auto_hyperopt_interface(default_conf): | |||||||
|     assert strategy.sell_minusdi.value == 0.5 |     assert strategy.sell_minusdi.value == 0.5 | ||||||
|     all_params = strategy.detect_all_parameters() |     all_params = strategy.detect_all_parameters() | ||||||
|     assert isinstance(all_params, dict) |     assert isinstance(all_params, dict) | ||||||
|     assert len(all_params['buy']) == 2 |     # TODO-lev: Should these be 4,4 and 10? | ||||||
|     assert len(all_params['sell']) == 2 |     assert len(all_params['buy']) == 4 | ||||||
|     # Number of Hyperoptable parameters |     assert len(all_params['sell']) == 4 | ||||||
|     assert all_params['count'] == 6 |     assert all_params['count'] == 10 | ||||||
|  |  | ||||||
|     strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') |     strategy.__class__.sell_rsi = IntParameter([0, 10], default=5, space='buy') | ||||||
|  |  | ||||||
|   | |||||||
| @@ -117,12 +117,18 @@ def test_strategy(result, default_conf): | |||||||
|     df_indicators = strategy.advise_indicators(result, metadata=metadata) |     df_indicators = strategy.advise_indicators(result, metadata=metadata) | ||||||
|     assert 'adx' in df_indicators |     assert 'adx' in df_indicators | ||||||
|  |  | ||||||
|     dataframe = strategy.advise_buy(df_indicators, metadata=metadata) |     dataframe = strategy.advise_enter(df_indicators, metadata=metadata, is_short=False) | ||||||
|     assert 'buy' in dataframe.columns |     assert 'buy' in dataframe.columns | ||||||
|  |  | ||||||
|     dataframe = strategy.advise_sell(df_indicators, metadata=metadata) |     dataframe = strategy.advise_exit(df_indicators, metadata=metadata, is_short=False) | ||||||
|     assert 'sell' in dataframe.columns |     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 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_strategy_override_minimal_roi(caplog, default_conf): | def test_strategy_override_minimal_roi(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
| @@ -218,6 +224,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf): | |||||||
| def test_strategy_override_order_types(caplog, default_conf): | def test_strategy_override_order_types(caplog, default_conf): | ||||||
|     caplog.set_level(logging.INFO) |     caplog.set_level(logging.INFO) | ||||||
|  |  | ||||||
|  |     # TODO-lev: Maybe change | ||||||
|     order_types = { |     order_types = { | ||||||
|         'buy': 'market', |         'buy': 'market', | ||||||
|         'sell': 'limit', |         'sell': 'limit', | ||||||
| @@ -345,7 +352,7 @@ def test_deprecate_populate_indicators(result, default_conf): | |||||||
|     with warnings.catch_warnings(record=True) as w: |     with warnings.catch_warnings(record=True) as w: | ||||||
|         # Cause all warnings to always be triggered. |         # Cause all warnings to always be triggered. | ||||||
|         warnings.simplefilter("always") |         warnings.simplefilter("always") | ||||||
|         strategy.advise_buy(indicators, {'pair': 'ETH/BTC'}) |         strategy.advise_enter(indicators, {'pair': 'ETH/BTC'}, is_short=False)  # TODO-lev | ||||||
|         assert len(w) == 1 |         assert len(w) == 1 | ||||||
|         assert issubclass(w[-1].category, DeprecationWarning) |         assert issubclass(w[-1].category, DeprecationWarning) | ||||||
|         assert "deprecated - check out the Sample strategy to see the current function headers!" \ |         assert "deprecated - check out the Sample strategy to see the current function headers!" \ | ||||||
| @@ -354,7 +361,7 @@ def test_deprecate_populate_indicators(result, default_conf): | |||||||
|     with warnings.catch_warnings(record=True) as w: |     with warnings.catch_warnings(record=True) as w: | ||||||
|         # Cause all warnings to always be triggered. |         # Cause all warnings to always be triggered. | ||||||
|         warnings.simplefilter("always") |         warnings.simplefilter("always") | ||||||
|         strategy.advise_sell(indicators, {'pair': 'ETH_BTC'}) |         strategy.advise_exit(indicators, {'pair': 'ETH_BTC'}, is_short=False)    # TODO-lev | ||||||
|         assert len(w) == 1 |         assert len(w) == 1 | ||||||
|         assert issubclass(w[-1].category, DeprecationWarning) |         assert issubclass(w[-1].category, DeprecationWarning) | ||||||
|         assert "deprecated - check out the Sample strategy to see the current function headers!" \ |         assert "deprecated - check out the Sample strategy to see the current function headers!" \ | ||||||
| @@ -374,6 +381,8 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): | |||||||
|     assert strategy._populate_fun_len == 2 |     assert strategy._populate_fun_len == 2 | ||||||
|     assert strategy._buy_fun_len == 2 |     assert strategy._buy_fun_len == 2 | ||||||
|     assert strategy._sell_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.INTERFACE_VERSION == 1 | ||||||
|     assert strategy.timeframe == '5m' |     assert strategy.timeframe == '5m' | ||||||
|     assert strategy.ticker_interval == '5m' |     assert strategy.ticker_interval == '5m' | ||||||
| @@ -382,14 +391,22 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): | |||||||
|     assert isinstance(indicator_df, DataFrame) |     assert isinstance(indicator_df, DataFrame) | ||||||
|     assert 'adx' in indicator_df.columns |     assert 'adx' in indicator_df.columns | ||||||
|  |  | ||||||
|     buydf = strategy.advise_buy(result, metadata=metadata) |     buydf = strategy.advise_enter(result, metadata=metadata, is_short=False) | ||||||
|     assert isinstance(buydf, DataFrame) |     assert isinstance(buydf, DataFrame) | ||||||
|     assert 'buy' in buydf.columns |     assert 'buy' in buydf.columns | ||||||
|  |  | ||||||
|     selldf = strategy.advise_sell(result, metadata=metadata) |     selldf = strategy.advise_exit(result, metadata=metadata, is_short=False) | ||||||
|     assert isinstance(selldf, DataFrame) |     assert isinstance(selldf, DataFrame) | ||||||
|     assert 'sell' in selldf |     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'.", |     assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", | ||||||
|                    caplog) |                    caplog) | ||||||
|  |  | ||||||
| @@ -403,16 +420,26 @@ def test_strategy_interface_versioning(result, monkeypatch, default_conf): | |||||||
|     assert strategy._populate_fun_len == 3 |     assert strategy._populate_fun_len == 3 | ||||||
|     assert strategy._buy_fun_len == 3 |     assert strategy._buy_fun_len == 3 | ||||||
|     assert strategy._sell_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 |     assert strategy.INTERFACE_VERSION == 2 | ||||||
|  |  | ||||||
|     indicator_df = strategy.advise_indicators(result, metadata=metadata) |     indicator_df = strategy.advise_indicators(result, metadata=metadata) | ||||||
|     assert isinstance(indicator_df, DataFrame) |     assert isinstance(indicator_df, DataFrame) | ||||||
|     assert 'adx' in indicator_df.columns |     assert 'adx' in indicator_df.columns | ||||||
|  |  | ||||||
|     buydf = strategy.advise_buy(result, metadata=metadata) |     buydf = strategy.advise_enter(result, metadata=metadata, is_short=False) | ||||||
|     assert isinstance(buydf, DataFrame) |     assert isinstance(buydf, DataFrame) | ||||||
|     assert 'buy' in buydf.columns |     assert 'buy' in buydf.columns | ||||||
|  |  | ||||||
|     selldf = strategy.advise_sell(result, metadata=metadata) |     selldf = strategy.advise_exit(result, metadata=metadata, is_short=False) | ||||||
|     assert isinstance(selldf, DataFrame) |     assert isinstance(selldf, DataFrame) | ||||||
|     assert 'sell' in selldf |     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 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user