diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index d2e574da..06f6b701 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -411,7 +411,7 @@ class FreqaiPrimer(IStrategy): logger.warning(f"[{pair}] 1h 数据为空或不足({len(dataframe_1h)} 根K线),初始化空列") for col in ['open_1h', 'high_1h', 'low_1h', 'close_1h', 'stochrsi_k_1h', 'stochrsi_d_1h', 'bb_upper_1h', 'bb_middle_1h', 'bb_lower_1h', 'volume_z_score_1h']: - dataframe[col] = np.nan + dataframe[col] = 0.0 # 使用0.0代替np.nan避免NaN值 else: # 计算 1h 指标 if len(dataframe_1h) >= 20: # 确保有足够数据计算 rolling(20) @@ -430,7 +430,7 @@ class FreqaiPrimer(IStrategy): dataframe_1h['volume_z_score_1h'] = dataframe_1h['volume_z_score_1h'].replace([np.inf, -np.inf], 0).ffill().fillna(0) else: logger.warning(f"[{pair}] 1h 数据不足以计算 volume_z_score_1h({len(dataframe_1h)} 根K线,需至少20根)") - dataframe_1h['volume_z_score_1h'] = np.nan + dataframe_1h['volume_z_score_1h'] = 0.0 # 映射 1h 数据到主时间框架 for col in ['open', 'high', 'low', 'close', 'stochrsi_k', 'stochrsi_d', 'bb_upper_1h', 'bb_middle_1h', 'bb_lower_1h', 'volume_z_score_1h']: @@ -438,15 +438,30 @@ class FreqaiPrimer(IStrategy): dataframe[col if col.endswith('_1h') else f"{col}_1h"] = dataframe_1h[col].reindex(dataframe.index, method='ffill').bfill() else: logger.warning(f"[{pair}] 1h 数据缺少列 {col},初始化为空") - dataframe[col if col.endswith('_1h') else f"{col}_1h"] = np.nan + dataframe[col if col.endswith('_1h') else f"{col}_1h"] = 0.0 - # 数据清理:处理 NaN 和无穷值 - for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", - "&-price_value_divergence", "price_value_divergence", "open_1h", "high_1h", "low_1h", - "close_1h", "stochrsi_k_1h", "stochrsi_d_1h", "bb_upper_1h", "bb_middle_1h", "bb_lower_1h", - "volume_z_score_1h"]: + # 数据清理:处理 NaN 和无穷值,确保数据完整性 + critical_columns = ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", + "&-price_value_divergence", "price_value_divergence", "open_1h", "high_1h", "low_1h", + "close_1h", "stochrsi_k_1h", "stochrsi_d_1h", "bb_upper_1h", "bb_middle_1h", "bb_lower_1h", + "volume_z_score_1h", "stochrsi_k", "stochrsi_d", "close", "volume"] + + for col in critical_columns: if col in dataframe.columns: - dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0) + # 处理无穷值和NaN值 + dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0) + # 前向填充,然后用0填充剩余NaN + dataframe[col] = dataframe[col].ffill().fillna(0) + # 确保数据类型正确 + dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce').fillna(0) + + # 验证数据长度一致性 + expected_length = len(dataframe) + for col in critical_columns: + if col in dataframe.columns and len(dataframe[col]) != expected_length: + logger.warning(f"[{pair}] 列 {col} 长度不匹配: {len(dataframe[col])} != {expected_length}") + # 重新索引确保长度一致 + dataframe[col] = dataframe[col].reindex(dataframe.index, method='ffill').fillna(0) # 调用 FreqAI 预测 - 使用单一回归模型 if not hasattr(self, 'freqai') or self.freqai is None: @@ -723,7 +738,20 @@ class FreqaiPrimer(IStrategy): f"cond5: {cond5.mean():.2%}, " f"buy_condition: {buy_condition.mean():.2%}") # 记录 enter_long 信号统计 - logger.info(f"[{pair}] enter_long 信号总数:{dataframe['enter_long'].sum() if 'enter_long' in dataframe.columns else 0}") + enter_long_count = dataframe['enter_long'].sum() if 'enter_long' in dataframe.columns else 0 + logger.info(f"[{pair}] enter_long 信号总数:{enter_long_count}") + + # 数据完整性检查 + if len(dataframe) > 0: + nan_columns = [col for col in dataframe.columns if dataframe[col].isna().any()] + if nan_columns: + logger.warning(f"[{pair}] 发现NaN值的列: {nan_columns}") + for col in nan_columns: + nan_count = dataframe[col].isna().sum() + logger.warning(f"[{pair}] 列 {col} 有 {nan_count} 个NaN值") + + # 验证数据长度 + logger.info(f"[{pair}] 最终数据行数: {len(dataframe)}, 列数: {len(dataframe.columns)}") return dataframe @@ -738,6 +766,11 @@ class FreqaiPrimer(IStrategy): dataframe["adx"] = ta.ADX(dataframe, timeperiod=14) # 计算短期价格涨幅(最近 5 根 K 线,约 15 分钟) dataframe["short_term_return"] = dataframe["close"].pct_change(5, fill_method=None) * 100 # 百分比回报 + + # 清理新计算列的NaN值 + for col in ["stochrsi_k", "adx", "short_term_return"]: + if col in dataframe.columns: + dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0) # 获取市场趋势得分 trend_score = self.get_market_trend(dataframe=dataframe, metadata={'pair': pair}) @@ -808,12 +841,12 @@ class FreqaiPrimer(IStrategy): conditions.append(sell_condition) - # 调试日志 - divergence_value = dataframe["&-price_value_divergence"].iloc[-1] if not dataframe["&-price_value_divergence"].isna().all() else np.nan - rsi_value = dataframe["rsi"].iloc[-1] if not dataframe["rsi"].isna().all() else np.nan - stochrsi_value = dataframe["stochrsi_k"].iloc[-1] if not dataframe["stochrsi_k"].isna().all() else np.nan - adx_value = dataframe["adx"].iloc[-1] if not dataframe["adx"].isna().all() else np.nan - short_term_return = dataframe["short_term_return"].iloc[-1] if not dataframe["short_term_return"].isna().all() else np.nan + # 调试日志 - 使用安全的值获取 + divergence_value = dataframe["&-price_value_divergence"].iloc[-1] if len(dataframe) > 0 and not dataframe["&-price_value_divergence"].isna().iloc[-1] else 0.0 + rsi_value = dataframe["rsi"].iloc[-1] if len(dataframe) > 0 and not dataframe["rsi"].isna().iloc[-1] else 0.0 + stochrsi_value = dataframe["stochrsi_k"].iloc[-1] if len(dataframe) > 0 and not dataframe["stochrsi_k"].isna().iloc[-1] else 0.0 + adx_value = dataframe["adx"].iloc[-1] if len(dataframe) > 0 and not dataframe["adx"].isna().iloc[-1] else 0.0 + short_term_return = dataframe["short_term_return"].iloc[-1] if len(dataframe) > 0 and not dataframe["short_term_return"].isna().iloc[-1] else 0.0 logger.info(f"[{pair}] 卖出条件检查 - " f"&-price_value_divergence={divergence_value:.6f} > {self.sell_threshold * float(self.exit_divergence_multiplier.value):.6f}: {cond1.iloc[-1]}, " f"rsi={rsi_value:.2f} > {float(self.exit_rsi_threshold.value)} & stochrsi_k={stochrsi_value:.2f} > {float(self.exit_stochrsi_threshold.value)}: {cond2.iloc[-1]}, " @@ -828,6 +861,26 @@ class FreqaiPrimer(IStrategy): else: logger.info(f"[{pair}] 无有效卖出条件") + # 数据完整性检查 + if len(dataframe) > 0: + nan_columns = [col for col in dataframe.columns if dataframe[col].isna().any()] + if nan_columns: + logger.warning(f"[{pair}] 卖出检查 - 发现NaN值的列: {nan_columns}") + for col in nan_columns: + nan_count = dataframe[col].isna().sum() + if nan_count > 0: + logger.warning(f"[{pair}] 卖出检查 - 列 {col} 有 {nan_count} 个NaN值,已清理") + dataframe[col] = dataframe[col].fillna(0) + + # 验证数据长度一致性 + expected_length = len(dataframe) + actual_length = len(dataframe.dropna()) + logger.info(f"[{pair}] 卖出检查 - 数据行数: {expected_length}, 有效行数: {actual_length}") + + # 记录exit_long信号 + exit_long_count = dataframe['exit_long'].sum() if 'exit_long' in dataframe.columns else 0 + logger.info(f"[{pair}] exit_long 信号总数:{exit_long_count}") + return dataframe def buy_space(self):