"""分析2019年和2026年1月准确率<40%交易日的前一天技术特征""" import sys import os sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import torch import pickle import numpy as np import pandas as pd import pandas_ta as ta from sklearn.preprocessing import StandardScaler from data.fetcher import BinanceDataFetcher from datetime import datetime, timedelta # ==================== 预处理器定义 ==================== class ClassificationPreprocessor: """DLinear分类预处理器""" def __init__(self, lookback=168, forecast_horizon=1): self.lookback = lookback self.forecast_horizon = forecast_horizon self.scaler = StandardScaler() self.is_fitted = False def add_features(self, df): df = df.copy() df['rsi'] = ta.rsi(df['close'], length=14) macd = ta.macd(df['close']) if macd is not None and not macd.empty: df['macd'] = macd.iloc[:, 0] df['macd_hist'] = macd.iloc[:, 1] df['macd_signal'] = macd.iloc[:, 2] return df def create_labels(self, df): df = df.copy() df['future_price'] = df['close'].shift(-self.forecast_horizon) df['future_return'] = (df['future_price'] - df['close']) / df['close'] df['label'] = (df['future_return'] > 0).astype(int) return df[:-self.forecast_horizon] def preprocess(self, df, fit=True): df = self.add_features(df) df = self.create_labels(df) df = df.dropna().reset_index(drop=True) feature_cols = ['open', 'high', 'low', 'close', 'volume', 'rsi', 'macd', 'macd_hist', 'macd_signal'] if fit: scaled_features = self.scaler.fit_transform(df[feature_cols]) self.is_fitted = True self.feature_columns = feature_cols else: scaled_features = self.scaler.transform(df[feature_cols]) X, y = [], [] for i in range(len(scaled_features) - self.lookback): X.append(scaled_features[i:i + self.lookback]) y.append(df['label'].iloc[i + self.lookback]) return np.array(X), np.array(y), df def calculate_technical_features(df): """计算丰富的技术特征""" df = df.copy() # 基础价格特征 df['price_change'] = df['close'] - df['open'] df['price_change_pct'] = (df['close'] - df['open']) / df['open'] * 100 df['high_low_range'] = df['high'] - df['low'] df['high_low_range_pct'] = (df['high'] - df['low']) / df['low'] * 100 df['body_ratio'] = abs(df['close'] - df['open']) / (df['high'] - df['low'] + 1e-10) # 移动平均线 df['sma_5'] = ta.sma(df['close'], length=5) df['sma_10'] = ta.sma(df['close'], length=10) df['sma_20'] = ta.sma(df['close'], length=20) df['sma_50'] = ta.sma(df['close'], length=50) # 价格相对MA位置 df['price_vs_sma5'] = (df['close'] - df['sma_5']) / df['sma_5'] * 100 df['price_vs_sma20'] = (df['close'] - df['sma_20']) / df['sma_20'] * 100 # RSI df['rsi_14'] = ta.rsi(df['close'], length=14) df['rsi_7'] = ta.rsi(df['close'], length=7) # MACD macd = ta.macd(df['close']) if macd is not None and not macd.empty: df['macd'] = macd.iloc[:, 0] df['macd_hist'] = macd.iloc[:, 1] df['macd_signal'] = macd.iloc[:, 2] # 布林带 bbands = ta.bbands(df['close'], length=20) if bbands is not None and not bbands.empty: df['bb_upper'] = bbands.iloc[:, 0] df['bb_mid'] = bbands.iloc[:, 1] df['bb_lower'] = bbands.iloc[:, 2] df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_mid'] * 100 df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'] + 1e-10) * 100 # ATR波动率 df['atr_14'] = ta.atr(df['high'], df['low'], df['close'], length=14) df['atr_pct'] = df['atr_14'] / df['close'] * 100 # 成交量特征 df['volume_sma_20'] = ta.sma(df['volume'], length=20) df['volume_ratio'] = df['volume'] / (df['volume_sma_20'] + 1e-10) # 价格动量 df['momentum_1'] = df['close'].pct_change(1) * 100 df['momentum_3'] = df['close'].pct_change(3) * 100 df['momentum_7'] = df['close'].pct_change(7) * 100 df['momentum_24'] = df['close'].pct_change(24) * 100 # 波动率 df['volatility_5'] = df['close'].pct_change().rolling(5).std() * 100 df['volatility_10'] = df['close'].pct_change().rolling(10).std() * 100 df['volatility_20'] = df['close'].pct_change().rolling(20).std() * 100 # 连续涨跌天数 df['is_up'] = (df['close'] > df['open']).astype(int) df['consecutive_days'] = 0 return df def analyze_period(model, preprocessor, device, period_name, start_date, end_date): """分析单个时期的数据,返回低准确率日期""" print(f"\n{'='*80}") print(f"分析 {period_name}") print(f"{'='*80}") # 获取数据(需要额外获取前一天的数据) fetcher = BinanceDataFetcher() # 获取包含前一天的数据 start_dt = pd.to_datetime(start_date) - pd.Timedelta(days=2) df = fetcher.fetch_ohlcv('BTC', '1h', start_date=start_dt.strftime('%Y-%m-%d'), end_date=end_date, use_cache=False) print(f"✓ 数据: {len(df)} 行") # 预处理 X, y, df_processed = preprocessor.preprocess(df, fit=False) print(f"✓ 有效样本: {len(X)}") # DLinear预测 X_tensor = torch.FloatTensor(X).to(device) with torch.no_grad(): logits = model(X_tensor).cpu().numpy() probs = 1 / (1 + np.exp(-logits)) preds = (probs > 0.5).astype(int).flatten() true_labels = y # 构建每日结果 timestamps = df['timestamp'].iloc[168:168+len(X)].values timestamps_utc = pd.to_datetime(timestamps) timestamps_et = timestamps_utc.tz_localize('UTC').tz_convert('America/New_York') dates = timestamps_et.date results_df = pd.DataFrame({ 'date': dates, 'timestamp_et': timestamps_et, 'correct': preds == true_labels, }) # 按天统计准确率 daily_stats = results_df.groupby('date').agg({ 'correct': ['count', 'sum'] }) daily_stats.columns = ['total', 'correct'] daily_stats['accuracy'] = daily_stats['correct'] / daily_stats['total'] * 100 # 找出准确率<40%的日期 low_acc_dates = daily_stats[daily_stats['accuracy'] < 40].index.tolist() print(f"✓ 准确率<40%的日期: {len(low_acc_dates)}天") for date in low_acc_dates: acc = daily_stats.loc[date, 'accuracy'] print(f" {date}: {acc:.1f}%") # 计算技术特征 df_with_features = calculate_technical_features(df) # 为每天聚合技术特征(使用当天的24小时数据) # 先确保timestamp是datetime类型 if not pd.api.types.is_datetime64_any_dtype(df_with_features['timestamp']): df_with_features['timestamp'] = pd.to_datetime(df_with_features['timestamp']) df_with_features['date'] = df_with_features['timestamp'].dt.tz_localize('UTC').tz_convert('America/New_York').dt.date # 返回低准确率日期和完整数据 return low_acc_dates, df_with_features, daily_stats def main(): print("="*80) print("分析准确率<40%交易日的前一天技术特征共同点") print("="*80) # ========== 加载模型 ========== print("\n[1/3] 加载DLinear模型...") device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') from models.dlinear.model import DLinear checkpoint = torch.load( './models/checkpoints/dlinear_classification_2026/best_model.pt', map_location=device, weights_only=False ) model = DLinear(checkpoint['config']) model.load_state_dict(checkpoint['model_state_dict']) model = model.to(device) model.eval() with open('./models/checkpoints/dlinear_classification_2026/preprocessor.pkl', 'rb') as f: preprocessor = pickle.load(f) print("✓ DLinear模型已加载") # ========== 分析两个时期 ========== print("\n[2/3] 分析2019年和2026年1月...") # 分析2019年1月 low_acc_2019, df_2019, stats_2019 = analyze_period( model, preprocessor, device, "2019年1月", "2019-01-01", "2019-01-31" ) # 分析2026年1月 low_acc_2026, df_2026, stats_2026 = analyze_period( model, preprocessor, device, "2026年1月", "2026-01-01", "2026-01-31" ) # ========== 提取前一天特征 ========== print("\n[3/3] 分析前一天技术特征...") # 合并所有低准确率日期 all_low_acc_dates = low_acc_2019 + low_acc_2026 print(f"\n总计{len(all_low_acc_dates)}个低准确率交易日") # 提取前一天的特征 prev_day_features = [] for date in all_low_acc_dates: # 获取前一天日期 prev_date = date - timedelta(days=1) # 确定使用哪个数据集 if date in low_acc_2019: df = df_2019 period = "2019年" else: df = df_2026 period = "2026年" # 获取前一天的数据 prev_day_data = df[df['date'] == prev_date] if len(prev_day_data) == 0: continue # 聚合前一天的技术特征(使用24小时的平均值、最大值、最小值等) features = { 'date': date, 'prev_date': prev_date, 'period': period, 'accuracy': stats_2019.loc[date, 'accuracy'] if date in low_acc_2019 else stats_2026.loc[date, 'accuracy'], # 价格特征 'prev_close': prev_day_data['close'].iloc[-1], 'prev_open': prev_day_data['open'].iloc[0], 'prev_price_change_pct': prev_day_data['price_change_pct'].mean(), 'prev_high_low_range_pct': prev_day_data['high_low_range_pct'].mean(), 'prev_body_ratio': prev_day_data['body_ratio'].mean(), # RSI 'prev_rsi_14': prev_day_data['rsi_14'].iloc[-1], 'prev_rsi_7': prev_day_data['rsi_7'].iloc[-1], # MACD 'prev_macd': prev_day_data['macd'].iloc[-1] if 'macd' in prev_day_data.columns else None, 'prev_macd_hist': prev_day_data['macd_hist'].iloc[-1] if 'macd_hist' in prev_day_data.columns else None, # 布林带 'prev_bb_width': prev_day_data['bb_width'].iloc[-1] if 'bb_width' in prev_day_data.columns else None, 'prev_bb_position': prev_day_data['bb_position'].iloc[-1] if 'bb_position' in prev_day_data.columns else None, # 波动率 'prev_atr_pct': prev_day_data['atr_pct'].iloc[-1] if 'atr_pct' in prev_day_data.columns else None, 'prev_volatility_5': prev_day_data['volatility_5'].iloc[-1] if 'volatility_5' in prev_day_data.columns else None, 'prev_volatility_20': prev_day_data['volatility_20'].iloc[-1] if 'volatility_20' in prev_day_data.columns else None, # 成交量 'prev_volume_ratio': prev_day_data['volume_ratio'].iloc[-1] if 'volume_ratio' in prev_day_data.columns else None, # 动量 'prev_momentum_1': prev_day_data['momentum_1'].iloc[-1] if 'momentum_1' in prev_day_data.columns else None, 'prev_momentum_3': prev_day_data['momentum_3'].iloc[-1] if 'momentum_3' in prev_day_data.columns else None, 'prev_momentum_7': prev_day_data['momentum_7'].iloc[-1] if 'momentum_7' in prev_day_data.columns else None, 'prev_momentum_24': prev_day_data['momentum_24'].iloc[-1] if 'momentum_24' in prev_day_data.columns else None, # 价格相对MA位置 'prev_price_vs_sma5': prev_day_data['price_vs_sma5'].iloc[-1] if 'price_vs_sma5' in prev_day_data.columns else None, 'prev_price_vs_sma20': prev_day_data['price_vs_sma20'].iloc[-1] if 'price_vs_sma20' in prev_day_data.columns else None, } prev_day_features.append(features) # 转换为DataFrame features_df = pd.DataFrame(prev_day_features) # ========== 分析共同点 ========== print("\n" + "="*80) print("📊 前一天技术特征统计分析") print("="*80) print(f"\n分析样本数: {len(features_df)}个低准确率交易日") print(f" 2019年: {(features_df['period']=='2019年').sum()}天") print(f" 2026年: {(features_df['period']=='2026年').sum()}天") # 统计各特征的平均值、中位数、标准差 numeric_cols = [col for col in features_df.columns if col.startswith('prev_') and features_df[col].dtype in [np.float64, np.int64]] print(f"\n前一天技术特征统计:") print(f"{'特征':<30} {'平均值':>10} {'中位数':>10} {'标准差':>10} {'共同点':<30}") print("-" * 95) for col in numeric_cols: if features_df[col].notna().sum() > 0: mean_val = features_df[col].mean() median_val = features_df[col].median() std_val = features_df[col].std() # 判断共同点 common_trait = "" if 'rsi' in col: if mean_val > 70: common_trait = "超买区域" elif mean_val < 30: common_trait = "超卖区域" elif 45 <= mean_val <= 55: common_trait = "中性区域" elif 'volatility' in col: if mean_val > 3: common_trait = "高波动" elif mean_val < 1: common_trait = "低波动" elif 'momentum' in col: if mean_val > 2: common_trait = "强势上涨" elif mean_val < -2: common_trait = "强势下跌" elif abs(mean_val) < 0.5: common_trait = "横盘震荡" elif 'bb_position' in col: if mean_val > 80: common_trait = "接近布林上轨" elif mean_val < 20: common_trait = "接近布林下轨" elif 'volume_ratio' in col: if mean_val > 1.5: common_trait = "成交量放大" elif mean_val < 0.8: common_trait = "成交量萎缩" print(f"{col:<30} {mean_val:>10.2f} {median_val:>10.2f} {std_val:>10.2f} {common_trait:<30}") # ========== 对比分析 ========== print("\n" + "="*80) print("🔍 2019年 vs 2026年对比分析") print("="*80) features_2019 = features_df[features_df['period'] == '2019年'] features_2026 = features_df[features_df['period'] == '2026年'] if len(features_2019) > 0 and len(features_2026) > 0: print(f"\n{'特征':<30} {'2019年平均':>12} {'2026年平均':>12} {'差异':>10}") print("-" * 70) for col in numeric_cols[:15]: # 显示前15个特征 if features_2019[col].notna().sum() > 0 and features_2026[col].notna().sum() > 0: mean_2019 = features_2019[col].mean() mean_2026 = features_2026[col].mean() diff = mean_2026 - mean_2019 print(f"{col:<30} {mean_2019:>12.2f} {mean_2026:>12.2f} {diff:>10.2f}") # ========== 关键共同点总结 ========== print("\n" + "="*80) print("💡 关键共同点总结") print("="*80) # RSI分析 rsi_mean = features_df['prev_rsi_14'].mean() print(f"\n1. RSI指标:") print(f" 平均RSI: {rsi_mean:.1f}") if rsi_mean > 70: print(f" 🔴 共同点: 前一天处于超买状态") elif rsi_mean < 30: print(f" 🟢 共同点: 前一天处于超卖状态") else: print(f" ➖ 共同点: 前一天RSI在中性区域") # 波动率分析 vol_mean = features_df['prev_volatility_20'].mean() print(f"\n2. 波动率:") print(f" 平均波动率: {vol_mean:.2f}%") if vol_mean > 3: print(f" ⚠️ 共同点: 前一天波动率较高") elif vol_mean < 1: print(f" ✅ 共同点: 前一天波动率较低") # 动量分析 mom_24_mean = features_df['prev_momentum_24'].mean() print(f"\n3. 24小时动量:") print(f" 平均动量: {mom_24_mean:.2f}%") if abs(mom_24_mean) > 5: print(f" 🚀 共同点: 前一天有强趋势({'+' if mom_24_mean > 0 else ''}{mom_24_mean:.1f}%)") else: print(f" 🔄 共同点: 前一天横盘震荡") # 布林带位置 bb_pos_mean = features_df['prev_bb_position'].mean() print(f"\n4. 布林带位置:") print(f" 平均位置: {bb_pos_mean:.1f}%") if bb_pos_mean > 80: print(f" 🔴 共同点: 前一天价格接近布林上轨") elif bb_pos_mean < 20: print(f" 🟢 共同点: 前一天价格接近布林下轨") else: print(f" ➖ 共同点: 前一天价格在布林带中部") # 成交量 vol_ratio_mean = features_df['prev_volume_ratio'].mean() print(f"\n5. 成交量:") print(f" 平均成交量比: {vol_ratio_mean:.2f}x") if vol_ratio_mean > 1.5: print(f" 📈 共同点: 前一天成交量显著放大") elif vol_ratio_mean < 0.8: print(f" 📉 共同点: 前一天成交量萎缩") print("\n" + "="*80) print("✓ 技术特征分析完成!") print("="*80) # 保存详细数据 output_file = "./results/low_accuracy_prev_day_features.csv" os.makedirs("./results", exist_ok=True) features_df.to_csv(output_file, index=False) print(f"\n✓ 详细数据已保存至: {output_file}") if __name__ == "__main__": main()