diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 694dc10c..c1ea5779 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -901,123 +901,146 @@ class FreqaiPrimer(IStrategy): """ self.strategy_log(f"[{pair}] confirm_trade_entry 被调用 - 价格: {rate:.8f}, 时间: {current_time}") - # 默认允许交易 - allow_trade = True + # 记录所有拒绝条件的列表 + rejected_conditions = [] # 仅对多头交易进行检查 if side == 'long': # 冷启动保护:程序启动后前20分钟内不允许入场 + cold_start_rejected = False if hasattr(self, "_strategy_start_time"): # 在非实盘环境(回测/模拟)下禁用冷启动保护 if not self.config.get("dry_run", False): warmup_minutes = 20 elapsed_minutes = (current_time - self._strategy_start_time).total_seconds() / 60.0 if elapsed_minutes < warmup_minutes: - self.strategy_log( - f"[{pair}] 策略启动未满 {warmup_minutes} 分钟(已运行 {elapsed_minutes:.1f} 分钟),跳过本次入场信号" - ) - return False + rejected_conditions.append(f"冷启动保护: 策略启动未满 {warmup_minutes} 分钟(已运行 {elapsed_minutes:.1f} 分钟)") + self.strategy_log(f"[{pair}] {rejected_conditions[-1]}") + cold_start_rejected = True else: # 在实盘模式下,记录冷启动保护已禁用 self.strategy_log(f"[{pair}] 实盘模式,冷启动保护已禁用") # 检查1:入场间隔控制(使用hyperopt参数) - if pair in self._last_entry_time: + interval_rejected = False + if pair in self._last_entry_time and not cold_start_rejected: last_entry = self._last_entry_time[pair] time_diff = (current_time - last_entry).total_seconds() * 0.0166666667 # 转换为分钟(使用乘法避免除法) if time_diff < self.entry_interval_minutes.value: - self.strategy_log(f"[{pair}] 入场间隔不足: 距离上次入场 {time_diff:.1f}分钟 < {self.entry_interval_minutes.value}分钟,取消本次入场") - return False + rejected_conditions.append(f"入场间隔不足: 距离上次入场 {time_diff:.1f}分钟 < {self.entry_interval_minutes.value}分钟") + self.strategy_log(f"[{pair}] {rejected_conditions[-1]}") + interval_rejected = True # 检查2:检查是否处于剧烈拉升的不稳固区域 - is_unstable_region = self.detect_h1_rapid_rise(pair) - if is_unstable_region: - self.strategy_log(f"[{pair}] 检测到剧烈拉升,取消入场交易") - return False + unstable_rejected = False + if not cold_start_rejected and not interval_rejected: + is_unstable_region = self.detect_h1_rapid_rise(pair) + if is_unstable_region: + rejected_conditions.append("剧烈拉升检测: 检测到1小时图剧烈拉升") + self.strategy_log(f"[{pair}] {rejected_conditions[-1]}") + unstable_rejected = True # 检查3:ML 审核官(FreqAI 过滤低质量入场)+ 入场诊断统计 # 逻辑:用 entry_signal 概率来判断——若"容易上涨概率"低,则拒绝入场 + ml_rejected = False try: - df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) - if len(df) > 0: - last_row = df.iloc[-1] - entry_prob = None - - # 优先使用 FreqAI 的 entry_signal 预测列 - if '&s-entry_signal' in df.columns: - entry_prob = float(last_row['&s-entry_signal']) - elif '&-entry_signal_prob' in df.columns: - entry_prob = float(last_row['&-entry_signal_prob']) - elif '&-s-entry_signal_prob' in df.columns: - entry_prob = float(last_row['&-s-entry_signal_prob']) - elif '&-entry_signal' in df.columns: - val = last_row['&-entry_signal'] - if isinstance(val, (int, float)): - entry_prob = float(val) + if not cold_start_rejected and not interval_rejected and not unstable_rejected: + df, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + if len(df) > 0: + last_row = df.iloc[-1] + entry_prob = None + + # 优先使用 FreqAI 的 entry_signal 预测列 + if '&s-entry_signal' in df.columns: + entry_prob = float(last_row['&s-entry_signal']) + elif '&-entry_signal_prob' in df.columns: + entry_prob = float(last_row['&-entry_signal_prob']) + elif '&-s-entry_signal_prob' in df.columns: + entry_prob = float(last_row['&-s-entry_signal_prob']) + elif '&-entry_signal' in df.columns: + val = last_row['&-entry_signal'] + if isinstance(val, (int, float)): + entry_prob = float(val) + else: + # 文本标签时,简单映射为 0/1 + entry_prob = 1.0 if str(val).lower() in ['entry', 'buy', '1'] else 0.0 + + # ========== 新增:入场诊断统计 ========== + # 统计当前入场点的关键指标,用于分析"买在高位"问题 + current_close = float(last_row['close']) + + # 1. 价格与短期高点的关系 + recent_high_5 = float(df['high'].iloc[-5:].max()) if len(df) >= 5 else current_close + price_vs_recent_high = (current_close - recent_high_5) / recent_high_5 if recent_high_5 > 0 else 0 + + # 2. 价格与 EMA5 的关系 + ema5_1h = float(last_row.get('ema_5_1h', current_close)) + price_vs_ema5 = (current_close - ema5_1h) / ema5_1h if ema5_1h > 0 else 0 + + # 3. 价格与布林带的位置 + bb_upper = float(last_row.get('bb_upper_1h', current_close)) + bb_lower = float(last_row.get('bb_lower_1h', current_close)) + bb_position = (current_close - bb_lower) / (bb_upper - bb_lower) if (bb_upper - bb_lower) > 0 else 0.5 + + # 4. RSI 状态 + rsi_1h = float(last_row.get('rsi_1h', 50)) + + # 5. MACD 状态 + macd_1h = float(last_row.get('macd_1h', 0)) + macd_signal_1h = float(last_row.get('macd_signal_1h', 0)) + macd_cross = 'up' if macd_1h > macd_signal_1h else 'down' + + # 6. 市场状态 + market_state = str(last_row.get('market_state', 'unknown')) + + # 输出诊断日志 + if entry_prob is not None: + ml_prob_str = f"{entry_prob:.2f}" else: - # 文本标签时,简单映射为 0/1 - entry_prob = 1.0 if str(val).lower() in ['entry', 'buy', '1'] else 0.0 - - # ========== 新增:入场诊断统计 ========== - # 统计当前入场点的关键指标,用于分析"买在高位"问题 - current_close = float(last_row['close']) - - # 1. 价格与短期高点的关系 - recent_high_5 = float(df['high'].iloc[-5:].max()) if len(df) >= 5 else current_close - price_vs_recent_high = (current_close - recent_high_5) / recent_high_5 if recent_high_5 > 0 else 0 - - # 2. 价格与 EMA5 的关系 - ema5_1h = float(last_row.get('ema_5_1h', current_close)) - price_vs_ema5 = (current_close - ema5_1h) / ema5_1h if ema5_1h > 0 else 0 - - # 3. 价格与布林带的位置 - bb_upper = float(last_row.get('bb_upper_1h', current_close)) - bb_lower = float(last_row.get('bb_lower_1h', current_close)) - bb_position = (current_close - bb_lower) / (bb_upper - bb_lower) if (bb_upper - bb_lower) > 0 else 0.5 - - # 4. RSI 状态 - rsi_1h = float(last_row.get('rsi_1h', 50)) - - # 5. MACD 状态 - macd_1h = float(last_row.get('macd_1h', 0)) - macd_signal_1h = float(last_row.get('macd_signal_1h', 0)) - macd_cross = 'up' if macd_1h > macd_signal_1h else 'down' - - # 6. 市场状态 - market_state = str(last_row.get('market_state', 'unknown')) - - # 输出诊断日志 - if entry_prob is not None: - ml_prob_str = f"{entry_prob:.2f}" + ml_prob_str = "N/A" + self.strategy_log( + f"[入场诊断] {pair} | " + f"价格: {current_close:.6f} | " + f"vs 5K高点: {price_vs_recent_high:+.2%} | " + f"vs EMA5: {price_vs_ema5:+.2%} | " + f"布林位置: {bb_position:.2f} | " + f"RSI: {rsi_1h:.1f} | " + f"MACD: {macd_cross} | " + f"市场: {market_state} | " + f"ML入场概率: {ml_prob_str}" + ) + # ========== 诊断统计结束 ========== + + if entry_prob is not None: + # 确保概率在 [0, 1] 范围内(分类器输出可能有浮点误差) + entry_prob = max(0.0, min(1.0, entry_prob)) + entry_threshold = self.ml_entry_signal_threshold.value + if entry_prob < entry_threshold: + rejected_conditions.append(f"ML审核官: entry_signal 概率 {entry_prob:.2f} < 阈值 {entry_threshold:.2f}") + self.strategy_log(f"[{pair}] {rejected_conditions[-1]}") + ml_rejected = True + else: + self.strategy_log(f"[{pair}] ML 审核官允许入场: entry_signal 概率 {entry_prob:.2f} >= 阈值 {entry_threshold:.2f}") else: - ml_prob_str = "N/A" - self.strategy_log( - f"[入场诊断] {pair} | " - f"价格: {current_close:.6f} | " - f"vs 5K高点: {price_vs_recent_high:+.2%} | " - f"vs EMA5: {price_vs_ema5:+.2%} | " - f"布林位置: {bb_position:.2f} | " - f"RSI: {rsi_1h:.1f} | " - f"MACD: {macd_cross} | " - f"市场: {market_state} | " - f"ML入场概率: {ml_prob_str}" - ) - # ========== 诊断统计结束 ========== - - if entry_prob is not None: - # 确保概率在 [0, 1] 范围内(分类器输出可能有浮点误差) - entry_prob = max(0.0, min(1.0, entry_prob)) - entry_threshold = self.ml_entry_signal_threshold.value - if entry_prob < entry_threshold: - self.strategy_log(f"[{pair}] ML 审核官拒绝入场: entry_signal 概率 {entry_prob:.2f} < 阈值 {entry_threshold:.2f}(上涨概率低,不宜入场)") - return False - else: - self.strategy_log(f"[{pair}] ML 审核官允许入场: entry_signal 概率 {entry_prob:.2f} >= 阈值 {entry_threshold:.2f}") + self.strategy_log(f"[{pair}] 无数据分析数据,跳过ML审核") else: - self.strategy_log(f"[{pair}] 无数据分析数据,跳过ML审核") + self.strategy_log(f"[{pair}] 因前置条件拒绝,跳过ML审核") except Exception as e: logger.warning(f"[{pair}] ML 审核官检查失败,忽略 ML 过滤: {e}") - return True + rejected_conditions.append(f"ML审核官异常: {str(e)}") + self.strategy_log(f"[{pair}] {rejected_conditions[-1]}") + else: + # 如果不是多头交易,也记录下来 + self.strategy_log(f"[{pair}] 非多头交易,跳过入场检查") + + # 如果没有任何拒绝条件,表示允许入场 + if not rejected_conditions: + self.strategy_log(f"[{pair}] 所有条件通过,允许入场") + return True + else: + # 显示所有被拒绝的条件 + self.strategy_log(f"[{pair}] 入场被拒绝,原因列表: {', '.join(rejected_conditions)}") + return False def confirm_trade_exit( self,