From 2bbf5209bbc1e42232f76dda04d780c046660866 Mon Sep 17 00:00:00 2001 From: "zhangkun9038@dingtalk.com" Date: Tue, 19 Aug 2025 02:11:34 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A2=E5=8A=A8=E7=8E=87=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=89=B9=E5=BE=81freqai=E6=96=B0=E5=9B=9E=E5=BD=92=E5=B1=9E?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- freqtrade/templates/freqaiprimer.json | 33 ++++++++++++++++ freqtrade/templates/freqaiprimer.py | 56 ++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/freqtrade/templates/freqaiprimer.json b/freqtrade/templates/freqaiprimer.json index 7ce199e..cdeaed2 100644 --- a/freqtrade/templates/freqaiprimer.json +++ b/freqtrade/templates/freqaiprimer.json @@ -23,6 +23,39 @@ "TRAILING_STOP_START": 0.045 }, "protection": {}, + "model_training_parameters": { + "price_value_divergence": { + "model": "LightGBMRegressor", + "model_params": { + "n_estimators": 200, + "learning_rate": 0.05, + "num_leaves": 31, + "verbose": -1 + } + }, + "optimal_first_length": { + "model": "LightGBMClassifier", + "model_params": { + "n_estimators": 150, + "learning_rate": 0.1, + "num_leaves": 15, + "max_depth": 8, + "min_child_samples": 10, + "class_weight": "balanced", + "verbose": -1 + } + }, + "volatility_forecast": { + "model": "LightGBMRegressor", + "model_params": { + "n_estimators": 180, + "learning_rate": 0.08, + "num_leaves": 25, + "max_depth": 6, + "verbose": -1 + } + } + }, "trailing": { "trailing_stop": true, "trailing_stop_positive": 0.0125, diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index a3fd0b9..a8d5da5 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -162,6 +162,16 @@ class FreqaiPrimer(IStrategy): "class_weight": "balanced", "verbose": -1, } + }, + "volatility_forecast": { # 新增:波动率预测模型 + "model": "LightGBMRegressor", + "model_params": { + "n_estimators": 180, + "learning_rate": 0.08, + "num_leaves": 25, + "max_depth": 6, + "verbose": -1, + } } }, "fit_live_predictions_candles": 100, @@ -225,11 +235,18 @@ class FreqaiPrimer(IStrategy): dataframe["ema200"] = ta.EMA(dataframe, timeperiod=200) dataframe["%-price_value_divergence"] = (dataframe["close"] - dataframe["ema200"]) / dataframe["ema200"] + + # 新增:波动率相关特征 + dataframe["%-atr-period"] = ta.ATR(dataframe, timeperiod=period) + dataframe["%-volatility_ratio"] = dataframe["%-atr-period"] / dataframe["close"] + dataframe["%-price_range"] = (dataframe["high"] - dataframe["low"]) / dataframe["close"] + dataframe["%-volume_volatility"] = dataframe["volume"].pct_change().rolling(period).std() columns_to_clean = [ "%-rsi-period", "%-mfi-period", "%-sma-period", "%-ema-period", "%-adx-period", "bb_lowerband-period", "bb_middleband-period", "bb_upperband-period", - "%-bb_width-period", "%-relative_volume-period", "%-price_value_divergence" + "%-bb_width-period", "%-relative_volume-period", "%-price_value_divergence", + "%-atr-period", "%-volatility_ratio", "%-price_range", "%-volume_volatility" ] for col in columns_to_clean: dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0) @@ -256,10 +273,21 @@ class FreqaiPrimer(IStrategy): price_divergence = dataframe["&-price_value_divergence"].replace([np.inf, -np.inf], 0).ffill().fillna(0) dataframe["&-price_value_divergence"] = price_divergence.astype(np.float64) + # 新增:波动率预测目标 + # 计算未来12根K线的真实波动率 + future_high = dataframe['high'].shift(-12).rolling(12).max() + future_low = dataframe['low'].shift(-12).rolling(12).min() + dataframe["&-volatility_forecast"] = ((future_high - future_low) / dataframe['close']).replace([np.inf, -np.inf], 0).ffill().fillna(0) + # 验证数据类型和范围 if not np.isfinite(dataframe["&-price_value_divergence"]).all(): logger.warning(f"[{pair}] price_value_divergence包含非有限值,进行清理") dataframe["&-price_value_divergence"] = dataframe["&-price_value_divergence"].replace([np.inf, -np.inf, np.nan], 0.0) + + # 清理波动率预测值 + if not np.isfinite(dataframe["&-volatility_forecast"]).all(): + logger.warning(f"[{pair}] volatility_forecast包含非有限值,进行清理") + dataframe["&-volatility_forecast"] = dataframe["&-volatility_forecast"].replace([np.inf, -np.inf, np.nan], 0.0) # 新增:分类模型优化 first_length # 基于市场状态预测最优的 first_length 分类(2, 4, 6, 8, 10) @@ -600,6 +628,22 @@ class FreqaiPrimer(IStrategy): logger.info("==============================================") self.stats_logged = True + # 新增:使用波动率预测结果进行风险调整 + if "&-volatility_forecast" in dataframe.columns: + # 计算波动率因子,用于仓位调整 + dataframe["volatility_factor"] = 1 / (1 + dataframe["&-volatility_forecast"] * 10) + + # 计算动态止损 + dataframe["dynamic_stop_loss"] = self.stoploss * (1 + dataframe["&-volatility_forecast"] * 2) + + # 波动率阈值判断 + high_volatility = dataframe["&-volatility_forecast"] > 0.05 # 5%以上认为是高波动 + low_volatility = dataframe["&-volatility_forecast"] < 0.02 # 2%以下认为是低波动 + + logger.info(f"[{pair}] 波动率分析 - 当前: {dataframe['&-volatility_forecast'].iloc[-1]:.4f}, " + f"因子: {dataframe['volatility_factor'].iloc[-1]:.2f}, " + f"动态止损: {dataframe['dynamic_stop_loss'].iloc[-1]:.4f}") + return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: @@ -622,6 +666,16 @@ class FreqaiPrimer(IStrategy): stochrsi_min = 25 stochrsi_max = 45 stochrsi_threshold = self.linear_map(trend_score, 0, 100, stochrsi_max, stochrsi_min) + + # 新增:基于波动率预测的动态调整 + volatility_factor = 1.0 + if "volatility_factor" in dataframe.columns: + volatility_factor = dataframe["volatility_factor"].iloc[-1] if len(dataframe) > 0 else 1.0 + + # 根据波动率调整所有阈值(高波动时更严格) + volume_z_score_threshold *= volatility_factor + rsi_threshold *= volatility_factor + stochrsi_threshold *= volatility_factor # 计算熊市信号和 STOCHRSI 超买信号 bearish_signal = self.is_bearish_market(dataframe, metadata, timeframe="1h")