Initial commit: BTC 1h prediction model (53.10% accuracy)
🎯 项目成果: - 准确率: 53.10% (2026年测试集) - 任务类型: 二分类 (预测涨/跌) - 特征: OHLCV + RSI + MACD (9个特征) - 训练数据: 2020-2025年BTC小时数据 (52,351序列) - 测试数据: 2026年1-29日 (484序列) 📊 性能指标: - 精确率: 51.79% - 召回率: 49.36% - F1分数: 0.5054 - 预测分布: 非常平衡 (偏差仅-11个) 🔬 实验历史: - 尝试了5种方案 (回归/分类, 不同数据划分) - 分类任务优于回归任务 (53.10% vs 49.5%) - 全量训练避免了过拟合问题 📦 主要模型文件: - models/checkpoints/dlinear_classification_2026/ - best_model.pt (最佳模型) - preprocessor.pkl (预处理器) - test_2026_results.json (测试结果) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
9bbdc8d42b
82 files changed
+8742
No files matched your search
@@ -0,0 +1,232 @@
|
||||
"""在2026年数据上测试分类模型"""
|
||||
|
||||
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 sklearn.metrics import confusion_matrix, classification_report
|
||||
from data.fetcher import BinanceDataFetcher
|
||||
from models.dlinear.model import DLinear
|
||||
|
||||
class ClassificationPreprocessor:
|
||||
"""分类任务预处理器"""
|
||||
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:
|
||||
if not self.is_fitted:
|
||||
raise ValueError("Must fit before transform")
|
||||
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 main():
|
||||
print("="*80)
|
||||
print("2026年分类模型测试")
|
||||
print("="*80)
|
||||
|
||||
# ========== 加载模型 ==========
|
||||
print("\n[1/4] 加载模型...")
|
||||
model_path = './models/checkpoints/dlinear_classification_2026/best_model.pt'
|
||||
preprocessor_path = './models/checkpoints/dlinear_classification_2026/preprocessor.pkl'
|
||||
|
||||
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
|
||||
checkpoint = torch.load(model_path, 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(preprocessor_path, 'rb') as f:
|
||||
preprocessor = pickle.load(f)
|
||||
|
||||
print(f"✓ 模型加载成功")
|
||||
print(f"✓ 任务类型: 分类(预测涨/跌)")
|
||||
|
||||
# ========== 获取2026年数据 ==========
|
||||
print("\n[2/4] 获取2026年测试数据...")
|
||||
fetcher = BinanceDataFetcher()
|
||||
|
||||
try:
|
||||
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2026-01-01',
|
||||
end_date='2026-12-31', use_cache=False)
|
||||
except:
|
||||
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2025-12-01', use_cache=False)
|
||||
|
||||
print(f"✓ 测试数据: {len(df_2026)} 行")
|
||||
print(f"✓ 时间: {df_2026['timestamp'].min()} 至 {df_2026['timestamp'].max()}")
|
||||
print(f"✓ 价格: ${df_2026['close'].iloc[0]:,.2f} → ${df_2026['close'].iloc[-1]:,.2f}")
|
||||
|
||||
# ========== 预处理 ==========
|
||||
print("\n[3/4] 预处理并预测...")
|
||||
X_test, y_test, processed_df = preprocessor.preprocess(df_2026, fit=False)
|
||||
|
||||
if len(X_test) == 0:
|
||||
print("❌ 数据不足")
|
||||
return
|
||||
|
||||
print(f"✓ 测试序列: {len(X_test)}")
|
||||
|
||||
# 真实标签统计
|
||||
n_true_up = (y_test == 1).sum()
|
||||
n_true_down = (y_test == 0).sum()
|
||||
print(f"✓ 实际涨跌: 上涨{n_true_up}次 ({n_true_up/len(y_test)*100:.1f}%), "
|
||||
f"下跌{n_true_down}次 ({n_true_down/len(y_test)*100:.1f}%)")
|
||||
|
||||
# 模型预测
|
||||
X_tensor = torch.FloatTensor(X_test).to(device)
|
||||
with torch.no_grad():
|
||||
logits = model(X_tensor).squeeze().cpu().numpy()
|
||||
probs = 1 / (1 + np.exp(-logits)) # sigmoid
|
||||
preds = (probs > 0.5).astype(int)
|
||||
|
||||
# ========== 评估 ==========
|
||||
print("\n[4/4] 评估结果...")
|
||||
|
||||
# 基础统计
|
||||
n_pred_up = (preds == 1).sum()
|
||||
n_pred_down = (preds == 0).sum()
|
||||
accuracy = (preds == y_test).mean()
|
||||
|
||||
# 混淆矩阵
|
||||
cm = confusion_matrix(y_test, preds)
|
||||
tn, fp, fn, tp = cm.ravel()
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("📊 2026年测试结果(分类任务)")
|
||||
print("="*80)
|
||||
|
||||
print(f"\n预测统计:")
|
||||
print(f" 预测上涨: {n_pred_up} ({n_pred_up/len(preds)*100:.1f}%)")
|
||||
print(f" 预测下跌: {n_pred_down} ({n_pred_down/len(preds)*100:.1f}%)")
|
||||
|
||||
print(f"\n实际统计:")
|
||||
print(f" 实际上涨: {n_true_up} ({n_true_up/len(y_test)*100:.1f}%)")
|
||||
print(f" 实际下跌: {n_true_down} ({n_true_down/len(y_test)*100:.1f}%)")
|
||||
|
||||
print(f"\n✨ 准确率: {accuracy*100:.2f}%")
|
||||
|
||||
print(f"\n详细分析:")
|
||||
print(f" ✓ 预测涨且真涨 (TP): {tp} ({tp/len(preds)*100:.1f}%)")
|
||||
print(f" ✓ 预测跌且真跌 (TN): {tn} ({tn/len(preds)*100:.1f}%)")
|
||||
print(f" ✗ 预测涨但真跌 (FP): {fp} ({fp/len(preds)*100:.1f}%)")
|
||||
print(f" ✗ 预测跌但真涨 (FN): {fn} ({fn/len(preds)*100:.1f}%)")
|
||||
|
||||
# 精确率、召回率、F1
|
||||
precision = tp / (tp + fp) if (tp + fp) > 0 else 0
|
||||
recall = tp / (tp + fn) if (tp + fn) > 0 else 0
|
||||
f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
|
||||
|
||||
print(f"\n分类指标:")
|
||||
print(f" 精确率 (Precision): {precision*100:.2f}%")
|
||||
print(f" 召回率 (Recall): {recall*100:.2f}%")
|
||||
print(f" F1分数: {f1:.4f}")
|
||||
|
||||
# 预测偏差分析
|
||||
bias = n_pred_up - n_true_up
|
||||
print(f"\n预测偏差:")
|
||||
if abs(bias) < len(preds) * 0.05:
|
||||
print(f" ✅ 预测平衡(偏差{bias}个)")
|
||||
elif bias > 0:
|
||||
print(f" ⚠️ 偏向预测上涨(多预测{bias}次上涨)")
|
||||
else:
|
||||
print(f" ⚠️ 偏向预测下跌(多预测{-bias}次下跌)")
|
||||
|
||||
# 性能评估
|
||||
print("\n" + "="*80)
|
||||
print("🎯 性能评估")
|
||||
print("="*80)
|
||||
|
||||
if accuracy > 0.55:
|
||||
print(f"\n ✅ 准确率 {accuracy*100:.2f}% > 55%,模型有预测能力!")
|
||||
elif accuracy > 0.52:
|
||||
print(f"\n ⚠️ 准确率 {accuracy*100:.2f}% > 52%,略优于随机")
|
||||
elif accuracy >= 0.48:
|
||||
print(f"\n ⚠️ 准确率 {accuracy*100:.2f}% ≈ 50%,接近随机")
|
||||
else:
|
||||
print(f"\n ❌ 准确率 {accuracy*100:.2f}% < 48%,低于随机")
|
||||
|
||||
# 预测样本展示
|
||||
print("\n" + "="*80)
|
||||
print("📋 预测样本展示(前30个)")
|
||||
print("="*80)
|
||||
print(f"\n{'时间':<20} {'预测概率':<10} {'预测':<6} {'实际':<6} {'结果':<4}")
|
||||
print("-"*60)
|
||||
|
||||
for i in range(min(30, len(preds))):
|
||||
timestamp = processed_df['timestamp'].iloc[i + 168]
|
||||
prob = probs[i]
|
||||
pred = "上涨" if preds[i] == 1 else "下跌"
|
||||
true = "上涨" if y_test[i] == 1 else "下跌"
|
||||
result = "✓" if preds[i] == y_test[i] else "✗"
|
||||
|
||||
print(f"{str(timestamp):<20} {prob:>8.4f} {pred:<6} {true:<6} {result:<4}")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print(f"总结: 在{len(preds)}次预测中,准确率 {accuracy*100:.2f}%")
|
||||
print("="*80)
|
||||
|
||||
# 保存结果
|
||||
results = {
|
||||
'accuracy': float(accuracy),
|
||||
'precision': float(precision),
|
||||
'recall': float(recall),
|
||||
'f1_score': float(f1),
|
||||
'true_positives': int(tp),
|
||||
'true_negatives': int(tn),
|
||||
'false_positives': int(fp),
|
||||
'false_negatives': int(fn),
|
||||
'n_samples': int(len(preds))
|
||||
}
|
||||
|
||||
import json
|
||||
with open('./models/checkpoints/dlinear_classification_2026/test_2026_results.json', 'w') as f:
|
||||
json.dump(results, f, indent=2)
|
||||
|
||||
print(f"\n✓ 结果已保存至: ./models/checkpoints/dlinear_classification_2026/test_2026_results.json")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user