hyperopt - freqai - docs and refactoring

This commit is contained in:
Wagner Costa Santos 2022-09-06 15:42:47 -03:00
parent 1820bc6832
commit 8d16dd804d
5 changed files with 29 additions and 111 deletions

View File

@ -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`. 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` ### 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`). 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 ### Deciding the size of the sliding training window and backtesting duration
The user defines the backtesting timerange with the typical `--timerange` parameter in the The user defines the backtesting timerange with the typical `--timerange` parameter in the

View File

@ -285,6 +285,7 @@ class Configuration:
logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"]) logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"])
# Hyperopt section # Hyperopt section
self._check_hyperopt_analyze_per_epoch_freqai()
self._args_to_config(config, argname='hyperopt', self._args_to_config(config, argname='hyperopt',
logstring='Using Hyperopt class name: {}') logstring='Using Hyperopt class name: {}')
@ -537,3 +538,12 @@ class Configuration:
config['pairs'] = load_file(pairs_file) config['pairs'] = load_file(pairs_file)
if 'pairs' in config and isinstance(config['pairs'], list): if 'pairs' in config and isinstance(config['pairs'], list):
config['pairs'].sort() 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.')

View File

@ -93,16 +93,6 @@ class FreqaiDataDrawer:
"model_filename": "", "trained_timestamp": 0, "model_filename": "", "trained_timestamp": 0,
"priority": 1, "first": True, "data_path": "", "extras": {}} "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): def load_drawer_from_disk(self):
""" """
Locate and load a previously saved data drawer full of all pair model metadata in Locate and load a previously saved data drawer full of all pair model metadata in
@ -165,21 +155,13 @@ class FreqaiDataDrawer:
# create a backup # create a backup
shutil.copy(self.historic_predictions_path, self.historic_predictions_bkp_path) 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. Save data drawer full of all pair model metadata in present model folder.
""" """
if live:
with self.save_lock: with self.save_lock:
with open(self.pair_dictionary_path, 'w') as fp: with open(self.pair_dictionary_path, 'w') as fp:
rapidjson.dump( rapidjson.dump(self.pair_dict, fp, default=self.np_encoder,
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) number_mode=rapidjson.NM_NATIVE)
def save_follower_dict_to_disk(self): def save_follower_dict_to_disk(self):
@ -455,7 +437,7 @@ class FreqaiDataDrawer:
self.model_dictionary[coin] = model self.model_dictionary[coin] = model
self.pair_dict[coin]["model_filename"] = dk.model_filename self.pair_dict[coin]["model_filename"] = dk.model_filename
self.pair_dict[coin]["data_path"] = str(dk.data_path) self.pair_dict[coin]["data_path"] = str(dk.data_path)
self.save_drawer_to_disk(dk.live) self.save_drawer_to_disk()
return return

View File

@ -92,10 +92,9 @@ class IFreqaiModel(ABC):
def __getstate__(self): def __getstate__(self):
""" """
Return state values to be pickled. Return an empty state to be pickled in hyperopt
It's necessary to allow serialization in hyperopt
""" """
return ({"dd": self.dd}) return ({})
def assert_config(self, config: Dict[str, Any]) -> None: def assert_config(self, config: Dict[str, Any]) -> None:

View File

@ -6,9 +6,7 @@ import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
from technical import qtpylib from technical import qtpylib
from freqtrade.exchange import timeframe_to_prev_date from freqtrade.strategy import IStrategy, merge_informative_pair
from freqtrade.persistence import Trade
from freqtrade.strategy import DecimalParameter, IntParameter, IStrategy, merge_informative_pair
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -47,11 +45,6 @@ class FreqaiExampleStrategy(IStrategy):
startup_candle_count: int = 40 startup_candle_count: int = 40
can_short = False 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): def informative_pairs(self):
whitelist_pairs = self.dp.current_whitelist() whitelist_pairs = self.dp.current_whitelist()
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"] corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
@ -226,83 +219,6 @@ class FreqaiExampleStrategy(IStrategy):
def get_ticker_indicator(self): def get_ticker_indicator(self):
return int(self.config["timeframe"][:-1]) 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( def confirm_trade_entry(
self, self,
pair: str, pair: str,