diff --git a/freqtrade/templates/freqaiprimer.py b/freqtrade/templates/freqaiprimer.py index 2987e5fb..57c11fd6 100644 --- a/freqtrade/templates/freqaiprimer.py +++ b/freqtrade/templates/freqaiprimer.py @@ -172,55 +172,55 @@ class FreqaiPrimer(IStrategy): avg_stochrsi = dataframe['stochrsi_k'].rolling(window=period).mean() return avg_stochrsi > threshold - def is_bearish_market(self, dataframe: DataFrame, metadata: dict, timeframe: str = "4h") -> pd.Series: + def is_bearish_market(self, dataframe: DataFrame, metadata: dict, timeframe: str = "1h") -> pd.Series: pair = metadata.get('pair', 'Unknown') - logger.debug(f"[{pair}] 开始计算 4h 熊市信号") + logger.debug(f"[{pair}] 开始计算 1h 熊市信号") # 防御性检查 - required_columns = ['close_4h', 'high_4h', 'low_4h', 'open_4h', 'stochrsi_k_4h', 'stochrsi_d_4h', 'bb_middle_4h'] + required_columns = ['close_1h', 'high_1h', 'low_1h', 'open_1h', 'stochrsi_k_1h', 'stochrsi_d_1h', 'bb_middle_1h'] missing = [col for col in required_columns if col not in dataframe.columns] if missing: logger.error(f"[{pair}] 缺少必要列:{missing},返回全 False") return pd.Series([False] * len(dataframe), index=dataframe.index) - # 检查 volume_z_score_4h 是否存在 - has_volume_z_score = 'volume_z_score_4h' in dataframe.columns and not dataframe['volume_z_score_4h'].isna().all() + # 检查 volume_z_score_1h 是否存在 + has_volume_z_score = 'volume_z_score_1h' in dataframe.columns and not dataframe['volume_z_score_1h'].isna().all() # 条件 a:价格远低于布林带中轨(低于中轨 1% 以上) - cond_a = dataframe['close_4h'] < dataframe['bb_middle_4h'] * 0.99 + cond_a = dataframe['close_1h'] < dataframe['bb_middle_1h'] * 0.99 # 条件 b:STOCHRSI 超过90 - cond_b = dataframe['stochrsi_k_4h'] > 90 + cond_b = dataframe['stochrsi_k_1h'] > 90 # 条件 c:看跌蜡烛图形态 - open_4h = dataframe['open_4h'] - close_4h = dataframe['close_4h'] - high_4h = dataframe['high_4h'] - low_4h = dataframe['low_4h'] - prev_open = open_4h.shift(1) - prev_close = close_4h.shift(1) + open_1h = dataframe['open_1h'] + close_1h = dataframe['close_1h'] + high_1h = dataframe['high_1h'] + low_1h = dataframe['low_1h'] + prev_open = open_1h.shift(1) + prev_close = close_1h.shift(1) cond_engulfing = ( (prev_close > prev_open) & - (close_4h < open_4h) & - (close_4h < prev_open) & - (open_4h > prev_close) + (close_1h < open_1h) & + (close_1h < prev_open) & + (open_1h > prev_close) ) cond_dark_cloud_cover = ( (prev_close > prev_open) & - (open_4h > prev_close) & - (close_4h < (prev_open + prev_close) / 2) & - (close_4h < open_4h) + (open_1h > prev_close) & + (close_1h < (prev_open + prev_close) / 2) & + (close_1h < open_1h) ) - body = abs(open_4h - close_4h) - upper_wick = high_4h - np.maximum(open_4h, close_4h) - lower_wick = np.minimum(open_4h, close_4h) - low_4h - cond_shooting_star = (upper_wick > 2 * body) & (lower_wick < body) & (close_4h < open_4h) + body = abs(open_1h - close_1h) + upper_wick = high_1h - np.maximum(open_1h, close_1h) + lower_wick = np.minimum(open_1h, close_1h) - low_1h + cond_shooting_star = (upper_wick > 2 * body) & (lower_wick < body) & (close_1h < open_1h) cond_stochrsi_high = self.is_stochrsi_overbought(dataframe, period=10, threshold=85) cond_c = cond_engulfing | cond_dark_cloud_cover | cond_shooting_star | cond_stochrsi_high # 条件 d:成交量显著下降 - cond_d = dataframe['volume_z_score_4h'] < -1.0 if has_volume_z_score else pd.Series([False] * len(dataframe), index=dataframe.index) + cond_d = dataframe['volume_z_score_1h'] < -1.0 if has_volume_z_score else pd.Series([False] * len(dataframe), index=dataframe.index) # 综合熊市信号(至少满足两个条件) bearish_signal = (cond_a & cond_b) | (cond_a & cond_c) | (cond_b & cond_c) | (cond_a & cond_d) @@ -250,8 +250,8 @@ class FreqaiPrimer(IStrategy): def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ - 计算主时间框架(3m)和 4h 时间框架的指标,并映射到主 dataframe。 - 包含 FreqAI 预测、布林带、RSI、成交量 Z 分数等,并确保 4h 数据列完整性。 + 计算主时间框架(3m)和 1h 时间框架的指标,并映射到主 dataframe。 + 包含 FreqAI 预测、布林带、RSI、成交量 Z 分数等,并确保 1h 数据列完整性。 """ pair = metadata.get('pair', 'Unknown') logger.info(f"[{pair}] 当前可用列(调用FreqAI前):{list(dataframe.columns)}") @@ -272,46 +272,46 @@ class FreqaiPrimer(IStrategy): dataframe["stochrsi_k"] = stochrsi["fastk"] dataframe["stochrsi_d"] = stochrsi["fastd"] - # 获取 4h 时间框架数据 - dataframe_4h = self.dp.get_pair_dataframe(pair=pair, timeframe="4h") - if dataframe_4h.empty or len(dataframe_4h) < 50: - logger.warning(f"[{pair}] 4h 数据为空或不足({len(dataframe_4h)} 根K线),初始化空列") - for col in ['open_4h', 'high_4h', 'low_4h', 'close_4h', 'stochrsi_k_4h', 'stochrsi_d_4h', - 'bb_upper_4h', 'bb_middle_4h', 'bb_lower_4h', 'volume_z_score_4h']: + # 获取 1h 时间框架数据 + dataframe_1h = self.dp.get_pair_dataframe(pair=pair, timeframe="1h") + if dataframe_1h.empty or len(dataframe_1h) < 50: + 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 else: - # 计算 4h 指标 - if len(dataframe_4h) >= 20: # 确保有足够数据计算 rolling(20) - stochrsi_4h = ta.STOCHRSI(dataframe_4h, timeperiod=14, fastk_period=3, fastd_period=3) - dataframe_4h['stochrsi_k'] = stochrsi_4h['fastk'] - dataframe_4h['stochrsi_d'] = stochrsi_4h['fastd'] - real = ta.TYPPRICE(dataframe_4h) + # 计算 1h 指标 + if len(dataframe_1h) >= 20: # 确保有足够数据计算 rolling(20) + stochrsi_1h = ta.STOCHRSI(dataframe_1h, timeperiod=14, fastk_period=3, fastd_period=3) + dataframe_1h['stochrsi_k'] = stochrsi_1h['fastk'] + dataframe_1h['stochrsi_d'] = stochrsi_1h['fastd'] + real = ta.TYPPRICE(dataframe_1h) upperband, middleband, lowerband = ta.BBANDS(real, timeperiod=20, nbdevup=2.0, nbdevdn=2.0) - dataframe_4h['bb_upper_4h'] = upperband - dataframe_4h['bb_middle_4h'] = middleband - dataframe_4h['bb_lower_4h'] = lowerband - dataframe_4h['volume_mean_20_4h'] = dataframe_4h["volume"].rolling(20).mean() - dataframe_4h['volume_std_20_4h'] = dataframe_4h["volume"].rolling(20).std() - dataframe_4h['volume_z_score_4h'] = (dataframe_4h["volume"] - dataframe_4h['volume_mean_20_4h']) / dataframe_4h['volume_std_20_4h'] + dataframe_1h['bb_upper_1h'] = upperband + dataframe_1h['bb_middle_1h'] = middleband + dataframe_1h['bb_lower_1h'] = lowerband + dataframe_1h['volume_mean_20_1h'] = dataframe_1h["volume"].rolling(20).mean() + dataframe_1h['volume_std_20_1h'] = dataframe_1h["volume"].rolling(20).std() + dataframe_1h['volume_z_score_1h'] = (dataframe_1h["volume"] - dataframe_1h['volume_mean_20_1h']) / dataframe_1h['volume_std_20_1h'] # 清理 NaN 和无穷值 - dataframe_4h['volume_z_score_4h'] = dataframe_4h['volume_z_score_4h'].replace([np.inf, -np.inf], 0).ffill().fillna(0) + 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}] 4h 数据不足以计算 volume_z_score_4h({len(dataframe_4h)} 根K线,需至少20根)") - dataframe_4h['volume_z_score_4h'] = np.nan + logger.warning(f"[{pair}] 1h 数据不足以计算 volume_z_score_1h({len(dataframe_1h)} 根K线,需至少20根)") + dataframe_1h['volume_z_score_1h'] = np.nan - # 映射 4h 数据到主时间框架 - for col in ['open', 'high', 'low', 'close', 'stochrsi_k', 'stochrsi_d', 'bb_upper_4h', 'bb_middle_4h', 'bb_lower_4h', 'volume_z_score_4h']: - if col in dataframe_4h.columns: - dataframe[col if col.endswith('_4h') else f"{col}_4h"] = dataframe_4h[col].reindex(dataframe.index, method='ffill').bfill() + # 映射 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']: + if col in dataframe_1h.columns: + dataframe[col if col.endswith('_1h') else f"{col}_1h"] = dataframe_1h[col].reindex(dataframe.index, method='ffill').bfill() else: - logger.warning(f"[{pair}] 4h 数据缺少列 {col},初始化为空") - dataframe[col if col.endswith('_4h') else f"{col}_4h"] = np.nan + logger.warning(f"[{pair}] 1h 数据缺少列 {col},初始化为空") + dataframe[col if col.endswith('_1h') else f"{col}_1h"] = np.nan # 数据清理:处理 NaN 和无穷值 for col in ["ema200", "bb_upperband", "bb_middleband", "bb_lowerband", "rsi", "volume_z_score", - "&-price_value_divergence", "price_value_divergence", "open_4h", "high_4h", "low_4h", - "close_4h", "stochrsi_k_4h", "stochrsi_d_4h", "bb_upper_4h", "bb_middle_4h", "bb_lower_4h", - "volume_z_score_4h"]: + "&-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"]: if col in dataframe.columns: dataframe[col] = dataframe[col].replace([np.inf, -np.inf], 0).ffill().fillna(0) @@ -385,9 +385,9 @@ class FreqaiPrimer(IStrategy): f"&-price_value_divergence: {dataframe['&-price_value_divergence'].iloc[-1]:.6f}, " f"volume_z_score: {dataframe['volume_z_score'].iloc[-1]:.2f}, " f"bb_lowerband: {dataframe['bb_lowerband'].iloc[-1]:.6f}, " - f"close_4h: {dataframe['close_4h'].iloc[-1] if 'close_4h' in dataframe else 'N/A'}, " - f"stochrsi_k_4h: {dataframe['stochrsi_k_4h'].iloc[-1] if 'stochrsi_k_4h' in dataframe else 'N/A'}, " - f"volume_z_score_4h: {dataframe['volume_z_score_4h'].iloc[-1] if 'volume_z_score_4h' in dataframe else 'N/A'}") + f"close_1h: {dataframe['close_1h'].iloc[-1] if 'close_1h' in dataframe else 'N/A'}, " + f"stochrsi_k_1h: {dataframe['stochrsi_k_1h'].iloc[-1] if 'stochrsi_k_1h' in dataframe else 'N/A'}, " + f"volume_z_score_1h: {dataframe['volume_z_score_1h'].iloc[-1] if 'volume_z_score_1h' in dataframe else 'N/A'}") if not self.stats_logged: logger.info("===== 所有币对的 labels_mean 和 labels_std 汇总 =====") @@ -419,7 +419,7 @@ class FreqaiPrimer(IStrategy): stochrsi_threshold = self.linear_map(trend_score, 0, 100, stochrsi_max, stochrsi_min) # 计算熊市信号和 STOCHRSI 超买信号 - bearish_signal = self.is_bearish_market(dataframe, metadata, timeframe="4h") + bearish_signal = self.is_bearish_market(dataframe, metadata, timeframe="1h") bearish_signal_aligned = bearish_signal.reindex(dataframe.index, method='ffill').fillna(False) stochrsi_overbought = self.is_stochrsi_overbought(dataframe, period=10, threshold=85) stochrsi_overbought_aligned = stochrsi_overbought.reindex(dataframe.index, method='ffill').fillna(True) diff --git a/tools/view_feather.py b/tools/view_feather.py index f9e648ec..c6626c90 100644 --- a/tools/view_feather.py +++ b/tools/view_feather.py @@ -8,7 +8,7 @@ def analyze_candlestick_data(file_path): # 查看数据集行数和列数 rows, columns = df.shape - if rows < 500: + if rows < 1000: # 短表数据(行数少于500)查看全量数据信息 print('数据全部内容信息:') print(df.to_csv(sep='\t', na_rep='nan')) @@ -17,6 +17,10 @@ def analyze_candlestick_data(file_path): print('数据前几行内容信息:') print(df.head().to_csv(sep='\t', na_rep='nan')) + # 查看数据最后几行信息 + print('数据最后几行内容信息:') + print(df.tail().to_csv(sep='\t', na_rep='nan')) + # 查看数据的基本信息 print('数据基本信息:') df.info()