diff --git a/freqtrade/templates/freqaiprimer.json b/freqtrade/templates/freqaiprimer.json index 5036dc8b..dbbaf721 100644 --- a/freqtrade/templates/freqaiprimer.json +++ b/freqtrade/templates/freqaiprimer.json @@ -22,14 +22,8 @@ "TRAILING_STOP_DISTANCE": 0.015, "TRAILING_STOP_START": 0.016 }, - "protection": {}, - "trailing": { - "trailing_stop": true, - "trailing_stop_positive": 0.106, - "trailing_stop_positive_offset": 0.196, - "trailing_only_offset_is_reached": false - } + "protection": {} }, "ft_stratparam_v": 1, "export_time": "2025-07-01 14:51:29.420394+00:00" -} \ No newline at end of file +} diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index aefc89e2..1afa2713 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -19,9 +19,6 @@ class FreqaiPrimer(IStrategy): 基于 FreqAI 的动态阈值交易策略,集成动态加仓、减仓和自定义 ROI 逻辑,兼容最新 Freqtrade 版本 """ - # --- 🧪 Hyperopt Parameters --- - TRAILING_STOP_START = DecimalParameter(0.01, 0.05, default=0.03, space='sell', optimize=True) - TRAILING_STOP_DISTANCE = DecimalParameter(0.005, 0.02, default=0.01, space='sell', optimize=True) BUY_THRESHOLD_MIN = DecimalParameter(-0.15, -0.03, default=-0.07, space='buy', optimize=True) BUY_THRESHOLD_MAX = DecimalParameter(-0.04, -0.005, default=-0.01, space='buy', optimize=True) @@ -190,6 +187,11 @@ class FreqaiPrimer(IStrategy): dataframe["volume_mean_20"] = dataframe["volume"].rolling(20).mean() dataframe["volume_std_20"] = dataframe["volume"].rolling(20).std() dataframe["volume_z_score"] = (dataframe["volume"] - dataframe["volume_mean_20"]) / dataframe["volume_std_20"] + dataframe_15m = self.dp.get_pair_dataframe(pair=pair, timeframe="15m") + dataframe_15m['atr15m'] = ta.ATR(dataframe_15m, timeperiod=14) + dataframe['atr15m'] = dataframe_15m['atr15m'].reindex(dataframe.index, method="ffill") + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema20'] = ta.EMA(dataframe, timeperiod=20) # 数据清理 for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", "&-price_value_divergence", "price_value_divergence"]: @@ -349,25 +351,35 @@ class FreqaiPrimer(IStrategy): def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: pair = metadata.get('pair', 'Unknown') - conditions = [] + + # 初始化所有条件为 False + cond1 = cond2 = cond3 = False if "&-price_value_divergence" in dataframe.columns: - cond1 = (dataframe["&-price_value_divergence"] > self.sell_threshold * 1.025) - cond2 = (dataframe["rsi"] > 75) - sell_condition = cond1 | cond2 - conditions.append(sell_condition) + cond1 = (dataframe["&-price_value_divergence"] > self.sell_threshold * 1.05) - logger.debug(f"[{pair}] 卖出条件检查 - " - f"&-price_value_divergence > {self.sell_threshold:.6f}: {cond1.iloc[-1]}, " - f"rsi > 75: {cond2.iloc[-1]}") - else: - logger.warning(f"[{pair}] ⚠️ &-price_value_divergence 列缺失,跳过该条件") + if "rsi" in dataframe.columns: + cond2 = (dataframe["rsi"] > 70) - if len(conditions) > 0: - dataframe.loc[reduce(lambda x, y: x & y, conditions), 'exit_long'] = 1 - logger.debug(f"[{pair}] 出场信号触发,条件满足") - else: - logger.debug(f"[{pair}] 无有效卖出条件") + if "ema10" in dataframe.columns and "ema20" in dataframe.columns: + cond3 = (dataframe['ema10'] < dataframe['ema20']) + + # 组合卖出信号 + sell_condition = cond1 | cond2 | cond3 + if sell_condition.any(): + dataframe.loc[sell_condition, 'exit_long'] = 1 + + # 获取最后一根K线的实际指标值(用于日志) + price_divergence = dataframe["&-price_value_divergence"].iloc[-1] if "&-price_value_divergence" in dataframe.columns else np.nan + rsi_value = dataframe["rsi"].iloc[-1] if "rsi" in dataframe.columns else np.nan + ema10_value = dataframe["ema10"].iloc[-1] if "ema10" in dataframe.columns else np.nan + ema20_value = dataframe["ema20"].iloc[-1] if "ema20" in dataframe.columns else np.nan + + # 构建详细日志信息 + logger.debug(f"[{pair}] 卖出条件检查 - " + f"&-price_value_divergence={price_divergence:.6f} > {self.sell_threshold * 1.05:.6f}: {cond1}, " + f"rsi={rsi_value:.2f} > 70: {cond2}, " + f"ema10={ema10_value:.6f} < ema20={ema20_value:.6f}: {cond3}") return dataframe @@ -463,8 +475,14 @@ class FreqaiPrimer(IStrategy): return (reduce_amount, f"Profit {profit_ratio*100:.2f}%") # 追踪止损逻辑 - trailing_stop_start = self.TRAILING_STOP_START.value - trailing_stop_distance = self.TRAILING_STOP_DISTANCE.value + + atr15m = dataframe['atr15m'].iloc[-1] + trailing_stop_distance = 2 * atr15m / current_rate # 2倍ATR,转换为比例 + trailing_stop_price = max_rate * (1 - trailing_stop_distance) + if current_rate < trailing_stop_price: + logger.info(f"{pair} 价格回落至 {trailing_stop_price:.6f},触发全部卖出") + return (-trade.stake_amount, f"ATR trailing stop at {trailing_stop_price:.6f}") + # 使用 Sigmoid 映射调整追踪止损参数 sigmoid = 1 / (1 + np.exp(-0.1 * (trend_score - 50))) trailing_factor = 0.8 + (1.2 - 0.8) * sigmoid # 牛市(100) -> 1.2, 熊市(0) -> 0.8 diff --git a/tools/hyperopt.sh b/tools/hyperopt.sh index 3ae7ef3f..af7846be 100755 --- a/tools/hyperopt.sh +++ b/tools/hyperopt.sh @@ -65,7 +65,7 @@ echo "docker-compose run --rm freqtrade hyperopt \ --timerange ${START_DATE}-${END_DATE} \ -e 100 \ --hyperopt-loss ShortTradeDurHyperOptLoss \ - --spaces buy sell trailing \ + --spaces buy sell \ --fee 0.0016" docker-compose run --rm freqtrade hyperopt \ --logfile /freqtrade/user_data/logs/freqtrade.log \ @@ -77,7 +77,7 @@ docker-compose run --rm freqtrade hyperopt \ --timerange ${START_DATE}-${END_DATE} \ -e 100 \ --hyperopt-loss SharpeHyperOptLoss \ - --spaces buy sell trailing \ + --spaces buy sell \ --fee 0.0016 #>output.log 2>&1