diff --git a/config_examples/freqaiprimer.json b/config_examples/freqaiprimer.json index 9404607e..946bc5ef 100644 --- a/config_examples/freqaiprimer.json +++ b/config_examples/freqaiprimer.json @@ -112,13 +112,7 @@ "main_plot": {}, "subplots": { "&-predictions": { - "&-exit_return": {"color": "red"} - }, - "&-risk": { - "&-max_drawdown": {"color": "orange"} - }, - "&-timing": { - "&-hold_duration": {"color": "blue"} + "&-entry_confidence": {"color": "green"} } } } diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 02c748fe..fe29007a 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -372,38 +372,19 @@ class FreqaiPrimer(IStrategy): def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame: """ - 设置 FreqAI 训练标签 (3个变量,简化版) - - &-exit_return: 出场回归标签 (未来收益率) - - &-max_drawdown: 最大回撤预测 (入场风险过滤) - - &-hold_duration: 最佳持仓时长 (0-1,出场ROI优化) + 设置 FreqAI 训练标签 (1个变量,简化版) + - &-entry_confidence: 入场置信度评分 (0-1),仅用于 confirm_trade_entry 最终确认 """ label_period = self.freqai_info["feature_parameters"]["label_period_candles"] - # ========== 1. 出场回归标签 ========== - # 直接使用未来收益率 + # ========== 入场置信度标签 ========== + # 基于未来收益率估算入场好坏,使用 S 型函数映射到 0-1 future_return = dataframe["close"].shift(-label_period) / dataframe["close"] - 1 - dataframe["&-exit_return"] = future_return - - # ========== 2. 最大回撤标签 (入场风险过滤) ========== - # 使用未来最低价计算最大回撤,使用 min_periods=1 避免过多 NaN - future_low = dataframe["low"].shift(-1).rolling(window=label_period, min_periods=1).min() - dataframe["&-max_drawdown"] = (future_low / dataframe["close"]) - 1 - - # ========== 3. 最佳持仓时长标签 (出场ROI优化) ========== - # 基于未来收益率估算持仓价值,归一化到 0-1 - # 收益率高 → hold_duration 高 (应该持仓久) - # 收益率低/负 → hold_duration 低 (应该尽快出场) - future_return_clipped = future_return.clip(-0.1, 0.1) # 限制在 -10% ~ +10% - dataframe["&-hold_duration"] = (future_return_clipped + 0.1) / 0.2 # 归一化到 0-1 + dataframe["&-entry_confidence"] = 1 / (1 + np.exp(-future_return * 50)) # ========== 统一处理 NaN,避免训练数据被全部过滤 ========== - # 所有标签列末尾会因为 shift(-label_period) 产生 NaN,用前向填充避免丢失样本 - label_columns = [ - "&-exit_return", "&-max_drawdown", "&-hold_duration" - ] - for col in label_columns: - if col in dataframe.columns: - dataframe[col] = dataframe[col].fillna(method='ffill').fillna(0) + if "&-entry_confidence" in dataframe.columns: + dataframe["&-entry_confidence"] = dataframe["&-entry_confidence"].fillna(method='ffill').fillna(0) return dataframe @@ -669,54 +650,11 @@ class FreqaiPrimer(IStrategy): # 合并所有条件 traditional_condition = breakout_condition | volume_spike | macd_downward | rsi_overbought - # ==================== FreqAI 出场信号融合 (市场自适应) ==================== - # 检查 FreqAI 预测结果是否可用 - if "&-exit_return" in dataframe.columns and "do_predict" in dataframe.columns: - # 为每一行根据市场状态计算自适应退出阈值,避免在 populate_ 中使用 iloc[-1] - if 'market_state' in dataframe.columns: - exit_th_series = dataframe['market_state'].apply( - lambda s: self._get_adaptive_thresholds(s)['exit_threshold'] - ) - else: - default_th = self._get_adaptive_thresholds('neutral')['exit_threshold'] - exit_th_series = np.full(len(dataframe), default_th) - - # 回归器预测: 未来收益率 - # 熊市时 exit_th 为负值(如-0.02),更容易触发出场 - # 牛市时 exit_th 为正值(如+0.02),需要更大下跌才出场 - ml_exit_signal = (dataframe["&-exit_return"] < exit_th_series) - - # 置信度过滤: do_predict >= 1 表示预测可信 - ml_confidence = (dataframe["do_predict"] >= 1) - - # 融合策略: 传统条件 OR (ML出场信号 AND ML置信度) - # 出场使用 OR 逻辑,更激进地保护利润 - final_condition = traditional_condition | (ml_exit_signal & ml_confidence) - - # 记录融合信息(只统计数量,不再依赖最后一行) - logger.info( - f"[{metadata['pair']}] FreqAI出场: " - f"传统={int(traditional_condition.sum())}, " - f"ML出场={int(ml_exit_signal.sum())}, " - f"最终={int(final_condition.sum())}" - ) - else: - # FreqAI 未启用,使用传统条件 - final_condition = traditional_condition - - # 设置出场信号 + # ==================== 出场信号:仅使用传统条件 ==================== + final_condition = traditional_condition + dataframe.loc[final_condition, 'exit_long'] = 1 - - # 增强调试信息 - #logger.info(f"[{metadata['pair']}] 出场条件检查:") - #logger.info(f" - 价格突破布林带上轨: {breakout_condition.sum()} 次") - #logger.info(f" - 成交量显著放大: {volume_spike.sum()} 次") - #logger.info(f" - MACD 下降趋势: {macd_downward.sum()} 次") - #logger.info(f" - RSI 超买: {rsi_overbought.sum()} 次") - #logger.info(f" - 最终条件: {final_condition.sum()} 次") - #logger.info(f" - 使用参数: exit_bb_upper_deviation={self.exit_bb_upper_deviation.value}, exit_volume_multiplier={self.exit_volume_multiplier.value}, rsi_overbought={self.rsi_overbought.value}") - - # 日志记录 + if dataframe['exit_long'].sum() > 0: logger.info(f"[{metadata['pair']}] 触发出场信号数量: {dataframe['exit_long'].sum()}") @@ -767,55 +705,13 @@ class FreqaiPrimer(IStrategy): ) traditional_condition = condition_count >= self.min_condition_count.value - # ==================== FreqAI 入场信号融合 (市场自适应) ==================== - # 检查 FreqAI 预测结果是否可用 - if "&-max_drawdown" in dataframe.columns and "do_predict" in dataframe.columns: - # 行级动态阈值,避免在 populate_ 中使用 iloc[-1] - if 'market_state' in dataframe.columns: - drawdown_th_series = dataframe['market_state'].apply( - lambda s: self._get_adaptive_thresholds(s)['max_drawdown'] - ) - else: - default_th = self._get_adaptive_thresholds('neutral') - drawdown_th_series = np.full(len(dataframe), default_th['max_drawdown']) - - # 置信度过滤: do_predict >= 1 表示预测可信 - ml_confidence = (dataframe["do_predict"] >= 1) - - # 最大回撤过滤: 使用动态阈值 - ml_drawdown_safe = (dataframe["&-max_drawdown"] >= drawdown_th_series) - - # === 极简融合策略:FreqAI 仅做风险否决 === - bad_risk_mask = (dataframe["&-max_drawdown"] < drawdown_th_series) & ml_confidence - - final_condition = traditional_condition & ~bad_risk_mask - - # 记录融合信息(仅统计数量) - logger.info( - f"[{metadata['pair']}] FreqAI入场: " - f"传统={int(traditional_condition.sum())}, " - f"回撤安全={int(ml_drawdown_safe.sum())}, " - f"风险否决={int(bad_risk_mask.sum())}, " - f"最终={int(final_condition.sum())}" - ) - else: - # FreqAI 未启用,使用传统条件 - final_condition = traditional_condition + # ==================== 入场信号:仅使用传统条件 ==================== + final_condition = traditional_condition # 设置入场信号 dataframe.loc[final_condition, 'enter_long'] = 1 - # 增强调试信息 - #logger.info(f"[{metadata['pair']}] 入场条件检查:") - #logger.info(f" - 价格接近布林带下轨: {close_to_bb_lower_1h.sum()} 次") - #logger.info(f" - RSI 超卖: {rsi_condition_1h.sum()} 次") - #logger.info(f" - StochRSI 超卖: {stochrsi_condition_1h.sum()} 次") - #logger.info(f" - MACD 上升趋势: {macd_condition_1h.sum()} 次") - #logger.info(f" - 成交量或布林带宽度: {(volume_spike | bb_width_condition).sum()} 次") - #logger.info(f" - 趋势确认: {trend_confirmation.sum()} 次") - #logger.info(f" - 最终条件: {final_condition.sum()} 次") - # 在populate_entry_trend方法末尾添加 - # 计算条件间的相关性 + # 计算条件间的相关性(仅用于分析) conditions = DataFrame({ 'close_to_bb': close_to_bb_lower_1h, 'rsi': rsi_condition_1h, @@ -826,6 +722,7 @@ class FreqaiPrimer(IStrategy): }) correlation = conditions.corr().mean().mean() #logger.info(f"[{metadata['pair']}] 条件平均相关性: {correlation:.2f}") + # 日志记录 if dataframe['enter_long'].sum() > 0: logger.info(f"[{metadata['pair']}] 发现入场信号数量: {dataframe['enter_long'].sum()}") @@ -902,20 +799,35 @@ class FreqaiPrimer(IStrategy): ) -> bool: """ 交易买入前的确认函数,用于最终决定是否执行交易 - 此处实现剧烈拉升检查逻辑 + - 步骤1: 剧烈拉升过滤(纯规则) + - 步骤2: FreqAI 入场置信度过滤(仅作为最终确认,不影响回测一致性) """ - # 默认允许交易 allow_trade = True - + # 仅对多头交易进行检查 if side == 'long': - # 检查是否处于剧烈拉升的不稳固区域 + # 步骤1: 检查是否处于剧烈拉升的不稳固区域 is_unstable_region = self.detect_h1_rapid_rise(pair) if is_unstable_region: #logger.info(f"[{pair}] 由于检测到剧烈拉升,取消入场交易") allow_trade = False - - # 如果没有阻止因素,允许交易 + + # 步骤2: 使用 FreqAI 入场置信度进行二次确认 + try: + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + if "&-entry_confidence" in dataframe.columns and "do_predict" in dataframe.columns and len(dataframe) > 0: + last_row = dataframe.iloc[-1] + score = last_row["&-entry_confidence"] + do_predict = last_row["do_predict"] + + # 简单规则:预测有效且置信度足够高才允许入场 + min_confidence = 0.52 + if do_predict < 1 or np.isnan(score) or score < min_confidence: + #logger.info(f"[{pair}] 由于 FreqAI 入场置信度不足(score={score:.3f}, do_predict={do_predict}),取消入场交易") + allow_trade = False + except Exception as e: + logger.error(f"[{pair}] confirm_trade_entry 中 FreqAI 置信度检查异常: {str(e)}") + return allow_trade def custom_stoploss(self, pair: str, trade: 'Trade', current_time, current_rate: float, @@ -964,20 +876,8 @@ class FreqaiPrimer(IStrategy): dynamic_roi_threshold = 0.0 dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - # 优先使用市场状态特征;若不存在则回退为中性市场,避免依赖不存在的sma列 + # 优先使用市场状态特征;若不存在则回退为中性市场 current_state = dataframe['market_state'].iloc[-1] if 'market_state' in dataframe.columns else 'neutral' - - # ==================== FreqAI hold_duration 动态调整 ==================== - # 使用 ML 预测的最佳持仓时长来调整 ROI 阈值 - # hold_duration 在 0-1 之间,0 = 应立即出场,1 = 应持有到最后 - if '&-hold_duration' in dataframe.columns and 'do_predict' in dataframe.columns: - ml_hold_duration = dataframe['&-hold_duration'].iloc[-1] - ml_predict_valid = dataframe['do_predict'].iloc[-1] >= 1 - if ml_predict_valid and not np.isnan(ml_hold_duration): - # 如果预测应尽快出场(hold_duration < 0.3),降低 ROI 阈值(更容易触发出场) - # 如果预测应持有(hold_duration > 0.7),提高 ROI 阈值(更难触发出场) - hold_adjustment = 1.0 + (ml_hold_duration - 0.5) * 0.6 # 范围: 0.7 ~ 1.3 - dynamic_roi_threshold = dynamic_roi_threshold * hold_adjustment entry_tag = trade.enter_tag if hasattr(trade, 'enter_tag') else None profit_ratio = current_profit / dynamic_roi_threshold if dynamic_roi_threshold > 0 else 0