- 删除测试脚本: test_2026_classification.py, test_lagllama.py, test_timesfm.py, test_ttm.py - 删除2019相关文件: analyze_2019_*, apply_2019_*, dlinear_2019_*, three_models_2019_* - 添加集成模型相关代码和分析脚本 - 添加N-BEATS模型和CatBoost分类模型
460 lines
17 KiB
Python
460 lines
17 KiB
Python
"""分析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()
|