From 5b826150dff2c2d0f04b4a93ab802a5ce8b50c63 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Mon, 5 Sep 2022 17:43:28 -0300 Subject: [PATCH 01/23] fix hyperopt - freqai --- freqtrade/freqai/data_drawer.py | 29 +++++++++++++++++++++++----- freqtrade/freqai/freqai_interface.py | 7 +++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 9eeabef8f..a1ecb7654 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -93,6 +93,16 @@ class FreqaiDataDrawer: "model_filename": "", "trained_timestamp": 0, "priority": 1, "first": True, "data_path": "", "extras": {}} + def __getstate__(self): + """ + Return state values to be pickled. + It's necessary to allow serialization in hyperopt + """ + return ({ + "pair_dict": self.pair_dict, + "pair_dictionary_path": self.pair_dictionary_path + }) + def load_drawer_from_disk(self): """ Locate and load a previously saved data drawer full of all pair model metadata in @@ -155,14 +165,23 @@ class FreqaiDataDrawer: # create a backup shutil.copy(self.historic_predictions_path, self.historic_predictions_bkp_path) - def save_drawer_to_disk(self): + def save_drawer_to_disk(self, live=False): """ Save data drawer full of all pair model metadata in present model folder. """ - with self.save_lock: + if live: + with self.save_lock: + with open(self.pair_dictionary_path, 'w') as fp: + rapidjson.dump( + self.pair_dict, fp, default=self.np_encoder, + number_mode=rapidjson.NM_NATIVE + ) + else: + # save_lock it's not working with hyperopt with open(self.pair_dictionary_path, 'w') as fp: - rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE) + rapidjson.dump( + self.pair_dict, fp, default=self.np_encoder, + number_mode=rapidjson.NM_NATIVE) def save_follower_dict_to_disk(self): """ @@ -437,7 +456,7 @@ class FreqaiDataDrawer: self.model_dictionary[coin] = model self.pair_dict[coin]["model_filename"] = dk.model_filename self.pair_dict[coin]["data_path"] = str(dk.data_path) - self.save_drawer_to_disk() + self.save_drawer_to_disk(dk.live) return diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index a9c21fb65..9a05e8383 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -90,6 +90,13 @@ class IFreqaiModel(ABC): self._threads: List[threading.Thread] = [] self._stop_event = threading.Event() + def __getstate__(self): + """ + Return state values to be pickled. + It's necessary to allow serialization in hyperopt + """ + return ({"dd": self.dd}) + def assert_config(self, config: Dict[str, Any]) -> None: if not config.get("freqai", {}): From 2c8e5b191bf84580684bf3a372121cef2e13d8f8 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Mon, 5 Sep 2022 17:43:55 -0300 Subject: [PATCH 02/23] fix hyperopt - freqai --- freqtrade/freqai/data_drawer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index a1ecb7654..dff6b5942 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -174,8 +174,7 @@ class FreqaiDataDrawer: with open(self.pair_dictionary_path, 'w') as fp: rapidjson.dump( self.pair_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE - ) + number_mode=rapidjson.NM_NATIVE) else: # save_lock it's not working with hyperopt with open(self.pair_dictionary_path, 'w') as fp: From 8d16dd804dcc0b0cfe94e1b3389186bc23d8e957 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Tue, 6 Sep 2022 15:42:47 -0300 Subject: [PATCH 03/23] hyperopt - freqai - docs and refactoring --- docs/freqai.md | 11 +++ freqtrade/configuration/configuration.py | 10 +++ freqtrade/freqai/data_drawer.py | 28 ++----- freqtrade/freqai/freqai_interface.py | 5 +- freqtrade/templates/FreqaiExampleStrategy.py | 86 +------------------- 5 files changed, 29 insertions(+), 111 deletions(-) diff --git a/docs/freqai.md b/docs/freqai.md index 303c2f151..f8cf64d21 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -279,6 +279,8 @@ The FreqAI strategy requires the user to include the following lines of code in Notice how the `populate_any_indicators()` is where the user adds their own features ([more information](#feature-engineering)) and labels ([more information](#setting-classifier-targets)). See a full example at `templates/FreqaiExampleStrategy.py`. +*Important*: The `self.freqai.start()` function cannot be called outside the `populate_indicators()`. + ### Setting the `startup_candle_count` Users need to take care to set the `startup_candle_count` in their strategy the same way they would for any normal Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling on the `dataprovider` to avoid any NaNs at the beginning of the first training. Users can easily set this value by identifying the longest period (in candle units) that they pass to their indicator creation functions (e.g. talib functions). In the present example, the user would pass 20 to as this value (since it is the maximum value in their `indicators_periods_candles`). @@ -532,6 +534,15 @@ for each pair, for each backtesting window within the expanded `--timerange`. --- +### Hyperopt + +The [Hyperopt](hyperopt.md) module can be executed with some restrictions: + +- The `--analyze-per-epoch` hyperopt parameter is not compatible with FreqAI. +- It's not possible to hyperopt indicators in `populate_any_indicators()` function. This means that the user cannot optimize model parameters using hyperopt. Apart from this exception, it is possible to optimize all other [spaces](hyperopt.md###runninghyperoptwithsmallersearchspace). +- The [Backtesting](#backtesting) instructions also apply apply to Hyperopt. + + ### Deciding the size of the sliding training window and backtesting duration The user defines the backtesting timerange with the typical `--timerange` parameter in the diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 7c68ac46c..d18b67ff2 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -285,6 +285,7 @@ class Configuration: logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"]) # Hyperopt section + self._check_hyperopt_analyze_per_epoch_freqai() self._args_to_config(config, argname='hyperopt', logstring='Using Hyperopt class name: {}') @@ -537,3 +538,12 @@ class Configuration: config['pairs'] = load_file(pairs_file) if 'pairs' in config and isinstance(config['pairs'], list): config['pairs'].sort() + + def _check_hyperopt_analyze_per_epoch_freqai(self) -> None: + """ + Helper for block hyperopt with analyze-per-epoch param. + """ + if ("analyze_per_epoch" in self.args and + self.args["analyze_per_epoch"] and "freqaimodel" in self.args): + raise OperationalException('analyze-per-epoch parameter is \ + not allowed with a Freqai strategy.') diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index dff6b5942..9eeabef8f 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -93,16 +93,6 @@ class FreqaiDataDrawer: "model_filename": "", "trained_timestamp": 0, "priority": 1, "first": True, "data_path": "", "extras": {}} - def __getstate__(self): - """ - Return state values to be pickled. - It's necessary to allow serialization in hyperopt - """ - return ({ - "pair_dict": self.pair_dict, - "pair_dictionary_path": self.pair_dictionary_path - }) - def load_drawer_from_disk(self): """ Locate and load a previously saved data drawer full of all pair model metadata in @@ -165,22 +155,14 @@ class FreqaiDataDrawer: # create a backup shutil.copy(self.historic_predictions_path, self.historic_predictions_bkp_path) - def save_drawer_to_disk(self, live=False): + def save_drawer_to_disk(self): """ Save data drawer full of all pair model metadata in present model folder. """ - if live: - with self.save_lock: - with open(self.pair_dictionary_path, 'w') as fp: - rapidjson.dump( - self.pair_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE) - else: - # save_lock it's not working with hyperopt + with self.save_lock: with open(self.pair_dictionary_path, 'w') as fp: - rapidjson.dump( - self.pair_dict, fp, default=self.np_encoder, - number_mode=rapidjson.NM_NATIVE) + rapidjson.dump(self.pair_dict, fp, default=self.np_encoder, + number_mode=rapidjson.NM_NATIVE) def save_follower_dict_to_disk(self): """ @@ -455,7 +437,7 @@ class FreqaiDataDrawer: self.model_dictionary[coin] = model self.pair_dict[coin]["model_filename"] = dk.model_filename self.pair_dict[coin]["data_path"] = str(dk.data_path) - self.save_drawer_to_disk(dk.live) + self.save_drawer_to_disk() return diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 9a05e8383..f631c9126 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -92,10 +92,9 @@ class IFreqaiModel(ABC): def __getstate__(self): """ - Return state values to be pickled. - It's necessary to allow serialization in hyperopt + Return an empty state to be pickled in hyperopt """ - return ({"dd": self.dd}) + return ({}) def assert_config(self, config: Dict[str, Any]) -> None: diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 0e822a028..78132b06d 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -6,9 +6,7 @@ import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.exchange import timeframe_to_prev_date -from freqtrade.persistence import Trade -from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, merge_informative_pair +from freqtrade.strategy import IStrategy, merge_informative_pair logger = logging.getLogger(__name__) @@ -47,11 +45,6 @@ class FreqaiExampleStrategy(IStrategy): startup_candle_count: int = 40 can_short = False - linear_roi_offset = DecimalParameter( - 0.00, 0.02, default=0.005, space="sell", optimize=False, load=True - ) - max_roi_time_long = IntParameter(0, 800, default=400, space="sell", optimize=False, load=True) - def informative_pairs(self): whitelist_pairs = self.dp.current_whitelist() corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"] @@ -226,83 +219,6 @@ class FreqaiExampleStrategy(IStrategy): def get_ticker_indicator(self): return int(self.config["timeframe"][:-1]) - def custom_exit( - self, pair: str, trade: Trade, current_time, current_rate, current_profit, **kwargs - ): - - dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) - - trade_date = timeframe_to_prev_date(self.config["timeframe"], trade.open_date_utc) - trade_candle = dataframe.loc[(dataframe["date"] == trade_date)] - - if trade_candle.empty: - return None - trade_candle = trade_candle.squeeze() - - follow_mode = self.config.get("freqai", {}).get("follow_mode", False) - - if not follow_mode: - pair_dict = self.freqai.dd.pair_dict - else: - pair_dict = self.freqai.dd.follower_dict - - entry_tag = trade.enter_tag - - if ( - "prediction" + entry_tag not in pair_dict[pair] - or pair_dict[pair]['extras']["prediction" + entry_tag] == 0 - ): - pair_dict[pair]['extras']["prediction" + entry_tag] = abs(trade_candle["&-s_close"]) - if not follow_mode: - self.freqai.dd.save_drawer_to_disk() - else: - self.freqai.dd.save_follower_dict_to_disk() - - roi_price = pair_dict[pair]['extras']["prediction" + entry_tag] - roi_time = self.max_roi_time_long.value - - roi_decay = roi_price * ( - 1 - ((current_time - trade.open_date_utc).seconds) / (roi_time * 60) - ) - if roi_decay < 0: - roi_decay = self.linear_roi_offset.value - else: - roi_decay += self.linear_roi_offset.value - - if current_profit > roi_decay: - return "roi_custom_win" - - if current_profit < -roi_decay: - return "roi_custom_loss" - - def confirm_trade_exit( - self, - pair: str, - trade: Trade, - order_type: str, - amount: float, - rate: float, - time_in_force: str, - exit_reason: str, - current_time, - **kwargs, - ) -> bool: - - entry_tag = trade.enter_tag - follow_mode = self.config.get("freqai", {}).get("follow_mode", False) - if not follow_mode: - pair_dict = self.freqai.dd.pair_dict - else: - pair_dict = self.freqai.dd.follower_dict - - pair_dict[pair]['extras']["prediction" + entry_tag] = 0 - if not follow_mode: - self.freqai.dd.save_drawer_to_disk() - else: - self.freqai.dd.save_follower_dict_to_disk() - - return True - def confirm_trade_entry( self, pair: str, From 5aba5de20f5dea1f61aa7ced19a966744b5c3e30 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Tue, 6 Sep 2022 16:17:10 -0300 Subject: [PATCH 04/23] fix link - hyperopt spaces --- docs/freqai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index fa58feb07..7aabb0e56 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -539,7 +539,7 @@ for each pair, for each backtesting window within the expanded `--timerange`. The [Hyperopt](hyperopt.md) module can be executed with some restrictions: - The `--analyze-per-epoch` hyperopt parameter is not compatible with FreqAI. -- It's not possible to hyperopt indicators in `populate_any_indicators()` function. This means that the user cannot optimize model parameters using hyperopt. Apart from this exception, it is possible to optimize all other [spaces](hyperopt.md###runninghyperoptwithsmallersearchspace). +- It's not possible to hyperopt indicators in `populate_any_indicators()` function. This means that the user cannot optimize model parameters using hyperopt. Apart from this exception, it is possible to optimize all other [spaces](hyperopt.md#running-hyperopt-with-smaller-search-space). - The [Backtesting](#backtesting) instructions also apply apply to Hyperopt. From 972b6991057d00ea1d321403cebf8e2feefa57ff Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Wed, 7 Sep 2022 11:11:31 -0300 Subject: [PATCH 05/23] hyperopt - freqai - change validation to config_validation --- freqtrade/configuration/config_validation.py | 9 +++++++++ freqtrade/configuration/configuration.py | 10 ---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index ee846e7e6..b37d44d0f 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -84,6 +84,7 @@ def validate_config_consistency(conf: Dict[str, Any], preliminary: bool = False) _validate_protections(conf) _validate_unlimited_amount(conf) _validate_ask_orderbook(conf) + _validate_freqai_hyperopt(conf) validate_migrated_strategy_settings(conf) # validate configuration before returning @@ -323,6 +324,14 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None: del conf['ask_strategy'] +def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None: + freqaimodel = conf.get('freqaimodel') + analyze_per_epoch = conf.get('analyze_per_epoch', False) + if analyze_per_epoch and freqaimodel is not None: + raise OperationalException( + 'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.') + + def _strategy_settings(conf: Dict[str, Any]) -> None: process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal') diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index d18b67ff2..7c68ac46c 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -285,7 +285,6 @@ class Configuration: logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"]) # Hyperopt section - self._check_hyperopt_analyze_per_epoch_freqai() self._args_to_config(config, argname='hyperopt', logstring='Using Hyperopt class name: {}') @@ -538,12 +537,3 @@ class Configuration: config['pairs'] = load_file(pairs_file) if 'pairs' in config and isinstance(config['pairs'], list): config['pairs'].sort() - - def _check_hyperopt_analyze_per_epoch_freqai(self) -> None: - """ - Helper for block hyperopt with analyze-per-epoch param. - """ - if ("analyze_per_epoch" in self.args and - self.args["analyze_per_epoch"] and "freqaimodel" in self.args): - raise OperationalException('analyze-per-epoch parameter is \ - not allowed with a Freqai strategy.') From 047ded1baa7c7e980b98c8289f689e2f900cf203 Mon Sep 17 00:00:00 2001 From: th0rntwig Date: Wed, 7 Sep 2022 17:47:27 +0200 Subject: [PATCH 06/23] Check for constant columns --- freqtrade/freqai/data_kitchen.py | 41 ++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 7670cfd45..0a2355c48 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -184,7 +184,7 @@ class FreqaiDataKitchen: def filter_features( self, - unfiltered_dataframe: DataFrame, + unfiltered_df: DataFrame, training_feature_list: List, label_list: List = list(), training_filter: bool = True, @@ -195,31 +195,36 @@ class FreqaiDataKitchen: 0s in the prediction dataset. However, prediction dataset do_predict will reflect any row that had a NaN and will shield user from that prediction. :params: - :unfiltered_dataframe: the full dataframe for the present training period + :unfiltered_df: the full dataframe for the present training period :training_feature_list: list, the training feature list constructed by self.build_feature_list() according to user specified parameters in the configuration file. :labels: the labels for the dataset :training_filter: boolean which lets the function know if it is training data or prediction data to be filtered. :returns: - :filtered_dataframe: dataframe cleaned of NaNs and only containing the user + :filtered_df: dataframe cleaned of NaNs and only containing the user requested feature set. :labels: labels cleaned of NaNs. """ - filtered_dataframe = unfiltered_dataframe.filter(training_feature_list, axis=1) - filtered_dataframe = filtered_dataframe.replace([np.inf, -np.inf], np.nan) + filtered_df = unfiltered_df.filter(training_feature_list, axis=1) + filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan) - drop_index = pd.isnull(filtered_dataframe).any(1) # get the rows that have NaNs, + const_cols = filtered_df[:, filtered_df.nunique(axis=0) == len(filtered_df.index)].columns + if const_cols: + filtered_df = filtered_df.filter(filtered_df.columns.difference(const_cols)) + logger.warning(f"Removed features {const_cols} with constant values.") + + drop_index = pd.isnull(filtered_df).any(1) # get the rows that have NaNs, drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement. if (training_filter): # we don't care about total row number (total no. datapoints) in training, we only care # about removing any row with NaNs # if labels has multiple columns (user wants to train multiple modelEs), we detect here - labels = unfiltered_dataframe.filter(label_list, axis=1) + labels = unfiltered_df.filter(label_list, axis=1) drop_index_labels = pd.isnull(labels).any(1) drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0) - dates = unfiltered_dataframe['date'] - filtered_dataframe = filtered_dataframe[ + dates = unfiltered_df['date'] + filtered_df = filtered_df[ (drop_index == 0) & (drop_index_labels == 0) ] # dropping values labels = labels[ @@ -229,13 +234,13 @@ class FreqaiDataKitchen: (drop_index == 0) & (drop_index_labels == 0) ] logger.info( - f"dropped {len(unfiltered_dataframe) - len(filtered_dataframe)} training points" - f" due to NaNs in populated dataset {len(unfiltered_dataframe)}." + f"dropped {len(unfiltered_df) - len(filtered_df)} training points" + f" due to NaNs in populated dataset {len(unfiltered_df)}." ) - if (1 - len(filtered_dataframe) / len(unfiltered_dataframe)) > 0.1 and self.live: - worst_indicator = str(unfiltered_dataframe.count().idxmin()) + if (1 - len(filtered_df) / len(unfiltered_df)) > 0.1 and self.live: + worst_indicator = str(unfiltered_df.count().idxmin()) logger.warning( - f" {(1 - len(filtered_dataframe)/len(unfiltered_dataframe)) * 100:.0f} percent " + f" {(1 - len(filtered_df)/len(unfiltered_df)) * 100:.0f} percent " " of training data dropped due to NaNs, model may perform inconsistent " f"with expectations. Verify {worst_indicator}" ) @@ -244,9 +249,9 @@ class FreqaiDataKitchen: else: # we are backtesting so we need to preserve row number to send back to strategy, # so now we use do_predict to avoid any prediction based on a NaN - drop_index = pd.isnull(filtered_dataframe).any(1) + drop_index = pd.isnull(filtered_df).any(1) self.data["filter_drop_index_prediction"] = drop_index - filtered_dataframe.fillna(0, inplace=True) + filtered_df.fillna(0, inplace=True) # replacing all NaNs with zeros to avoid issues in 'prediction', but any prediction # that was based on a single NaN is ultimately protected from buys with do_predict drop_index = ~drop_index @@ -255,11 +260,11 @@ class FreqaiDataKitchen: logger.info( "dropped %s of %s prediction data points due to NaNs.", len(self.do_predict) - self.do_predict.sum(), - len(filtered_dataframe), + len(filtered_df), ) labels = [] - return filtered_dataframe, labels + return filtered_df, labels def build_data_dictionary( self, From cdc72bf8cab4bf78b2da460690a3b71ab076bff4 Mon Sep 17 00:00:00 2001 From: th0rntwig Date: Wed, 7 Sep 2022 18:14:13 +0200 Subject: [PATCH 07/23] Correct indexing --- freqtrade/freqai/data_kitchen.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 0a2355c48..790b3c078 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -209,7 +209,7 @@ class FreqaiDataKitchen: filtered_df = unfiltered_df.filter(training_feature_list, axis=1) filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan) - const_cols = filtered_df[:, filtered_df.nunique(axis=0) == len(filtered_df.index)].columns + const_cols = list((filtered_df.nunique() == 1).loc[lambda x: x].index) if const_cols: filtered_df = filtered_df.filter(filtered_df.columns.difference(const_cols)) logger.warning(f"Removed features {const_cols} with constant values.") @@ -1205,7 +1205,6 @@ class FreqaiDataKitchen: def save_backtesting_prediction( self, append_df: DataFrame ) -> None: - """ Save prediction dataframe from backtesting to h5 file format :param append_df: dataframe for backtesting period @@ -1219,7 +1218,6 @@ class FreqaiDataKitchen: def get_backtesting_prediction( self ) -> DataFrame: - """ Get prediction dataframe from h5 file format """ From bf3ee51167f183739b0a71677126b9c18d733560 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Wed, 7 Sep 2022 14:42:05 -0300 Subject: [PATCH 08/23] validate freqai hyperopt with freqai enabled param --- freqtrade/configuration/config_validation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index b37d44d0f..8d9112bef 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -325,9 +325,9 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None: def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None: - freqaimodel = conf.get('freqaimodel') + freqai_enabled = conf.get('freqai', {}).get('enabled', False) analyze_per_epoch = conf.get('analyze_per_epoch', False) - if analyze_per_epoch and freqaimodel is not None: + if analyze_per_epoch and freqai_enabled: raise OperationalException( 'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.') From f4f2884a66c97c05e6871b1ecf2f12b1bc0ec460 Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Wed, 7 Sep 2022 18:52:58 -0300 Subject: [PATCH 09/23] Fix freqai backtesting time range issue --- freqtrade/freqai/data_kitchen.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 7670cfd45..01b2f1f3b 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -461,6 +461,20 @@ class FreqaiDataKitchen: return df + def remove_training_from_backtesting( + self + ) -> DataFrame: + """ + Function which takes the backtesting time range and + remove training data from dataframe + """ + tr = self.config["timerange"] + backtesting_timerange = TimeRange.parse_timerange(tr) + start = datetime.fromtimestamp(backtesting_timerange.startts, tz=timezone.utc) + df = self.return_dataframe + df = df.loc[df["date"] >= start, :] + return df + def principal_component_analysis(self) -> None: """ Performs Principal Component Analysis on the data for dimensionality reduction @@ -954,6 +968,7 @@ class FreqaiDataKitchen: to_keep = [col for col in dataframe.columns if not col.startswith("&")] self.return_dataframe = pd.concat([dataframe[to_keep], self.full_df], axis=1) + self.return_dataframe = self.remove_training_from_backtesting() self.full_df = DataFrame() return From bc7295579ff89387a402a6255ff5acfa9c583231 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Thu, 8 Sep 2022 22:22:50 +0200 Subject: [PATCH 10/23] improve docs, make example strat hyperoptable --- docs/freqai.md | 22 ++++++++++++-- freqtrade/templates/FreqaiExampleStrategy.py | 31 +++++++++++++------- 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/docs/freqai.md b/docs/freqai.md index 006fe1393..92e019dfe 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -538,12 +538,28 @@ for each pair, for each backtesting window within the expanded `--timerange`. ### Hyperopt -The [Hyperopt](hyperopt.md) module can be executed with some restrictions: +Users can hyperopt using the same command as typical [hyperopt](hyperopt.md): + +```bash +freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --strategy FreqaiExampleStrategy --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20220428-20220507 +``` + +Users need to have the data pre-downloaded in the same fashion as if they were doing a FreqAI [backtest](#backtesting). In addition, users must consider some restrictions when trying to [Hyperopt](hyperopt.md) FreqAI strategies: - The `--analyze-per-epoch` hyperopt parameter is not compatible with FreqAI. - It's not possible to hyperopt indicators in `populate_any_indicators()` function. This means that the user cannot optimize model parameters using hyperopt. Apart from this exception, it is possible to optimize all other [spaces](hyperopt.md#running-hyperopt-with-smaller-search-space). -- The [Backtesting](#backtesting) instructions also apply apply to Hyperopt. - +- The [Backtesting](#backtesting) instructions also apply to Hyperopt. + +The best method for combining hyperopt and FreqAI is to focus on hyperopting entry/exit thresholds/criteria. Users need to focus on hyperopting parameters that are not used in their FreqAI features. For example, users should not try to hyperopt rolling window lengths in their feature creation, or any of their FreqAI config which changes predictions. In order to efficiently hyperopt the FreqAI strategy, FreqAI stores predictions as dataframes and reuses them. Hence the requirement to hyperopt entry/exit thresholds/criteria only. + +A good example of a hyperoptable parameter in FreqAI is a value for `DI_values` beyond which we consider outliers and below which we consider inliers: + +```python +di_max = IntParameter(low=1, high=20, default=10, space='buy', optimize=True, load=True) +dataframe['outlier'] = np.where(dataframe['DI_values'] > self.di_max.value/10, 1, 0) +``` + +Which would help the user understand the appropriate Dissimilarity Index values for their particular parameter space. ### Deciding the size of the sliding training window and backtesting duration diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 78132b06d..8b8e03af7 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -6,7 +6,7 @@ import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.strategy import IStrategy, merge_informative_pair +from freqtrade.strategy import IStrategy, merge_informative_pair, CategoricalParameter logger = logging.getLogger(__name__) @@ -29,9 +29,6 @@ class FreqaiExampleStrategy(IStrategy): "main_plot": {}, "subplots": { "prediction": {"prediction": {"color": "blue"}}, - "target_roi": { - "target_roi": {"color": "brown"}, - }, "do_predict": { "do_predict": {"color": "brown"}, }, @@ -45,6 +42,11 @@ class FreqaiExampleStrategy(IStrategy): startup_candle_count: int = 40 can_short = False + std_dev_multiplier_buy = CategoricalParameter( + [0.75, 1, 1.25, 1.5, 1.75], default=1.25, space="buy", optimize=True) + std_dev_multiplier_sell = CategoricalParameter( + [0.1, 0.25, 0.4], space="sell", default=0.2, optimize=True) + def informative_pairs(self): whitelist_pairs = self.dp.current_whitelist() corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"] @@ -182,21 +184,26 @@ class FreqaiExampleStrategy(IStrategy): # `populate_any_indicators()` for each training period. dataframe = self.freqai.start(dataframe, metadata, self) - - dataframe["target_roi"] = dataframe["&-s_close_mean"] + dataframe["&-s_close_std"] * 1.25 - dataframe["sell_roi"] = dataframe["&-s_close_mean"] - dataframe["&-s_close_std"] * 1.25 + for val in self.std_dev_multiplier_buy.range: + dataframe[f'target_roi_{val}'] = dataframe["&-s_close_mean"] + \ + dataframe["&-s_close_std"] * val + for val in self.std_dev_multiplier_sell.range: + dataframe[f'sell_roi_{val}'] = dataframe["&-s_close_mean"] - \ + dataframe["&-s_close_std"] * val return dataframe def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - enter_long_conditions = [df["do_predict"] == 1, df["&-s_close"] > df["target_roi"]] + enter_long_conditions = [df["do_predict"] == 1, df["&-s_close"] + > df[f"target_roi_{self.std_dev_multiplier_buy.value}"]] if enter_long_conditions: df.loc[ reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"] ] = (1, "long") - enter_short_conditions = [df["do_predict"] == 1, df["&-s_close"] < df["sell_roi"]] + enter_short_conditions = [df["do_predict"] == 1, df["&-s_close"] + < df[f"sell_roi_{self.std_dev_multiplier_sell.value}"]] if enter_short_conditions: df.loc[ @@ -206,11 +213,13 @@ class FreqaiExampleStrategy(IStrategy): return df def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: - exit_long_conditions = [df["do_predict"] == 1, df["&-s_close"] < df["sell_roi"] * 0.25] + exit_long_conditions = [df["do_predict"] == 1, df["&-s_close"] < + df[f"sell_roi_{self.std_dev_multiplier_sell.value}"] * 0.25] if exit_long_conditions: df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1 - exit_short_conditions = [df["do_predict"] == 1, df["&-s_close"] > df["target_roi"] * 0.25] + exit_short_conditions = [df["do_predict"] == 1, df["&-s_close"] > + df[f"target_roi_{self.std_dev_multiplier_buy.value}"] * 0.25] if exit_short_conditions: df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1 From c5d918075881f62277f21854fdbd85fd94335024 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Thu, 8 Sep 2022 22:35:52 +0200 Subject: [PATCH 11/23] isort --- freqtrade/templates/FreqaiExampleStrategy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 8b8e03af7..991792eb9 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -6,7 +6,7 @@ import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.strategy import IStrategy, merge_informative_pair, CategoricalParameter +from freqtrade.strategy import CategoricalParameter, IStrategy, merge_informative_pair logger = logging.getLogger(__name__) From c13bec26d1b01a6173396dcf1f4daed9236eaad8 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 9 Sep 2022 19:41:28 +0200 Subject: [PATCH 12/23] add freqaimodel to hyperopt command --- docs/freqai.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freqai.md b/docs/freqai.md index 92e019dfe..09a51c713 100644 --- a/docs/freqai.md +++ b/docs/freqai.md @@ -541,7 +541,7 @@ for each pair, for each backtesting window within the expanded `--timerange`. Users can hyperopt using the same command as typical [hyperopt](hyperopt.md): ```bash -freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --strategy FreqaiExampleStrategy --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20220428-20220507 +freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20220428-20220507 ``` Users need to have the data pre-downloaded in the same fashion as if they were doing a FreqAI [backtest](#backtesting). In addition, users must consider some restrictions when trying to [Hyperopt](hyperopt.md) FreqAI strategies: From 37fcbeba580143179c9344021ba2b5ceea516902 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Sep 2022 19:58:55 +0200 Subject: [PATCH 13/23] Update backtesting output wording to Entries --- docs/backtesting.md | 74 ++++++++++++------------- freqtrade/optimize/optimize_reports.py | 5 +- tests/optimize/test_optimize_reports.py | 24 ++++---- 3 files changed, 52 insertions(+), 51 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 8b2fdc345..f20a53d22 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -107,7 +107,7 @@ Strategy arguments: ## Test your strategy with Backtesting -Now you have good Buy and Sell strategies and some historic data, you want to test it against +Now you have good Entry and exit strategies and some historic data, you want to test it against real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/` by default. @@ -215,7 +215,7 @@ Sometimes your account has certain fee rebates (fee reductions starting with a c To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting. This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit). -For example, if the buying and selling commission fee is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following: +For example, if the commission fee per order is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following: ```bash freqtrade backtesting --fee 0.001 @@ -252,41 +252,41 @@ The most important in the backtesting is to understand the result. A backtesting result will look like that: ``` -========================================================= BACKTESTING REPORT ========================================================== -| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | -|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:| -| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | -| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | -| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | -| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 | -| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 | -| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 | -| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 | -| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 | -| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 | -| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 | -| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 | -| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 | -| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 | -| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 | -| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 | -| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 | -| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | -| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | -| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | +========================================================= BACKTESTING REPORT ========================================================= +| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | +|:---------|--------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:| +| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | +| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | +| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | +| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 | +| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 | +| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 | +| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 | +| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 | +| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 | +| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 | +| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 | +| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 | +| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 | +| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 | +| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 | +| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 | +| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 | +| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | +| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | ========================================================= EXIT REASON STATS ========================================================== -| Exit Reason | Sells | Wins | Draws | Losses | +| Exit Reason | Exits | Wins | Draws | Losses | |:-------------------|--------:|------:|-------:|--------:| | trailing_stop_loss | 205 | 150 | 0 | 55 | | stop_loss | 166 | 0 | 0 | 166 | | exit_signal | 56 | 36 | 0 | 20 | | force_exit | 2 | 0 | 0 | 2 | ====================================================== LEFT OPEN TRADES REPORT ====================================================== -| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | -|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:| -| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | -| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | -| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | +| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | +|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:| +| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | +| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | +| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | ================== SUMMARY METRICS ================== | Metric | Value | |-----------------------------+---------------------| @@ -356,7 +356,7 @@ The column `Avg Profit %` shows the average profit for all trades made while the The column `Tot Profit %` shows instead the total profit % in relation to the starting balance. In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`. -Your strategy performance is influenced by your buy strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set. +Your strategy performance is influenced by your entry strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set. For example, if your `minimal_roi` is only `"0": 0.01` you cannot expect the bot to make more profit than 1% (because it will exit every time a trade reaches 1%). @@ -515,7 +515,7 @@ You can then load the trades to perform further analysis as shown in the [data a Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: - Exchange [trading limits](#trading-limits-in-backtesting) are respected -- Buys happen at open-price +- Entries happen at open-price - All orders are filled at the requested price (no slippage, no unfilled orders) - Exit-signal exits happen at open-price of the consecutive candle - Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open @@ -612,11 +612,11 @@ There will be an additional table comparing win/losses of the different strategi Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy. ``` -=========================================================== STRATEGY SUMMARY ========================================================================= -| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | -|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:| -| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | -| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | +=========================================================== STRATEGY SUMMARY =========================================================================== +| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | +|:------------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:| +| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | +| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | ``` ## Next step diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 519022db2..fa6c3f161 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -75,7 +75,8 @@ def _get_line_floatfmt(stake_currency: str) -> List[str]: '.2f', 'd', 's', 's'] -def _get_line_header(first_column: str, stake_currency: str, direction: str = 'Buys') -> List[str]: +def _get_line_header(first_column: str, stake_currency: str, + direction: str = 'Entries') -> List[str]: """ Generate header lines (goes in line with _generate_result_line()) """ @@ -642,7 +643,7 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr if (tag_type == "enter_tag"): headers = _get_line_header("TAG", stake_currency) else: - headers = _get_line_header("TAG", stake_currency, 'Sells') + headers = _get_line_header("TAG", stake_currency, 'Exits') floatfmt = _get_line_floatfmt(stake_currency) output = [ [ diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 562e12820..5095f2fde 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -40,14 +40,14 @@ def test_text_table_bt_results(): ) result_str = ( - '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |' - ' Avg Duration | Win Draw Loss Win% |\n' - '|---------+--------+----------------+----------------+------------------+----------------+' - '----------------+-------------------------|\n' - '| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |' - ' 0:20:00 | 2 0 1 66.7 |\n' - '| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |' - ' 0:20:00 | 2 0 1 66.7 |' + '| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | ' + 'Tot Profit % | Avg Duration | Win Draw Loss Win% |\n' + '|---------+-----------+----------------+----------------+------------------+' + '----------------+----------------+-------------------------|\n' + '| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | ' + '12.50 | 0:20:00 | 2 0 1 66.7 |\n' + '| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | ' + '12.50 | 0:20:00 | 2 0 1 66.7 |' ) pair_results = generate_pair_metrics(['ETH/BTC'], stake_currency='BTC', @@ -402,13 +402,13 @@ def test_text_table_strategy(testdatadir): bt_res_data_comparison = bt_res_data.pop('strategy_comparison') result_str = ( - '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |' + '| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC |' ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n' - '|----------------+--------+----------------+----------------+------------------+' + '|----------------+-----------+----------------+----------------+------------------+' '----------------+----------------+-------------------------+-----------------------|\n' - '| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |' + '| StrategyTestV2 | 179 | 0.08 | 14.39 | 0.02608550 |' ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |\n' - '| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |' + '| TestStrategy | 179 | 0.08 | 14.39 | 0.02608550 |' ' 260.85 | 3:40:00 | 170 0 9 95.0 | 0.00308222 BTC 8.67% |' ) From 97be3318f447445dfaab52f8b4d08ba2f8e59112 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Sep 2022 20:17:34 +0200 Subject: [PATCH 14/23] Use Datetime_format from constants --- freqtrade/data/history/history_utils.py | 8 ++++---- freqtrade/freqai/freqai_interface.py | 5 +++-- tests/data/test_history.py | 6 +++--- tests/test_persistence.py | 10 +++++----- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 7a3fa4e0c..6a6e29429 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -228,9 +228,9 @@ def _download_pair_history(pair: str, *, ) logger.debug("Current Start: %s", - f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') + f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None') logger.debug("Current End: %s", - f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') + f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None') # Default since_ms to 30 days if nothing is given new_data = exchange.get_historic_ohlcv(pair=pair, @@ -254,9 +254,9 @@ def _download_pair_history(pair: str, *, fill_missing=False, drop_incomplete=False) logger.debug("New Start: %s", - f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') + f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None') logger.debug("New End: %s", - f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') + f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None') data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type) return True diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index c5ac17a3a..7a48f6e15 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -14,6 +14,7 @@ from numpy.typing import NDArray from pandas import DataFrame from freqtrade.configuration import TimeRange +from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_seconds @@ -232,10 +233,10 @@ class IFreqaiModel(ABC): trained_timestamp = tr_train tr_train_startts_str = datetime.fromtimestamp( tr_train.startts, - tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) tr_train_stopts_str = datetime.fromtimestamp( tr_train.stopts, - tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT) logger.info( f"Training {metadata['pair']}, {self.pair_it}/{self.total_pairs} pairs" f" from {tr_train_startts_str} to {tr_train_stopts_str}, {train_it}/{total_trains} " diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 9709e7ad0..8081e984f 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -13,7 +13,7 @@ from pandas import DataFrame from pandas.testing import assert_frame_equal from freqtrade.configuration import TimeRange -from freqtrade.constants import AVAILABLE_DATAHANDLERS +from freqtrade.constants import AVAILABLE_DATAHANDLERS, DATETIME_PRINT_FORMAT from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.data.history.hdf5datahandler import HDF5DataHandler from freqtrade.data.history.history_utils import (_download_pair_history, _download_trades_history, @@ -386,7 +386,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: assert td != len(data['UNITTEST/BTC']) start_real = data['UNITTEST/BTC'].iloc[0, 0] assert log_has(f'UNITTEST/BTC, spot, 5m, ' - f'data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', + f'data starts at {start_real.strftime(DATETIME_PRINT_FORMAT)}', caplog) # Make sure we start fresh - test missing data at end caplog.clear() @@ -401,7 +401,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None: # Shift endtime with +5 - as last candle is dropped (partial candle) end_real = arrow.get(data['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) assert log_has(f'UNITTEST/BTC, spot, 5m, ' - f'data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', + f'data ends at {end_real.strftime(DATETIME_PRINT_FORMAT)}', caplog) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index cdca3bc4d..e7f218c02 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -9,7 +9,7 @@ import arrow import pytest from sqlalchemy import create_engine, text -from freqtrade import constants +from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_DB_PROD_URL from freqtrade.enums import TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.persistence import LocalTrade, Order, Trade, init_db @@ -52,7 +52,7 @@ def test_init_invalid_db_url(): def test_init_prod_db(default_conf, mocker): default_conf.update({'dry_run': False}) - default_conf.update({'db_url': constants.DEFAULT_DB_PROD_URL}) + default_conf.update({'db_url': DEFAULT_DB_PROD_URL}) create_engine_mock = mocker.patch('freqtrade.persistence.models.create_engine', MagicMock()) @@ -1739,7 +1739,7 @@ def test_to_json(fee): 'base_currency': 'ADA', 'quote_currency': 'USDT', 'is_open': None, - 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), + 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), 'open_timestamp': int(trade.open_date.timestamp() * 1000), 'open_order_id': 'dry_run_buy_12345', 'close_date': None, @@ -1817,9 +1817,9 @@ def test_to_json(fee): 'pair': 'XRP/BTC', 'base_currency': 'XRP', 'quote_currency': 'BTC', - 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), + 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT), 'open_timestamp': int(trade.open_date.timestamp() * 1000), - 'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"), + 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT), 'close_timestamp': int(trade.close_date.timestamp() * 1000), 'open_rate': 0.123, 'close_rate': 0.125, From 939fb7acb338202dfa2bf945ee51d95e37e14e97 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 9 Sep 2022 20:31:30 +0200 Subject: [PATCH 15/23] Fix enum imports --- freqtrade/commands/db_commands.py | 2 +- freqtrade/exchange/okx.py | 3 +-- freqtrade/rpc/discord.py | 2 +- freqtrade/strategy/interface.py | 5 ++--- freqtrade/strategy/parameters.py | 2 +- tests/exchange/test_okx.py | 3 +-- tests/strategy/test_interface.py | 3 +-- 7 files changed, 8 insertions(+), 12 deletions(-) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index 618b5cb6e..c424016b1 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -4,7 +4,7 @@ from typing import Any, Dict from sqlalchemy import func from freqtrade.configuration.config_setup import setup_utils_configuration -from freqtrade.enums.runmode import RunMode +from freqtrade.enums import RunMode logger = logging.getLogger(__name__) diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 9340dd0e4..49f8ea107 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -4,8 +4,7 @@ from typing import Dict, List, Optional, Tuple import ccxt from freqtrade.constants import BuySell -from freqtrade.enums import MarginMode, TradingMode -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange.common import retrier diff --git a/freqtrade/rpc/discord.py b/freqtrade/rpc/discord.py index 5991f7126..85acfae4e 100644 --- a/freqtrade/rpc/discord.py +++ b/freqtrade/rpc/discord.py @@ -1,7 +1,7 @@ import logging from typing import Any, Dict -from freqtrade.enums.rpcmessagetype import RPCMessageType +from freqtrade.enums import RPCMessageType from freqtrade.rpc import RPC from freqtrade.rpc.webhook import Webhook diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 70cc7fdb3..9401ebebe 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -12,9 +12,8 @@ from pandas import DataFrame from freqtrade.constants import ListPairsWithTimeframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, SignalDirection, SignalTagType, - SignalType, TradingMode) -from freqtrade.enums.runmode import RunMode +from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, RunMode, SignalDirection, + SignalTagType, SignalType, TradingMode) from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date, timeframe_to_seconds from freqtrade.persistence import Order, PairLocks, Trade diff --git a/freqtrade/strategy/parameters.py b/freqtrade/strategy/parameters.py index c6037ae0b..796fb9514 100644 --- a/freqtrade/strategy/parameters.py +++ b/freqtrade/strategy/parameters.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod from contextlib import suppress from typing import Any, Optional, Sequence, Union -from freqtrade.enums.hyperoptstate import HyperoptState +from freqtrade.enums import HyperoptState from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index b475b84ff..12322acae 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -4,8 +4,7 @@ from unittest.mock import MagicMock, PropertyMock import pytest -from freqtrade.enums import MarginMode, TradingMode -from freqtrade.enums.candletype import CandleType +from freqtrade.enums import CandleType, MarginMode, TradingMode from freqtrade.exchange.exchange import timeframe_to_minutes from tests.conftest import get_mock_coro, get_patched_exchange, log_has from tests.exchange.test_exchange import ccxt_exceptionhandlers diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 65ee05d71..070e78b1d 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -11,8 +11,7 @@ from pandas import DataFrame from freqtrade.configuration import TimeRange from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import load_data -from freqtrade.enums import ExitCheckTuple, ExitType, SignalDirection -from freqtrade.enums.hyperoptstate import HyperoptState +from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection from freqtrade.exceptions import OperationalException, StrategyError from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer from freqtrade.optimize.space import SKDecimal From 05581db4e387ae8c1b2ae7d097e3b20f349e4b16 Mon Sep 17 00:00:00 2001 From: robcaulk Date: Fri, 9 Sep 2022 20:37:21 +0200 Subject: [PATCH 16/23] ensure columns are only dropped in training --- freqtrade/freqai/data_kitchen.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 790b3c078..1af01cb6b 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -209,14 +209,13 @@ class FreqaiDataKitchen: filtered_df = unfiltered_df.filter(training_feature_list, axis=1) filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan) - const_cols = list((filtered_df.nunique() == 1).loc[lambda x: x].index) - if const_cols: - filtered_df = filtered_df.filter(filtered_df.columns.difference(const_cols)) - logger.warning(f"Removed features {const_cols} with constant values.") - drop_index = pd.isnull(filtered_df).any(1) # get the rows that have NaNs, drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement. if (training_filter): + const_cols = list((filtered_df.nunique() == 1).loc[lambda x: x].index) + if const_cols: + filtered_df = filtered_df.filter(filtered_df.columns.difference(const_cols)) + logger.warning(f"Removed features {const_cols} with constant values.") # we don't care about total row number (total no. datapoints) in training, we only care # about removing any row with NaNs # if labels has multiple columns (user wants to train multiple modelEs), we detect here From 79985fda015c0f060d0b0aca7d83962c43c75e8e Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Sat, 10 Sep 2022 10:27:17 -0300 Subject: [PATCH 17/23] fix backtesting freqai startup candles bug --- freqtrade/freqai/data_kitchen.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 01b2f1f3b..88d841b9e 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -468,8 +468,14 @@ class FreqaiDataKitchen: Function which takes the backtesting time range and remove training data from dataframe """ + startup_candle_count = self.config.get('startup_candle_count', 0) + tf = self.config['timeframe'] tr = self.config["timerange"] + backtesting_timerange = TimeRange.parse_timerange(tr) + if startup_candle_count > 0 and backtesting_timerange: + backtesting_timerange.subtract_start(timeframe_to_seconds(tf) * startup_candle_count) + start = datetime.fromtimestamp(backtesting_timerange.startts, tz=timezone.utc) df = self.return_dataframe df = df.loc[df["date"] >= start, :] From daf352e6a5dd660e247a38a1a209ff34561ba7a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Sep 2022 16:01:06 +0200 Subject: [PATCH 18/23] Test online candle fetching in futures, too disable gateio checking on spot markets --- tests/exchange/test_ccxt_compat.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 49b7684f8..f57b0b366 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -267,13 +267,8 @@ class TestCCXTExchange(): now = datetime.now(timezone.utc) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exchange.klines(pair_tf).iloc[-1]['date'] >= timeframe_to_prev_date(timeframe, now) - def test_ccxt__async_get_candle_history(self, exchange): - exchange, exchangename = exchange - # For some weired reason, this test returns random lengths for bittrex. - if not exchange._ft_has['ohlcv_has_history'] or exchangename == 'bittrex': - return - pair = EXCHANGES[exchangename]['pair'] - timeframe = EXCHANGES[exchangename]['timeframe'] + def ccxt__async_get_candle_history(self, exchange, exchangename, pair, timeframe): + candle_type = CandleType.SPOT timeframe_ms = timeframe_to_msecs(timeframe) now = timeframe_to_prev_date( @@ -299,6 +294,24 @@ class TestCCXTExchange(): assert len(candles) >= min(candle_count, candle_count1) assert candles[0][0] == since_ms or (since_ms + timeframe_ms) + def test_ccxt__async_get_candle_history(self, exchange): + exchange, exchangename = exchange + # For some weired reason, this test returns random lengths for bittrex. + if not exchange._ft_has['ohlcv_has_history'] or exchangename in ('bittrex', 'gateio'): + return + pair = EXCHANGES[exchangename]['pair'] + timeframe = EXCHANGES[exchangename]['timeframe'] + self.ccxt__async_get_candle_history(exchange, exchangename, pair, timeframe) + + def test_ccxt__async_get_candle_history_futures(self, exchange_futures): + exchange, exchangename = exchange_futures + if not exchange: + # exchange_futures only returns values for supported exchanges + return + pair = EXCHANGES[exchangename].get('futures_pair', EXCHANGES[exchangename]['pair']) + timeframe = EXCHANGES[exchangename]['timeframe'] + self.ccxt__async_get_candle_history(exchange, exchangename, pair, timeframe) + def test_ccxt_fetch_funding_rate_history(self, exchange_futures): exchange, exchangename = exchange_futures if not exchange: From 311ae8bf1f14e4cc80d6e5ec036c942d9af716bf Mon Sep 17 00:00:00 2001 From: Wagner Costa Santos Date: Sat, 10 Sep 2022 14:45:42 -0300 Subject: [PATCH 19/23] freqai backtesting - add startup_candle_count at function description --- freqtrade/freqai/data_kitchen.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 88d841b9e..9168db2aa 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -466,7 +466,8 @@ class FreqaiDataKitchen: ) -> DataFrame: """ Function which takes the backtesting time range and - remove training data from dataframe + remove training data from dataframe, keeping only the + startup_candle_count candles """ startup_candle_count = self.config.get('startup_candle_count', 0) tf = self.config['timeframe'] From f97f1dc5c38bd7e3c0b731404040a2a8e56a20e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Sep 2022 19:57:21 +0200 Subject: [PATCH 20/23] Test CatboostRegressorMultiTarget, simplify test setup via parametrization --- tests/freqai/test_freqai_interface.py | 44 ++++++--------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 5f8eeb086..115ad0ea8 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -46,10 +46,18 @@ def test_extract_data_and_train_model_LightGBM(mocker, freqai_conf): shutil.rmtree(Path(freqai.dk.full_path)) -def test_extract_data_and_train_model_LightGBMMultiModel(mocker, freqai_conf): +@pytest.mark.parametrize('model', [ + 'LightGBMRegressorMultiTarget', + 'XGBoostRegressorMultiTarget', + 'CatboostRegressorMultiTarget', + ]) +def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model): + if is_arm() and model == 'CatboostRegressorMultiTarget': + pytest.skip("CatBoost is not supported on ARM") + freqai_conf.update({"timerange": "20180110-20180130"}) freqai_conf.update({"strategy": "freqai_test_multimodel_strat"}) - freqai_conf.update({"freqaimodel": "LightGBMRegressorMultiTarget"}) + freqai_conf.update({"freqaimodel": model}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) strategy.dp = DataProvider(freqai_conf, exchange) @@ -205,38 +213,6 @@ def test_extract_data_and_train_model_XGBoostRegressor(mocker, freqai_conf): shutil.rmtree(Path(freqai.dk.full_path)) -def test_extract_data_and_train_model_XGBoostRegressorMultiModel(mocker, freqai_conf): - freqai_conf.update({"timerange": "20180110-20180130"}) - freqai_conf.update({"freqaimodel": "XGBoostRegressorMultiTarget"}) - freqai_conf.update({"strategy": "freqai_test_multimodel_strat"}) - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - freqai.dd.pair_dict = MagicMock() - - data_load_timerange = TimeRange.parse_timerange("20180110-20180130") - new_timerange = TimeRange.parse_timerange("20180120-20180130") - - freqai.extract_data_and_train_model( - new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange) - - assert len(freqai.dk.label_list) == 2 - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").is_file() - assert len(freqai.dk.data['training_features_list']) == 26 - - shutil.rmtree(Path(freqai.dk.full_path)) - - def test_start_backtesting(mocker, freqai_conf): freqai_conf.update({"timerange": "20180120-20180130"}) freqai_conf.get("freqai", {}).update({"save_backtest_models": True}) From 88892ba663ea8805e06ecd2d101c8982f595ad5f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Sep 2022 20:06:52 +0200 Subject: [PATCH 21/23] Parametrize regressor tests --- tests/freqai/test_freqai_interface.py | 76 ++++----------------------- 1 file changed, 11 insertions(+), 65 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index 115ad0ea8..bd921c16b 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -17,8 +17,18 @@ def is_arm() -> bool: return "arm" in machine or "aarch64" in machine -def test_extract_data_and_train_model_LightGBM(mocker, freqai_conf): +@pytest.mark.parametrize('model', [ + 'LightGBMRegressor', + 'XGBoostRegressor', + 'CatboostRegressor', + ]) +def test_extract_data_and_train_model_Regressors(mocker, freqai_conf, model): + if is_arm() and model == 'CatboostRegressor': + pytest.skip("CatBoost is not supported on ARM") + + freqai_conf.update({"freqaimodel": model}) freqai_conf.update({"timerange": "20180110-20180130"}) + freqai_conf.update({"strategy": "freqai_test_strat"}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) @@ -86,39 +96,6 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model): shutil.rmtree(Path(freqai.dk.full_path)) -@pytest.mark.skipif(is_arm(), reason="no ARM for Catboost ...") -def test_extract_data_and_train_model_Catboost(mocker, freqai_conf): - freqai_conf.update({"timerange": "20180110-20180130"}) - freqai_conf.update({"freqaimodel": "CatboostRegressor"}) - # freqai_conf.get('freqai', {}).update( - # {'model_training_parameters': {"n_estimators": 100, "verbose": 0}}) - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - freqai.dd.pair_dict = MagicMock() - - data_load_timerange = TimeRange.parse_timerange("20180110-20180130") - new_timerange = TimeRange.parse_timerange("20180120-20180130") - - freqai.extract_data_and_train_model(new_timerange, "ADA/BTC", - strategy, freqai.dk, data_load_timerange) - - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists() - - shutil.rmtree(Path(freqai.dk.full_path)) - - @pytest.mark.skipif(is_arm(), reason="no ARM for Catboost ...") def test_extract_data_and_train_model_CatboostClassifier(mocker, freqai_conf): freqai_conf.update({"timerange": "20180110-20180130"}) @@ -182,37 +159,6 @@ def test_extract_data_and_train_model_LightGBMClassifier(mocker, freqai_conf): shutil.rmtree(Path(freqai.dk.full_path)) -def test_extract_data_and_train_model_XGBoostRegressor(mocker, freqai_conf): - freqai_conf.update({"timerange": "20180110-20180130"}) - freqai_conf.update({"freqaimodel": "XGBoostRegressor"}) - freqai_conf.update({"strategy": "freqai_test_strat"}) - - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - freqai.dd.pair_dict = MagicMock() - - data_load_timerange = TimeRange.parse_timerange("20180110-20180130") - new_timerange = TimeRange.parse_timerange("20180120-20180130") - - freqai.extract_data_and_train_model( - new_timerange, "ADA/BTC", strategy, freqai.dk, data_load_timerange) - - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").is_file() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").is_file() - - shutil.rmtree(Path(freqai.dk.full_path)) - - def test_start_backtesting(mocker, freqai_conf): freqai_conf.update({"timerange": "20180120-20180130"}) freqai_conf.get("freqai", {}).update({"save_backtest_models": True}) From b3fc1cfde94b9b8e67e79d1f35f4ce3f36a4e5e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 Sep 2022 20:17:57 +0200 Subject: [PATCH 22/23] Parametrize classifier tests --- tests/freqai/test_freqai_interface.py | 43 ++++++--------------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/tests/freqai/test_freqai_interface.py b/tests/freqai/test_freqai_interface.py index bd921c16b..2a7cfeb73 100644 --- a/tests/freqai/test_freqai_interface.py +++ b/tests/freqai/test_freqai_interface.py @@ -96,42 +96,17 @@ def test_extract_data_and_train_model_MultiTargets(mocker, freqai_conf, model): shutil.rmtree(Path(freqai.dk.full_path)) -@pytest.mark.skipif(is_arm(), reason="no ARM for Catboost ...") -def test_extract_data_and_train_model_CatboostClassifier(mocker, freqai_conf): - freqai_conf.update({"timerange": "20180110-20180130"}) - freqai_conf.update({"freqaimodel": "CatboostClassifier"}) +@pytest.mark.parametrize('model', [ + 'LightGBMClassifier', + 'CatboostClassifier', + ]) +def test_extract_data_and_train_model_Classifiers(mocker, freqai_conf, model): + if is_arm() and model == 'CatboostClassifier': + pytest.skip("CatBoost is not supported on ARM") + + freqai_conf.update({"freqaimodel": model}) freqai_conf.update({"strategy": "freqai_test_classifier"}) - strategy = get_patched_freqai_strategy(mocker, freqai_conf) - exchange = get_patched_exchange(mocker, freqai_conf) - strategy.dp = DataProvider(freqai_conf, exchange) - - strategy.freqai_info = freqai_conf.get("freqai", {}) - freqai = strategy.freqai - freqai.live = True - freqai.dk = FreqaiDataKitchen(freqai_conf) - timerange = TimeRange.parse_timerange("20180110-20180130") - freqai.dd.load_all_pair_histories(timerange, freqai.dk) - - freqai.dd.pair_dict = MagicMock() - - data_load_timerange = TimeRange.parse_timerange("20180110-20180130") - new_timerange = TimeRange.parse_timerange("20180120-20180130") - - freqai.extract_data_and_train_model(new_timerange, "ADA/BTC", - strategy, freqai.dk, data_load_timerange) - - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_model.joblib").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_metadata.json").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_trained_df.pkl").exists() - assert Path(freqai.dk.data_path / f"{freqai.dk.model_filename}_svm_model.joblib").exists() - - shutil.rmtree(Path(freqai.dk.full_path)) - - -def test_extract_data_and_train_model_LightGBMClassifier(mocker, freqai_conf): freqai_conf.update({"timerange": "20180110-20180130"}) - freqai_conf.update({"freqaimodel": "LightGBMClassifier"}) - freqai_conf.update({"strategy": "freqai_test_classifier"}) strategy = get_patched_freqai_strategy(mocker, freqai_conf) exchange = get_patched_exchange(mocker, freqai_conf) strategy.dp = DataProvider(freqai_conf, exchange) From 5a0cfee27e8de330d00168b6de86a2ac84e0ed7f Mon Sep 17 00:00:00 2001 From: robcaulk Date: Sat, 10 Sep 2022 22:16:49 +0200 Subject: [PATCH 23/23] allow user to multithread jobs (advanced users only) --- .../freqai/base_models/FreqaiMultiOutputRegressor.py | 10 ---------- .../prediction_models/CatboostRegressorMultiTarget.py | 3 +++ .../prediction_models/LightGBMRegressorMultiTarget.py | 6 +++--- .../prediction_models/XGBoostRegressorMultiTarget.py | 3 +++ 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/freqtrade/freqai/base_models/FreqaiMultiOutputRegressor.py b/freqtrade/freqai/base_models/FreqaiMultiOutputRegressor.py index aa5dbe629..a9db81e31 100644 --- a/freqtrade/freqai/base_models/FreqaiMultiOutputRegressor.py +++ b/freqtrade/freqai/base_models/FreqaiMultiOutputRegressor.py @@ -36,9 +36,6 @@ class FreqaiMultiOutputRegressor(MultiOutputRegressor): y = self._validate_data(X="no_validation", y=y, multi_output=True) - # if is_classifier(self): - # check_classification_targets(y) - if y.ndim == 1: raise ValueError( "y must have at least two dimensions for " @@ -50,19 +47,12 @@ class FreqaiMultiOutputRegressor(MultiOutputRegressor): ): raise ValueError("Underlying estimator does not support sample weights.") - # fit_params_validated = _check_fit_params(X, fit_params) - if not fit_params: fit_params = [None] * y.shape[1] - # if not init_models: - # init_models = [None] * y.shape[1] - self.estimators_ = Parallel(n_jobs=self.n_jobs)( delayed(_fit_estimator)( self.estimator, X, y[:, i], sample_weight, **fit_params[i] - # init_model=init_models[i], eval_set=eval_sets[i], - # **fit_params_validated ) for i in range(y.shape[1]) ) diff --git a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py index a376b2c33..7fa4e293e 100644 --- a/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/CatboostRegressorMultiTarget.py @@ -60,6 +60,9 @@ class CatboostRegressorMultiTarget(BaseRegressionModel): {'eval_set': eval_sets[i], 'init_model': init_models[i]}) model = FreqaiMultiOutputRegressor(estimator=cbr) + thread_training = self.freqai_info.get('multitarget_parallel_training', False) + if thread_training: + model.n_jobs = y.shape[1] model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params) return model diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py index 7a9b5c36a..37c6bb186 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py @@ -56,9 +56,9 @@ class LightGBMRegressorMultiTarget(BaseRegressionModel): 'init_model': init_models[i]}) model = FreqaiMultiOutputRegressor(estimator=lgb) + thread_training = self.freqai_info.get('multitarget_parallel_training', False) + if thread_training: + model.n_jobs = y.shape[1] model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params) - # model = FreqaiMultiOutputRegressor(estimator=lgb) - # model.fit(X=X, y=y, sample_weight=sample_weight, init_models=init_models, - # eval_sets=eval_sets, eval_sample_weight=eval_weights) return model diff --git a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py index 38c478c0b..920745ec9 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/XGBoostRegressorMultiTarget.py @@ -55,6 +55,9 @@ class XGBoostRegressorMultiTarget(BaseRegressionModel): 'xgb_model': init_models[i]}) model = FreqaiMultiOutputRegressor(estimator=xgb) + thread_training = self.freqai_info.get('multitarget_parallel_training', False) + if thread_training: + model.n_jobs = y.shape[1] model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params) return model