🎯 项目成果: - 准确率: 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>
264 lines
11 KiB
Python
264 lines
11 KiB
Python
"""使用全量训练模型预测2026年BTC价格涨跌"""
|
||
|
||
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
|
||
from data.fetcher import BinanceDataFetcher
|
||
from models.dlinear.model import DLinear
|
||
from training.evaluator import ModelEvaluator
|
||
|
||
def main():
|
||
print("="*80)
|
||
print("使用全量训练模型预测2026年BTC涨跌")
|
||
print("="*80)
|
||
|
||
# ========== 步骤1: 加载模型和预处理器 ==========
|
||
print("\n[1/6] 加载训练好的模型...")
|
||
model_path = './models/checkpoints/dlinear_btc_full_data/best_model.pt'
|
||
preprocessor_path = './models/checkpoints/dlinear_btc_full_data/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()
|
||
print(f"✓ 模型加载成功 (设备: {device})")
|
||
|
||
# 加载预处理器
|
||
with open(preprocessor_path, 'rb') as f:
|
||
preprocessor = pickle.load(f)
|
||
print(f"✓ 预处理器加载成功")
|
||
|
||
# ========== 步骤2: 获取2026年数据 ==========
|
||
print("\n[2/6] 获取2026年BTC数据...")
|
||
fetcher = BinanceDataFetcher()
|
||
|
||
try:
|
||
df_2026 = fetcher.fetch_ohlcv(
|
||
symbol='BTC',
|
||
timeframe='1h',
|
||
start_date='2026-01-01',
|
||
end_date='2026-12-31',
|
||
use_cache=False # 不使用缓存,获取最新数据
|
||
)
|
||
print(f"✓ 已获取 {len(df_2026)} 行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}")
|
||
|
||
total_return = (df_2026['close'].iloc[-1] - df_2026['close'].iloc[0]) / df_2026['close'].iloc[0]
|
||
print(f"✓ 2026年收益率:{total_return*100:+.2f}%")
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 无法获取2026年完整数据:{e}")
|
||
print(f"尝试获取2025年12月至今的最新数据...")
|
||
|
||
df_2026 = fetcher.fetch_ohlcv(
|
||
symbol='BTC',
|
||
timeframe='1h',
|
||
start_date='2025-12-01',
|
||
use_cache=False
|
||
)
|
||
print(f"✓ 已获取 {len(df_2026)} 行最新数据")
|
||
print(f"✓ 时间范围:{df_2026['timestamp'].min()} 至 {df_2026['timestamp'].max()}")
|
||
|
||
# ========== 步骤3: 数据预处理 ==========
|
||
print("\n[3/6] 预处理2026年数据...")
|
||
|
||
# 添加技术指标和时间特征
|
||
df_processed = preprocessor.add_technical_indicators(df_2026)
|
||
df_processed = preprocessor.add_time_features(df_processed)
|
||
df_processed = preprocessor.create_labels(df_processed, task='regression')
|
||
|
||
# 删除NaN行
|
||
df_processed = df_processed.dropna().reset_index(drop=True)
|
||
|
||
# 确保包含所有训练时的列
|
||
# 如果缺少某些列,用0填充
|
||
for col in preprocessor.feature_columns:
|
||
if col not in df_processed.columns:
|
||
df_processed[col] = 0
|
||
print(f" ⚠️ 添加缺失列:{col}")
|
||
|
||
# 使用训练时的scaler标准化特征
|
||
scaled_features = preprocessor.transform(df_processed)
|
||
|
||
# 创建序列
|
||
X, y = preprocessor.create_sequences(
|
||
scaled_features,
|
||
df_processed['label'].values
|
||
)
|
||
|
||
print(f"✓ 预测序列数量:{len(X)}")
|
||
print(f"✓ 序列形状:{X.shape}")
|
||
|
||
if len(X) == 0:
|
||
print("❌ 没有足够的数据创建序列,需要至少169小时的数据")
|
||
return
|
||
|
||
# ========== 步骤4: 模型预测 ==========
|
||
print("\n[4/6] 使用模型预测未来1小时涨跌...")
|
||
|
||
X_tensor = torch.FloatTensor(X).to(device)
|
||
|
||
with torch.no_grad():
|
||
predictions = model(X_tensor).squeeze().cpu().numpy()
|
||
|
||
print(f"✓ 预测完成,共 {len(predictions)} 个预测值")
|
||
print(f"✓ 预测收益率范围:{predictions.min()*100:.4f}% ~ {predictions.max()*100:.4f}%")
|
||
print(f"✓ 预测收益率均值:{predictions.mean()*100:.4f}%")
|
||
|
||
# ========== 步骤5: 涨跌判断 ==========
|
||
print("\n[5/6] 根据预测结果判断涨跌...")
|
||
|
||
# 预测方向:正值=上涨,负值=下跌
|
||
predicted_direction = (predictions > 0).astype(int) # 1=涨, 0=跌
|
||
actual_direction = (y > 0).astype(int)
|
||
|
||
n_predict_up = (predicted_direction == 1).sum()
|
||
n_predict_down = (predicted_direction == 0).sum()
|
||
n_actual_up = (actual_direction == 1).sum()
|
||
n_actual_down = (actual_direction == 0).sum()
|
||
|
||
print(f"\n📊 预测统计:")
|
||
print(f" 预测上涨:{n_predict_up} ({n_predict_up/len(predictions)*100:.1f}%)")
|
||
print(f" 预测下跌:{n_predict_down} ({n_predict_down/len(predictions)*100:.1f}%)")
|
||
|
||
print(f"\n📊 实际统计:")
|
||
print(f" 实际上涨:{n_actual_up} ({n_actual_up/len(y)*100:.1f}%)")
|
||
print(f" 实际下跌:{n_actual_down} ({n_actual_down/len(y)*100:.1f}%)")
|
||
|
||
# ========== 步骤6: 评估预测准确率 ==========
|
||
print("\n[6/6] 评估预测准确率...")
|
||
|
||
evaluator = ModelEvaluator(task='regression')
|
||
metrics = evaluator.evaluate(y, predictions)
|
||
|
||
print("\n" + "="*80)
|
||
print("2026年预测结果评估")
|
||
print("="*80)
|
||
|
||
print(f"\n📊 回归指标:")
|
||
print(f" RMSE (均方根误差): {metrics.get('rmse', 0)*100:.4f}%")
|
||
print(f" MAE (平均绝对误差): {metrics.get('mae', 0)*100:.4f}%")
|
||
print(f" MAPE (平均百分比误差): {metrics.get('mape', 0):.2f}%")
|
||
print(f" 方向准确率: {metrics.get('directional_accuracy', 0)*100:.2f}%")
|
||
|
||
# 混淆矩阵
|
||
from sklearn.metrics import confusion_matrix
|
||
cm = confusion_matrix(actual_direction, predicted_direction)
|
||
tn, fp, fn, tp = cm.ravel()
|
||
|
||
print(f"\n📈 混淆矩阵(方向预测):")
|
||
print(f" 真阳性(预测涨且真涨): {tp} ({tp/len(y)*100:.1f}%)")
|
||
print(f" 真阴性(预测跌且真跌): {tn} ({tn/len(y)*100:.1f}%)")
|
||
print(f" 假阳性(预测涨但真跌): {fp} ({fp/len(y)*100:.1f}%)")
|
||
print(f" 假阴性(预测跌但真涨): {fn} ({fn/len(y)*100:.1f}%)")
|
||
|
||
print(f"\n 精确率(预测涨的准确率): {tp/(tp+fp)*100 if (tp+fp)>0 else 0:.2f}%")
|
||
print(f" 召回率(实际涨被预测为涨): {tp/(tp+fn)*100 if (tp+fn)>0 else 0:.2f}%")
|
||
|
||
# 回测策略
|
||
backtest_metrics = evaluator.backtest_strategy(
|
||
y,
|
||
predictions,
|
||
initial_capital=10000.0,
|
||
transaction_cost=0.001
|
||
)
|
||
|
||
print(f"\n💰 回测结果(2026年):")
|
||
print(f" 总收益率: {backtest_metrics.get('total_return', 0)*100:.2f}%")
|
||
print(f" 最终资金: ${backtest_metrics.get('final_capital', 0):,.2f}")
|
||
print(f" 夏普比率: {backtest_metrics.get('sharpe_ratio', 0):.4f}")
|
||
max_dd = backtest_metrics.get('max_drawdown', 0)
|
||
if not np.isnan(max_dd):
|
||
print(f" 最大回撤: {max_dd*100:.2f}%")
|
||
print(f" 胜率: {backtest_metrics.get('win_rate', 0)*100:.2f}%")
|
||
print(f" 交易次数: {backtest_metrics.get('num_trades', 0):.0f}")
|
||
|
||
# Buy & Hold对比
|
||
buy_hold_return = (df_processed['close'].iloc[-1] - df_processed['close'].iloc[168]) / df_processed['close'].iloc[168]
|
||
print(f"\n📊 策略对比:")
|
||
print(f" 模型策略收益: {backtest_metrics.get('total_return', 0)*100:.2f}%")
|
||
print(f" Buy & Hold: {buy_hold_return*100:.2f}%")
|
||
|
||
if backtest_metrics.get('total_return', 0) > buy_hold_return:
|
||
print(f" ✅ 模型策略跑赢Buy & Hold {(backtest_metrics.get('total_return', 0) - buy_hold_return)*100:.2f}%")
|
||
else:
|
||
print(f" ❌ 模型策略跑输Buy & Hold {(buy_hold_return - backtest_metrics.get('total_return', 0))*100:.2f}%")
|
||
|
||
# 性能评估
|
||
print("\n" + "="*80)
|
||
print("🎯 性能评估")
|
||
print("="*80)
|
||
|
||
dir_acc = metrics.get('directional_accuracy', 0)
|
||
total_return = backtest_metrics.get('total_return', 0)
|
||
|
||
print(f"\n方向预测能力:")
|
||
if dir_acc > 0.55:
|
||
print(f" ✅ 方向准确率 {dir_acc*100:.1f}% > 55%,模型有预测能力")
|
||
elif dir_acc > 0.50:
|
||
print(f" ⚠️ 方向准确率 {dir_acc*100:.1f}% 略优于随机")
|
||
else:
|
||
print(f" ❌ 方向准确率 {dir_acc*100:.1f}% ≤ 50%,预测能力不足")
|
||
|
||
print(f"\n盈利能力:")
|
||
if total_return > 0.1:
|
||
print(f" ✅ 回测收益 {total_return*100:.2f}% > 10%,策略表现优秀")
|
||
elif total_return > 0:
|
||
print(f" ⚠️ 回测收益 {total_return*100:.2f}% > 0,策略有微弱盈利")
|
||
else:
|
||
print(f" ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损")
|
||
|
||
print(f"\n风险控制:")
|
||
sharpe = backtest_metrics.get('sharpe_ratio', 0)
|
||
if sharpe > 1.0:
|
||
print(f" ✅ 夏普比率 {sharpe:.2f} > 1.0,风险调整后收益优秀")
|
||
elif sharpe > 0.5:
|
||
print(f" ⚠️ 夏普比率 {sharpe:.2f},风险调整后收益一般")
|
||
else:
|
||
print(f" ❌ 夏普比率 {sharpe:.2f} < 0.5,风险调整后收益较差")
|
||
|
||
# 详细预测样本展示
|
||
print("\n" + "="*80)
|
||
print("📋 部分预测样本展示(前20个)")
|
||
print("="*80)
|
||
print(f"\n{'时间':<20} {'预测收益率':<12} {'实际收益率':<12} {'预测方向':<8} {'实际方向':<8} {'准确':<4}")
|
||
print("-"*80)
|
||
|
||
for i in range(min(20, len(predictions))):
|
||
pred_val = predictions[i]
|
||
actual_val = y[i]
|
||
pred_dir = "上涨" if pred_val > 0 else "下跌"
|
||
actual_dir = "上涨" if actual_val > 0 else "下跌"
|
||
correct = "✓" if (pred_val > 0) == (actual_val > 0) else "✗"
|
||
|
||
timestamp = df_processed['timestamp'].iloc[i + 168] # +168因为lookback
|
||
print(f"{str(timestamp):<20} {pred_val*100:>10.4f}% {actual_val*100:>10.4f}% {pred_dir:<8} {actual_dir:<8} {correct:<4}")
|
||
|
||
print("\n" + "="*80)
|
||
print("✓ 2026年预测评估完成!")
|
||
print("="*80)
|
||
|
||
print("\n💡 总结:")
|
||
print(f" - 在2026年 {len(predictions)} 个小时的预测中")
|
||
print(f" - 方向准确率达到 {dir_acc*100:.2f}%")
|
||
print(f" - 策略回测收益 {total_return*100:.2f}%(Buy & Hold: {buy_hold_return*100:.2f}%)")
|
||
|
||
if dir_acc > 0.52 and total_return > 0:
|
||
print(f"\n ✅ 模型在2026年表现良好,可以考虑实盘小仓位测试")
|
||
elif dir_acc > 0.50:
|
||
print(f"\n ⚠️ 模型预测能力有限,建议谨慎使用")
|
||
else:
|
||
print(f"\n ❌ 模型在2026年预测失败,不建议实盘使用")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|