Merge pull request #7363 from wagnercosta/fixHyperoptFreqai
Fix hyperopt - freqai
This commit is contained in:
commit
2077f84f9b
@ -281,6 +281,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`).
|
||||||
|
|
||||||
@ -534,6 +536,31 @@ for each pair, for each backtesting window within the expanded `--timerange`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Hyperopt
|
||||||
|
|
||||||
|
Users can hyperopt using the same command as typical [hyperopt](hyperopt.md):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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:
|
||||||
|
|
||||||
|
- 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 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
|
### 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
|
||||||
|
@ -84,6 +84,7 @@ def validate_config_consistency(conf: Dict[str, Any], preliminary: bool = False)
|
|||||||
_validate_protections(conf)
|
_validate_protections(conf)
|
||||||
_validate_unlimited_amount(conf)
|
_validate_unlimited_amount(conf)
|
||||||
_validate_ask_orderbook(conf)
|
_validate_ask_orderbook(conf)
|
||||||
|
_validate_freqai_hyperopt(conf)
|
||||||
validate_migrated_strategy_settings(conf)
|
validate_migrated_strategy_settings(conf)
|
||||||
|
|
||||||
# validate configuration before returning
|
# validate configuration before returning
|
||||||
@ -323,6 +324,14 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
|||||||
del conf['ask_strategy']
|
del conf['ask_strategy']
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
|
||||||
|
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
|
||||||
|
analyze_per_epoch = conf.get('analyze_per_epoch', False)
|
||||||
|
if analyze_per_epoch and freqai_enabled:
|
||||||
|
raise OperationalException(
|
||||||
|
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
|
||||||
|
|
||||||
|
|
||||||
def _strategy_settings(conf: Dict[str, Any]) -> None:
|
def _strategy_settings(conf: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal')
|
process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal')
|
||||||
|
@ -92,6 +92,12 @@ class IFreqaiModel(ABC):
|
|||||||
self._threads: List[threading.Thread] = []
|
self._threads: List[threading.Thread] = []
|
||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
"""
|
||||||
|
Return an empty state to be pickled in hyperopt
|
||||||
|
"""
|
||||||
|
return ({})
|
||||||
|
|
||||||
def assert_config(self, config: Dict[str, Any]) -> None:
|
def assert_config(self, config: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
if not config.get("freqai", {}):
|
if not config.get("freqai", {}):
|
||||||
|
@ -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 CategoricalParameter, 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__)
|
||||||
@ -31,9 +29,6 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
"main_plot": {},
|
"main_plot": {},
|
||||||
"subplots": {
|
"subplots": {
|
||||||
"prediction": {"prediction": {"color": "blue"}},
|
"prediction": {"prediction": {"color": "blue"}},
|
||||||
"target_roi": {
|
|
||||||
"target_roi": {"color": "brown"},
|
|
||||||
},
|
|
||||||
"do_predict": {
|
"do_predict": {
|
||||||
"do_predict": {"color": "brown"},
|
"do_predict": {"color": "brown"},
|
||||||
},
|
},
|
||||||
@ -47,10 +42,10 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
startup_candle_count: int = 40
|
startup_candle_count: int = 40
|
||||||
can_short = False
|
can_short = False
|
||||||
|
|
||||||
linear_roi_offset = DecimalParameter(
|
std_dev_multiplier_buy = CategoricalParameter(
|
||||||
0.00, 0.02, default=0.005, space="sell", optimize=False, load=True
|
[0.75, 1, 1.25, 1.5, 1.75], default=1.25, space="buy", optimize=True)
|
||||||
)
|
std_dev_multiplier_sell = CategoricalParameter(
|
||||||
max_roi_time_long = IntParameter(0, 800, default=400, space="sell", optimize=False, load=True)
|
[0.1, 0.25, 0.4], space="sell", default=0.2, optimize=True)
|
||||||
|
|
||||||
def informative_pairs(self):
|
def informative_pairs(self):
|
||||||
whitelist_pairs = self.dp.current_whitelist()
|
whitelist_pairs = self.dp.current_whitelist()
|
||||||
@ -187,21 +182,26 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
# `populate_any_indicators()` for each training period.
|
# `populate_any_indicators()` for each training period.
|
||||||
|
|
||||||
dataframe = self.freqai.start(dataframe, metadata, self)
|
dataframe = self.freqai.start(dataframe, metadata, self)
|
||||||
|
for val in self.std_dev_multiplier_buy.range:
|
||||||
dataframe["target_roi"] = dataframe["&-s_close_mean"] + dataframe["&-s_close_std"] * 1.25
|
dataframe[f'target_roi_{val}'] = dataframe["&-s_close_mean"] + \
|
||||||
dataframe["sell_roi"] = dataframe["&-s_close_mean"] - dataframe["&-s_close_std"] * 1.25
|
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
|
return dataframe
|
||||||
|
|
||||||
def populate_entry_trend(self, df: DataFrame, metadata: dict) -> 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:
|
if enter_long_conditions:
|
||||||
df.loc[
|
df.loc[
|
||||||
reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"]
|
reduce(lambda x, y: x & y, enter_long_conditions), ["enter_long", "enter_tag"]
|
||||||
] = (1, "long")
|
] = (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:
|
if enter_short_conditions:
|
||||||
df.loc[
|
df.loc[
|
||||||
@ -211,11 +211,13 @@ class FreqaiExampleStrategy(IStrategy):
|
|||||||
return df
|
return df
|
||||||
|
|
||||||
def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame:
|
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:
|
if exit_long_conditions:
|
||||||
df.loc[reduce(lambda x, y: x & y, exit_long_conditions), "exit_long"] = 1
|
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:
|
if exit_short_conditions:
|
||||||
df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1
|
df.loc[reduce(lambda x, y: x & y, exit_short_conditions), "exit_short"] = 1
|
||||||
|
|
||||||
@ -224,83 +226,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,
|
||||||
|
Loading…
Reference in New Issue
Block a user