diff --git a/config_examples/config_freqai.okx.json b/config_examples/config_freqai.okx.json index 4535010f..41226b25 100644 --- a/config_examples/config_freqai.okx.json +++ b/config_examples/config_freqai.okx.json @@ -64,8 +64,12 @@ "data_kitchen": { "fillna": "ffill" }, - "freqaimodel": "CatboostClassifier", - "purge_old_models": 2, + "freqaimodel": "XGBoostRegressor", + "model_training_parameters": { + "n_estimators": 100, + "learning_rate": 0.05, + "max_depth": 5 + }, "train_period_days": 15, "train_period_days": 180, "backtest_period_days": 60, diff --git a/docker-compose.yml b/docker-compose.yml index defe81ee..aeb31e69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,7 +64,7 @@ services: command: > backtesting --logfile /freqtrade/user_data/logs/freqtrade.log - --freqaimodel LightGBMRegressor + --freqaimodel XGBoostRegressor --config /freqtrade/config_examples/config_freqai.okx.json --config /freqtrade/templates/FreqaiExampleStrategy.json --strategy-path /freqtrade/templates diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py index 3003cc64..e320c943 100644 --- a/freqtrade/templates/FreqaiExampleStrategy.py +++ b/freqtrade/templates/FreqaiExampleStrategy.py @@ -71,40 +71,50 @@ class FreqaiExampleStrategy(IStrategy): }, } - def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: +def feature_engineering_expand_all(self, dataframe: DataFrame, period: int, metadata: dict, **kwargs) -> DataFrame: + # 保留关键的技术指标 + dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) + macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) + dataframe["macd"] = macd["macd"] + dataframe["macdsignal"] = macd["macdsignal"] + + # 保留布林带相关特征 + 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["volume_ma"] = dataframe["volume"].rolling(window=20).mean() + + # 数据清理 + for col in dataframe.columns: + if dataframe[col].dtype in ["float64", "int64"]: + dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan) + dataframe[col] = dataframe[col].ffill().fillna(0) + + logger.info(f"特征工程完成,特征数量:{len(dataframe.columns)}") + return dataframe # 保留关键的技术指标 - dataframe["%-rsi"] = ta.RSI(dataframe, timeperiod=14) + dataframe["rsi"] = ta.RSI(dataframe, timeperiod=14) macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe["%-macd"] = macd["macd"] - dataframe["%-macdsignal"] = macd["macdsignal"] + dataframe["macd"] = macd["macd"] + dataframe["macdsignal"] = macd["macdsignal"] # 保留布林带相关特征 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["bb_width"] = (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) / dataframe["bb_middleband"] - dataframe["bb_pct"] = (dataframe["close"] - dataframe["bb_lowerband"]) / (dataframe["bb_upperband"] - dataframe["bb_lowerband"]) # 保留成交量相关特征 dataframe["volume_ma"] = dataframe["volume"].rolling(window=20).mean() - dataframe["volume_roc"] = dataframe["volume"].pct_change(periods=10) - # 保留价格变化率 - dataframe["close_roc"] = dataframe["close"].pct_change(periods=10) - - # 改进数据清理 + # 数据清理 for col in dataframe.columns: if dataframe[col].dtype in ["float64", "int64"]: - # 使用更稳健的归一化方法 - mean = dataframe[col].mean() - std = dataframe[col].std() - dataframe[col] = (dataframe[col] - mean) / std - dataframe[col] = dataframe[col].clip(-3, 3) # 限制在3个标准差内 - - dataframe.replace([np.inf, -np.inf], 0, inplace=True) - dataframe.ffill(inplace=True) - dataframe.fillna(0, inplace=True) + dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan) + dataframe[col] = dataframe[col].ffill().fillna(0) logger.info(f"特征工程完成,特征数量:{len(dataframe.columns)}") return dataframe @@ -134,7 +144,42 @@ class FreqaiExampleStrategy(IStrategy): dataframe.fillna(0, inplace=True) return dataframe - def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: +def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame: + logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}") + if "close" not in dataframe.columns: + logger.error("数据框缺少必要的 'close' 列") + raise ValueError("数据框缺少必要的 'close' 列") + + try: + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] + + # 确保目标变量是二维数组 + if dataframe["up_or_down"].ndim == 1: + dataframe["up_or_down"] = dataframe["up_or_down"].values.reshape(-1, 1) + + # 生成 %-volatility 特征 + dataframe["%-volatility"] = dataframe["close"].pct_change().rolling(20).std() + + # 确保 &-buy_rsi 列的值计算正确 + dataframe["&-buy_rsi"] = ta.RSI(dataframe, timeperiod=14) + + # 数据清理 + for col in ["&-buy_rsi", "up_or_down", "%-volatility"]: + # 使用直接操作避免链式赋值 + dataframe[col] = dataframe[col].replace([np.inf, -np.inf], np.nan) + dataframe[col] = dataframe[col].ffill() # 替代 fillna(method='ffill') + dataframe[col] = dataframe[col].fillna(dataframe[col].mean()) # 使用均值填充 NaN 值 + if dataframe[col].isna().any(): + logger.warning(f"目标列 {col} 仍包含 NaN,填充为默认值") + + except Exception as e: + logger.error(f"创建 FreqAI 目标失败:{str(e)}") + raise + + # Log the shape of the target variable for debugging + logger.info(f"目标列形状:{dataframe['up_or_down'].shape}") + logger.info(f"目标列预览:\n{dataframe[['up_or_down', '&-buy_rsi']].head().to_string()}") + return dataframe logger.info(f"设置 FreqAI 目标,交易对:{metadata['pair']}") if "close" not in dataframe.columns: logger.error("数据框缺少必要的 'close' 列") @@ -185,9 +230,10 @@ class FreqaiExampleStrategy(IStrategy): # 生成 up_or_down 信号(非 FreqAI 目标) label_period = self.freqai_info["feature_parameters"]["label_period_candles"] - # 使用历史数据生成 up_or_down 信号 + # 使用未来价格变化方向生成 up_or_down 信号 + label_period = self.freqai_info["feature_parameters"]["label_period_candles"] dataframe["up_or_down"] = np.where( - dataframe["close"] > dataframe["close"].shift(1), 1, 0 + dataframe["close"].shift(-label_period) > dataframe["close"], 1, 0 ) # 动态设置参数 @@ -208,12 +254,12 @@ class FreqaiExampleStrategy(IStrategy): # 简化动态参数生成逻辑 # 放松 buy_rsi 和 sell_rsi 的生成逻辑 # 计算 buy_rsi_pred 并清理 NaN 值 - dataframe["buy_rsi_pred"] = dataframe["&-buy_rsi"].rolling(window=10).mean().clip(30, 50) - dataframe["buy_rsi_pred"] = dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].mean()) + dataframe["buy_rsi_pred"] = dataframe["rsi"].rolling(window=10).mean().clip(30, 50) + dataframe["buy_rsi_pred"] = dataframe["buy_rsi_pred"].fillna(dataframe["buy_rsi_pred"].median()) # 计算 sell_rsi_pred 并清理 NaN 值 - dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 40 - dataframe["sell_rsi_pred"] = dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].mean()) + dataframe["sell_rsi_pred"] = dataframe["buy_rsi_pred"] + 20 + dataframe["sell_rsi_pred"] = dataframe["sell_rsi_pred"].fillna(dataframe["sell_rsi_pred"].median()) # 计算 stoploss_pred 并清理 NaN 值 dataframe["stoploss_pred"] = -0.1 - (dataframe["%-volatility"] * 10).clip(0, 0.25) @@ -279,6 +325,7 @@ class FreqaiExampleStrategy(IStrategy): (df["rsi"] < df["buy_rsi_pred"]), # RSI 低于买入阈值 (df["volume"] > df["volume"].rolling(window=10).mean() * 1.2), # 成交量高于近期均值20% (df["close"] > df["bb_middleband"]), # 价格高于布林带中轨 + (df["macd"] > df["macdsignal"]), # MACD 金叉 (df["do_predict"] == 1) # 确保模型预测为买入 ] if enter_long_conditions: