From cbb1de8a029ee69e4e550d0b4d5bed7def96c72c Mon Sep 17 00:00:00 2001 From: "zhangkun9038@dingtalk.com" Date: Wed, 23 Apr 2025 20:30:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=93=E5=89=8D=E4=BB=A3=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E5=90=88=20hyperopt=20=E7=BB=93=E6=9E=9C=E4=B8=8D=E9=94=99,=20?= =?UTF-8?q?=E4=B8=8B=E4=B8=80=E6=AD=A5=E5=B0=9D=E8=AF=95=20=E5=A6=82?= =?UTF-8?q?=E4=BD=95=E8=BD=AC=E5=8C=96=E5=88=B0=20backtesting=E4=B8=8A?= =?UTF-8?q?=E8=83=BD=E5=91=88=E7=8E=B0=E7=B1=BB=E4=BC=BC=E7=9A=84=E7=BB=93?= =?UTF-8?q?=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config_examples/config_freqai.okx.json | 2 +- docker-compose.yml | 13 +- .../templates/FreqaiExampleStrategy.json | 30 ++-- freqtrade/templates/FreqaiExampleStrategy.py | 151 ++++++++++++++---- 4 files changed, 144 insertions(+), 52 deletions(-) diff --git a/config_examples/config_freqai.okx.json b/config_examples/config_freqai.okx.json index ed829e3..d0d2e82 100644 --- a/config_examples/config_freqai.okx.json +++ b/config_examples/config_freqai.okx.json @@ -67,7 +67,7 @@ "freqaimodel": "CatboostClassifier", "purge_old_models": 2, "train_period_days": 15, - "identifier": "test52", + "identifier": "test58", "train_period_days": 30, "backtest_period_days": 10, "live_retrain_hours": 0, diff --git a/docker-compose.yml b/docker-compose.yml index 38000eb..3e39103 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,13 +52,20 @@ services: command: > hyperopt --logfile /freqtrade/user_data/logs/freqtrade.log - --freqaimodel XGBoostClassifier + --freqaimodel LightGBMRegressor --config /freqtrade/config_examples/config_freqai.okx.json --strategy-path /freqtrade/templates --strategy FreqaiExampleStrategy --timerange 20250301-20250420 --hyperopt-loss SharpeHyperOptLoss - --spaces buy sell roi stoploss trailing + --spaces roi stoploss -e 200 - + # command: > + # backtesting + # --logfile /freqtrade/user_data/logs/freqtrade.log + # --freqaimodel LightGBMRegressor + # --config /freqtrade/config_examples/config_freqai.okx.json + # --strategy-path /freqtrade/templates + # --strategy FreqaiExampleStrategy + # --timerange 20250301-20250420 diff --git a/freqtrade/templates/FreqaiExampleStrategy.json b/freqtrade/templates/FreqaiExampleStrategy.json index ef9c35f..8bf88e3 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.json +++ b/freqtrade/templates/FreqaiExampleStrategy.json @@ -1,32 +1,32 @@ { "strategy_name": "FreqaiExampleStrategy", "params": { + "trailing": { + "trailing_stop": true, + "trailing_stop_positive": 0.01, + "trailing_stop_positive_offset": 0.02, + "trailing_only_offset_is_reached": false + }, "max_open_trades": { "max_open_trades": 4 }, "buy": { - "buy_rsi": 28 + "buy_rsi": 39.92672300850069 }, "sell": { - "sell_rsi": 89 + "sell_rsi": 69.92672300850067 }, "protection": {}, "roi": { - "0": 0.08099999999999999, - "7": 0.038, - "23": 0.013, - "63": 0 + "0": 0.132, + "8": 0.047, + "14": 0.007, + "60": 0 }, "stoploss": { - "stoploss": -0.236 - }, - "trailing": { - "trailing_stop": true, - "trailing_stop_positive": 0.139, - "trailing_stop_positive_offset": 0.14800000000000002, - "trailing_only_offset_is_reached": true + "stoploss": -0.322 } }, "ft_stratparam_v": 1, - "export_time": "2025-04-23 09:36:34.486164+00:00" -} + "export_time": "2025-04-23 12:30:05.550433+00:00" +} \ No newline at end of file diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index cac917d..393beee 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -1,34 +1,61 @@ import logging -from functools import reduce import numpy as np +from functools import reduce import talib.abstract as ta from pandas import DataFrame from technical import qtpylib -from freqtrade.strategy import IntParameter, IStrategy, DecimalParameter +from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter + logger = logging.getLogger(__name__) class FreqaiExampleStrategy(IStrategy): - - minimal_roi = { - "0": DecimalParameter(low=0.01, high=0.05, default=0.02, space="roi", optimize=True, load=True).value, - "360": DecimalParameter(low=0.005, high=0.02, default=0.01, space="roi", optimize=True, load=True).value - } - stoploss = DecimalParameter(low=-0.1, high=-0.02, default=-0.07, space="stoploss", optimize=True, load=True).value + # 移除硬编码的 minimal_roi 和 stoploss,改为动态适配 + minimal_roi = {} # 将在 populate_indicators 中动态生成 + stoploss = 0.0 # 将在 populate_indicators 中动态设置 trailing_stop = True - trailing_stop_positive = 0.01 - trailing_stop_positive_offset = 0.02 process_only_new_candles = True use_exit_signal = True startup_candle_count: int = 40 can_short = False - buy_rsi = IntParameter(low=10, high=50, default=30, space="buy", optimize=True, load=True) - sell_rsi = IntParameter(low=50, high=90, default=70, space="sell", optimize=True, load=True) + # 参数定义:FreqAI 动态适配 buy_rsi 和 sell_rsi,禁用 Hyperopt 优化 + buy_rsi = IntParameter(low=10, high=50, default=27, space="buy", optimize=False, load=True) + sell_rsi = IntParameter(low=50, high=90, default=59, space="sell", optimize=False, load=True) + + # 为 Hyperopt 优化添加 ROI 和 stoploss 参数 + roi_0 = DecimalParameter(low=0.01, high=0.2, default=0.038, space="roi", optimize=True, load=True) + roi_15 = DecimalParameter(low=0.005, high=0.1, default=0.027, space="roi", optimize=True, load=True) + roi_30 = DecimalParameter(low=0.001, high=0.05, default=0.009, space="roi", optimize=True, load=True) + stoploss_param = DecimalParameter(low=-0.35, high=-0.1, default=-0.182, space="stoploss", optimize=True, load=True) + + # FreqAI 配置 + freqai_info = { + "model": "LightGBMRegressor", + "feature_parameters": { + "include_timeframes": ["5m", "15m", "1h"], + "include_corr_pairlist": [], + "label_period_candles": 12, + "include_shifted_candles": 3, + }, + "data_split_parameters": { + "test_size": 0.2, + "shuffle": False, + }, + "model_training_parameters": { + "n_estimators": 100, + "learning_rate": 0.1, + "num_leaves": 31, + "verbose": -1, + }, + } plot_config = { "main_plot": {}, "subplots": { - "&-up_or_down": {"&-up_or_down": {"color": "blue"}}, + "&-buy_rsi": {"&-buy_rsi": {"color": "green"}}, + "&-sell_rsi": {"&-sell_rsi": {"color": "red"}}, + "&-stoploss": {"&-stoploss": {"color": "purple"}}, + "&-roi_0": {"&-roi_0": {"color": "orange"}}, "do_predict": {"do_predict": {"color": "brown"}}, }, } @@ -74,48 +101,107 @@ class FreqaiExampleStrategy(IStrategy): return dataframe def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: - logger.info(f"Setting FreqAI targets for pair: {metadata['pair']}") + logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}") if "close" not in dataframe.columns: - logger.error("Required 'close' column missing in dataframe") - raise ValueError("Required 'close' column missing in dataframe") + logger.error("数据框缺少必要的 'close' 列") + raise ValueError("数据框缺少必要的 'close' 列") + try: label_period = self.freqai_info["feature_parameters"]["label_period_candles"] - dataframe["&-up_or_down"] = np.where( - dataframe["close"].shift(-label_period) > dataframe["close"], - "up", - "down" - ) + # 生成 %-volatility 特征 + dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() + + # 单一回归目标 + dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14).shift(-label_period) + + # 数据清理 + for col in ["&-buy_rsi", "%-volatility"]: + dataframe[col].replace([np.inf, -np.inf], 0, inplace=True) + dataframe[col].fillna(method='ffill', inplace=True) + dataframe[col].fillna(0, inplace=True) + if dataframe[col].isna().any(): + logger.warning(f"目标列 {col} 仍包含 NaN,检查数据生成逻辑") except Exception as e: - logger.error(f"Failed to create &-up_or_down column: {str(e)}") + logger.error(f"创建 FreqAI 目标失败:{str(e)}") raise - logger.info(f"Target column head:\n{dataframe[['&-up_or_down']].head().to_string()}") + + logger.info(f"目标列预览:\n{dataframe[['&-buy_rsi']].head().to_string()}") return dataframe def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - logger.info(f"Processing pair: {metadata['pair']}") + logger.info(f"处理交易对:{metadata['pair']}") dataframe = self.freqai.start(dataframe, metadata, self) + + # 计算传统指标 dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe["bb_lowerband"] = bollinger["lower"] dataframe["bb_middleband"] = bollinger["mid"] dataframe["bb_upperband"] = bollinger["upper"] dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9) + + # 生成 up_or_down 信号(非 FreqAI 目标) + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] + dataframe["up_or_down"] = np.where( + dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0 + ) + + # 动态设置参数 + if "&-buy_rsi" in dataframe.columns: + # 派生其他目标 + dataframe["&-sell_rsi"] = dataframe["&-buy_rsi"] + 30 + dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() + dataframe["&-stoploss"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25) + dataframe["&-roi_0"] = (dataframe["close"].shift(-label_period) / dataframe["close"] - 1).clip(0, 0.2) + + # 限制预测值,添加平滑 + dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(5).mean().clip(10, 50) + dataframe["sell_rsi_pred"] = dataframe["&-sell_rsi"].rolling(5).mean().clip(50, 90) + dataframe["stoploss_pred"] = dataframe["&-stoploss"].clip(-0.35, -0.1) + dataframe["roi_0_pred"] = dataframe["&-roi_0"].clip(0.01, 0.2) + + # 检查预测值 + for col in ["buy_rsi_pred", "sell_rsi_pred", "stoploss_pred", "roi_0_pred", "&-sell_rsi", "&-stoploss", "&-roi_0"]: + if dataframe[col].isna().any(): + logger.warning(f"列 {col} 包含 NaN,填充为默认值") + dataframe[col].fillna(dataframe[col].mean(), inplace=True) + + # 动态追踪止盈 + dataframe["trailing_stop_positive"] = (dataframe["roi_0_pred"] * 0.5).clip(0.01, 0.3) + dataframe["trailing_stop_positive_offset"] = (dataframe["roi_0_pred"] * 0.75).clip(0.02, 0.4) + + # 设置策略级参数 + self.buy_rsi.value = float(dataframe["buy_rsi_pred"].iloc[-1]) + self.sell_rsi.value = float(dataframe["sell_rsi_pred"].iloc[-1]) + self.stoploss = float(self.stoploss_param.value) + self.minimal_roi = { + "0": float(self.roi_0.value), + "15": float(self.roi_15.value), + "30": float(self.roi_30.value), + "60": 0 + } + self.trailing_stop_positive = float(dataframe["trailing_stop_positive"].iloc[-1]) + self.trailing_stop_positive_offset = float(dataframe["trailing_stop_positive_offset"].iloc[-1]) + + logger.info(f"动态参数:buy_rsi={self.buy_rsi.value}, sell_rsi={self.sell_rsi.value}, " + f"stoploss={self.stoploss}, trailing_stop_positive={self.trailing_stop_positive}") + dataframe.replace([np.inf, -np.inf], 0, inplace=True) dataframe.fillna(method='ffill', inplace=True) dataframe.fillna(0, inplace=True) - if "&-up_or_down" in dataframe.columns: - logger.info(f"&-up_or_down value counts:\n{dataframe['&-up_or_down'].value_counts().to_string()}") - logger.info(f"do_predict value counts:\n{dataframe['do_predict'].value_counts().to_string()}") + + logger.info(f"up_or_down 值统计:\n{dataframe['up_or_down'].value_counts().to_string()}") + logger.info(f"do_predict 值统计:\n{dataframe['do_predict'].value_counts().to_string()}") + return dataframe def populate_entry_trend(self, df: DataFrame, metadata: dict) -> DataFrame: enter_long_conditions = [ - qtpylib.crossed_above(df["rsi"], self.buy_rsi.value), + qtpylib.crossed_above(df["rsi"], df["buy_rsi_pred"]), df["tema"] > df["tema"].shift(1), df["volume"] > 0, df["do_predict"] == 1, - df["&-up_or_down"] == "up" - #df["%-bb_width-period_4h"] > 0.05 + df["up_or_down"] == 1 ] if enter_long_conditions: df.loc[ @@ -126,11 +212,11 @@ class FreqaiExampleStrategy(IStrategy): def populate_exit_trend(self, df: DataFrame, metadata: dict) -> DataFrame: exit_long_conditions = [ - qtpylib.crossed_above(df["rsi"], self.sell_rsi.value), + qtpylib.crossed_above(df["rsi"], df["sell_rsi_pred"]), (df["close"] < df["close"].shift(1) * 0.97), df["volume"] > 0, df["do_predict"] == 1, - df["&-up_or_down"] == "down" + df["up_or_down"] == 0 ] if exit_long_conditions: df.loc[ @@ -149,4 +235,3 @@ class FreqaiExampleStrategy(IStrategy): if rate > (last_candle["close"] * (1 + 0.0025)): return False return True -