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,