diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 8140830..cd13860 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,9 @@ "Bash(/Users/xxz/miniforge3/bin/conda run:*)", "Bash(tee:*)", "Bash(python:*)", - "Bash(source:*)" + "Bash(source:*)", + "Bash(ls:*)", + "Bash(git add:*)" ] } } diff --git a/analyze_split.py b/analyze_split.py deleted file mode 100644 index be93460..0000000 --- a/analyze_split.py +++ /dev/null @@ -1,195 +0,0 @@ -"""详细分析训练/验证/测试集的分配方式""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import pandas as pd -import numpy as np -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data - -def analyze_split_strategy(): - print("="*80) - print("数据集分割策略详细分析") - print("="*80) - - # 获取数据 - print("\n[1/4] 加载2020-2025年完整数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 原始数据: {len(df)} 行") - print(f"✓ 时间范围: {df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 预处理 - print("\n[2/4] 数据预处理...") - preprocessor = CryptoPreprocessor( - lookback=168, - forecast_horizon=1, - price_change_threshold=0.0 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - - print(f"✓ 序列数量: {len(X)}") - print(f"✓ 每个序列: {X.shape[1]}小时 × {X.shape[2]}特征") - - # 分析split_data函数的逻辑 - print("\n[3/4] 分析数据分割逻辑...") - print(f"\n📋 split_data函数的分割方式:") - print(f" train_split = 0.7 (70%)") - print(f" val_split = 0.15 (15%)") - print(f" test_split = 0.15 (15%)") - print(f"\n 分割方式: 时间序列顺序分割(非随机打乱)") - print(f" 计算方式:") - print(f" n = len(X) # 总序列数") - print(f" train_end = int(n * 0.7)") - print(f" val_end = int(n * 0.85)") - print(f" 训练集: X[0:train_end]") - print(f" 验证集: X[train_end:val_end]") - print(f" 测试集: X[val_end:]") - - # 执行分割 - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - n = len(X) - train_end_idx = int(n * 0.7) - val_end_idx = int(n * 0.85) - - print(f"\n[4/4] 详细分割结果分析...") - print(f"\n总序列数: {n:,}") - print(f"分割点:") - print(f" 训练集结束: 第 {train_end_idx:,} 个序列") - print(f" 验证集结束: 第 {val_end_idx:,} 个序列") - - # 计算每个数据集对应的原始数据索引 - # 注意:由于lookback=168,序列创建时有偏移 - lookback = 168 - - # 训练集对应的数据范围 - train_start_row = lookback # 第一个序列从lookback开始 - train_end_row = train_end_idx + lookback - - # 验证集对应的数据范围 - val_start_row = train_end_idx + lookback - val_end_row = val_end_idx + lookback - - # 测试集对应的数据范围 - test_start_row = val_end_idx + lookback - test_end_row = len(processed_df) - - print(f"\n📊 各数据集对应的原始数据行:") - print(f"\n训练集:") - print(f" 序列索引: 0 到 {train_end_idx:,}") - print(f" 数据行范围: {train_start_row:,} 到 {train_end_row:,}") - train_df = processed_df.iloc[train_start_row:train_end_row] - print(f" 时间范围: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" 价格: ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {train_return*100:+.2f}%") - - print(f"\n验证集:") - print(f" 序列索引: {train_end_idx:,} 到 {val_end_idx:,}") - print(f" 数据行范围: {val_start_row:,} 到 {val_end_row:,}") - val_df = processed_df.iloc[val_start_row:val_end_row] - print(f" 时间范围: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" 价格: ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {val_return*100:+.2f}%") - - print(f"\n测试集:") - print(f" 序列索引: {val_end_idx:,} 到 {n:,}") - print(f" 数据行范围: {test_start_row:,} 到 {test_end_row:,}") - test_df = processed_df.iloc[test_start_row:test_end_row] - print(f" 时间范围: {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - test_return = (test_df['close'].iloc[-1] - test_df['close'].iloc[0]) / test_df['close'].iloc[0] - print(f" 价格: ${test_df['close'].iloc[0]:,.0f} → ${test_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {test_return*100:+.2f}%") - - # 可视化时间线 - print(f"\n" + "="*80) - print("📅 时间线可视化") - print("="*80) - - print(f"\n2020━━━━━━━━━━━━训练集━━━━━━━━━━━━━2024━━验证━━2025━━测试━━2025") - print(f" ") - print(f"牛市+825% 牛市+34% 下跌-8.5%") - print(f" ") - - # 分析问题 - print(f"\n" + "="*80) - print("🔍 关键发现") - print("="*80) - - print(f"\n✅ 分割方式:正确") - print(f" - 时间序列顺序分割(未打乱)") - print(f" - 训练在前,验证在中,测试在后") - print(f" - 避免了数据泄露") - - print(f"\n⚠️ 问题:分布严重不匹配") - print(f" - 训练集(2020-2024初):+825% 🚀 极端牛市") - print(f" - 验证集(2024): +34% 📈 牛市延续") - print(f" - 测试集(2025): -8.5% 📉 回调下跌") - - print(f"\n💡 这导致:") - print(f" 1. 模型在牛市中训练 → 学会'牛市思维'") - print(f" 2. 验证集也是牛市 → 验证损失正常,未能发现问题") - print(f" 3. 测试集却是熊市 → 泛化性能差") - - # 标签分布分析 - print(f"\n📊 标签分布分析:") - train_up_ratio = (train_data[1] == 1).sum() / len(train_data[1]) - val_up_ratio = (val_data[1] == 1).sum() / len(val_data[1]) - test_up_ratio = (test_data[1] == 1).sum() / len(test_data[1]) - - print(f"\n 训练集上涨比例: {train_up_ratio*100:.1f}%") - print(f" 验证集上涨比例: {val_up_ratio*100:.1f}%") - print(f" 测试集上涨比例: {test_up_ratio*100:.1f}%") - - if abs(train_up_ratio - test_up_ratio) > 0.05: - print(f"\n ⚠️ 训练集和测试集的上涨比例差异: {abs(train_up_ratio - test_up_ratio)*100:.1f}%") - print(f" 这说明分布不一致!") - - # 建议 - print(f"\n" + "="*80) - print("💡 解决方案建议") - print("="*80) - - print(f"\n方案1: Walk-Forward验证(滚动窗口)⭐⭐⭐⭐⭐") - print(f" - 不是70/15/15一次性分割") - print(f" - 而是多次滚动分割:") - print(f" 第1次: 2020-2022训练 → 2023Q1测试") - print(f" 第2次: 2020-2023训练 → 2023Q2测试") - print(f" 第3次: 2020-2023训练 → 2023Q3测试") - print(f" ...") - print(f" - 平均所有测试结果") - - print(f"\n方案2: K-Fold时间序列交叉验证 ⭐⭐⭐⭐") - print(f" - TimeSeriesSplit (保持时间顺序)") - print(f" - 多个fold,每个fold包含不同市场环境") - - print(f"\n方案3: 分层采样(按市场环境)⭐⭐⭐") - print(f" - 识别牛市/熊市/震荡期") - print(f" - 确保训练/验证/测试都包含各种环境") - print(f" - 但要保持时间顺序") - - print(f"\n方案4: 使用更近期的数据作为测试集 ⭐⭐") - print(f" - 例如:2020-2024训练 → 2025测试") - print(f" - 目前就是这样,但问题是2025恰好是下跌期") - - print(f"\n当前问题的本质:") - print(f" ❌ 不是分割方法的问题") - print(f" ❌ 而是时间序列预测的固有难题:") - print(f" '过去的牛市'无法预测'未来的熊市'") - print(f" 除非训练数据包含足够多的熊市样本") - - print("\n" + "="*80) - -if __name__ == "__main__": - analyze_split_strategy() diff --git a/compare_tasks.py b/compare_tasks.py deleted file mode 100644 index 9d1883e..0000000 --- a/compare_tasks.py +++ /dev/null @@ -1,244 +0,0 @@ -"""详细对比二分类和回归任务在加密货币预测中的差异""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import numpy as np -import pandas as pd -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor - -def compare_classification_vs_regression(): - print("="*80) - print("二分类 vs 回归任务详细对比") - print("="*80) - - # 获取少量数据用于演示 - print("\n[1/3] 获取示例数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - df_sample = df.tail(200).copy() # 只取最近200小时 - - print(f"✓ 样本数据: {len(df_sample)} 行") - print(f"✓ 时间范围: {df_sample['timestamp'].min()} 至 {df_sample['timestamp'].max()}") - - # 对比两种任务 - print("\n[2/3] 对比两种任务的标签生成...") - - # 二分类任务 - print("\n" + "="*80) - print("📊 任务1: 二分类(Binary Classification)") - print("="*80) - - preprocessor_cls = CryptoPreprocessor( - lookback=24, - forecast_horizon=1, - price_change_threshold=0.0 - ) - - # 创建标签 - df_sample['future_price'] = df_sample['close'].shift(-1) - df_sample['future_return'] = (df_sample['future_price'] - df_sample['close']) / df_sample['close'] - df_sample['label_classification'] = (df_sample['future_return'] > 0).astype(int) - - print(f"\n目标: 预测价格会上涨(1)还是下跌(0)") - print(f"\n标签生成公式:") - print(f" future_return = (未来价格 - 当前价格) / 当前价格") - print(f" label = 1 if future_return > 0 else 0") - - print(f"\n具体例子(前10个样本):") - print(f"{'时间':<20} {'当前价格':<12} {'未来价格':<12} {'收益率':<10} {'标签':<8}") - print("-"*80) - for i in range(10): - row = df_sample.iloc[i] - if pd.notna(row['future_return']): - label = "上涨(1)" if row['label_classification'] == 1 else "下跌(0)" - print(f"{str(row['timestamp']):<20} ${row['close']:<11,.2f} ${row['future_price']:<11,.2f} " - f"{row['future_return']*100:>7.2f}% {label:<8}") - - print(f"\n标签统计:") - n_up = df_sample['label_classification'].sum() - n_down = len(df_sample) - n_up - df_sample['label_classification'].isna().sum() - print(f" 上涨(1): {n_up} 个") - print(f" 下跌(0): {n_down} 个") - print(f" 比例: {n_up/(n_up+n_down)*100:.1f}% vs {n_down/(n_up+n_down)*100:.1f}%") - - print(f"\n模型输出:") - print(f" 格式: 0-1之间的概率值") - print(f" 例如: 0.73 表示 73% 概率上涨") - print(f" 决策: 如果概率 > 0.5 → 预测上涨") - print(f" 如果概率 < 0.5 → 预测下跌") - - print(f"\n评估指标:") - print(f" - 准确率 (Accuracy)") - print(f" - 精确率 (Precision)") - print(f" - 召回率 (Recall)") - print(f" - F1分数") - print(f" - 混淆矩阵") - - print(f"\n优点:") - print(f" ✅ 简单直观(涨/跌二选一)") - print(f" ✅ 评估指标清晰") - print(f" ✅ 适合交易决策(做多/做空)") - - print(f"\n缺点:") - print(f" ❌ 丢失了涨跌幅度信息") - print(f" ❌ 涨0.1%和涨10%被视为一样") - print(f" ❌ 无法评估风险收益比") - - # 回归任务 - print("\n" + "="*80) - print("📈 任务2: 回归(Regression)") - print("="*80) - - df_sample['label_regression'] = df_sample['future_return'] - - print(f"\n目标: 预测价格变化的百分比") - print(f"\n标签生成公式:") - print(f" label = (未来价格 - 当前价格) / 当前价格") - print(f" 直接预测收益率(可正可负)") - - print(f"\n具体例子(前10个样本):") - print(f"{'时间':<20} {'当前价格':<12} {'未来价格':<12} {'收益率(标签)':<15}") - print("-"*80) - for i in range(10): - row = df_sample.iloc[i] - if pd.notna(row['future_return']): - print(f"{str(row['timestamp']):<20} ${row['close']:<11,.2f} ${row['future_price']:<11,.2f} " - f"{row['label_regression']*100:>12.4f}%") - - print(f"\n标签统计:") - valid_returns = df_sample['label_regression'].dropna() - print(f" 最小值: {valid_returns.min()*100:.4f}%") - print(f" 最大值: {valid_returns.max()*100:.4f}%") - print(f" 均值: {valid_returns.mean()*100:.4f}%") - print(f" 标准差: {valid_returns.std()*100:.4f}%") - print(f" 中位数: {valid_returns.median()*100:.4f}%") - - print(f"\n模型输出:") - print(f" 格式: 连续的数值(收益率)") - print(f" 例如: 0.0235 表示预测上涨 2.35%") - print(f" -0.0178 表示预测下跌 1.78%") - print(f" 决策: 根据预测值大小决定仓位") - print(f" 预测+5% → 大仓位做多") - print(f" 预测+0.5% → 小仓位做多") - - print(f"\n评估指标:") - print(f" - RMSE (均方根误差)") - print(f" - MAE (平均绝对误差)") - print(f" - MAPE (平均绝对百分比误差)") - print(f" - 方向准确率 (预测方向是否正确)") - print(f" - R² (决定系数)") - - print(f"\n优点:") - print(f" ✅ 保留了涨跌幅度信息") - print(f" ✅ 可以评估风险收益比") - print(f" ✅ 支持动态仓位管理") - print(f" ✅ 提供更精细的预测") - - print(f"\n缺点:") - print(f" ❌ 预测难度更高") - print(f" ❌ 容易过拟合") - print(f" ❌ 对异常值敏感") - - # 对比分析 - print("\n[3/3] 详细对比分析...") - print("\n" + "="*80) - print("🔍 详细对比") - print("="*80) - - comparison = [ - ["维度", "二分类", "回归"], - ["-"*30, "-"*40, "-"*40], - ["预测目标", "方向(涨/跌)", "具体涨跌幅度"], - ["输出范围", "0-1(概率)", "-∞ to +∞(收益率)"], - ["输出示例", "0.73(73%概率上涨)", "+0.0235(预测涨2.35%)"], - ["标签类型", "离散(0或1)", "连续(实数)"], - ["损失函数", "BCEWithLogitsLoss", "MSELoss / MAELoss"], - ["评估指标", "准确率、F1分数", "RMSE、MAE、MAPE"], - ["决策方式", "二元(做多/做空)", "连续(仓位大小)"], - ["信息量", "少(只有方向)", "多(方向+幅度)"], - ["预测难度", "中等", "困难"], - ["适用场景", "简单交易策略", "精细化资金管理"], - ] - - for row in comparison: - print(f"{row[0]:<30} {row[1]:<40} {row[2]:<40}") - - # 实际应用对比 - print("\n" + "="*80) - print("💰 实际交易应用对比") - print("="*80) - - print("\n场景1: 预测未来1小时BTC会上涨") - print("\n二分类模型:") - print(" 输出: 0.68 (68%概率上涨)") - print(" 决策: 概率>0.5,买入BTC") - print(" 行动: 全仓做多(或固定仓位如50%)") - print(" 问题: 不知道预期涨多少,无法评估风险") - - print("\n回归模型:") - print(" 输出: +0.0023 (预测上涨0.23%)") - print(" 决策: 预测值>0,买入BTC") - print(" 行动: 根据预测幅度调整仓位(0.23%很小,可能只用10%仓位)") - print(" 优势: 知道预期收益,可以计算风险收益比") - - print("\n场景2: 预测未来1小时BTC大涨") - print("\n二分类模型:") - print(" 输出: 0.72 (72%概率上涨)") - print(" 决策: 买入BTC") - print(" 问题: 72%概率可能是涨0.1%,也可能是涨5%,无法区分") - - print("\n回归模型:") - print(" 输出: +0.0480 (预测上涨4.8%)") - print(" 决策: 买入BTC") - print(" 行动: 预期涨幅大,可以加大仓位(如80%)") - print(" 优势: 可以根据预期收益调整风险敞口") - - # 性能对比 - print("\n" + "="*80) - print("📊 当前项目的实际性能对比") - print("="*80) - - print("\n二分类任务(当前已实现):") - print(" 准确率: 49.76%(接近随机)") - print(" F1分数: 0.3898") - print(" 召回率: 31.85%") - print(" 问题: 预测能力极弱") - - print("\n回归任务(未实现):") - print(" 预期性能: 可能更差") - print(" 原因: 预测具体数值比预测方向更难") - print(" 但优势: 即使方向准确率50%,如果能准确预测幅度,仍可盈利") - - # 建议 - print("\n" + "="*80) - print("💡 建议") - print("="*80) - - print("\n选择二分类的情况:") - print(" ✅ 初学者或概念验证") - print(" ✅ 简单的做多/做空策略") - print(" ✅ 预测周期较长(1天以上)") - print(" ✅ 只关心方向,不关心幅度") - - print("\n选择回归的情况:") - print(" ✅ 需要精细化资金管理") - print(" ✅ 需要评估风险收益比") - print(" ✅ 有足够多的训练数据") - print(" ✅ 想要更丰富的预测信息") - - print("\n对于1小时BTC预测:") - print(" 当前结论: 两者都很难!") - print(" 二分类: 准确率49.76%(已验证)") - print(" 回归: 预计MAPE会很高(未验证,但大概率也很差)") - print("\n 核心问题: 1小时价格变化接近随机游走") - print(" 建议: 延长预测周期到24小时或更长") - - print("\n" + "="*80) - print("✓ 对比分析完成") - print("="*80) - -if __name__ == "__main__": - compare_classification_vs_regression() diff --git a/diagnose_bias.py b/diagnose_bias.py deleted file mode 100644 index 0e81e5b..0000000 --- a/diagnose_bias.py +++ /dev/null @@ -1,234 +0,0 @@ -"""诊断模型为何倾向预测上涨""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import torch -import numpy as np -import pandas as pd -from models.dlinear.model import DLinear -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders - -def analyze_bias(): - print("="*80) - print("分析模型预测偏差的原因") - print("="*80) - - # 1. 加载数据 - print("\n[1/5] 加载数据并分析标签分布...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - - # 分析原始价格趋势 - df['price_change'] = df['close'].pct_change() - df['future_1h'] = df['close'].shift(-1) - df['future_return_1h'] = (df['future_1h'] - df['close']) / df['close'] - - print(f"\n📊 原始数据分析(最近8760小时):") - print(f" 数据范围: {df['timestamp'].min()} 至 {df['timestamp'].max()}") - print(f" BTC起始价格: ${df['close'].iloc[0]:,.2f}") - print(f" BTC结束价格: ${df['close'].iloc[-1]:,.2f}") - total_return = (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0] - print(f" 总收益率: {total_return*100:.2f}%") - - up_hours = (df['future_return_1h'] > 0).sum() - down_hours = (df['future_return_1h'] <= 0).sum() - total_hours = up_hours + down_hours - print(f"\n 小时K线涨跌统计:") - print(f" 上涨小时: {up_hours}/{total_hours} ({up_hours/total_hours*100:.1f}%)") - print(f" 下跌小时: {down_hours}/{total_hours} ({down_hours/total_hours*100:.1f}%)") - - # 2. 预处理并查看训练集分布 - print("\n[2/5] 分析训练/验证/测试集的标签分布...") - preprocessor = CryptoPreprocessor( - lookback=168, - forecast_horizon=1, - price_change_threshold=0.0 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - # 分析每个数据集的标签分布 - train_up = (train_data[1] == 1).sum() - train_down = (train_data[1] == 0).sum() - val_up = (val_data[1] == 1).sum() - val_down = (val_data[1] == 0).sum() - test_up = (test_data[1] == 1).sum() - test_down = (test_data[1] == 0).sum() - - print(f"\n 训练集标签分布:") - print(f" 上涨: {train_up}/{len(train_data[1])} ({train_up/len(train_data[1])*100:.1f}%)") - print(f" 下跌: {train_down}/{len(train_data[1])} ({train_down/len(train_data[1])*100:.1f}%)") - - print(f"\n 验证集标签分布:") - print(f" 上涨: {val_up}/{len(val_data[1])} ({val_up/len(val_data[1])*100:.1f}%)") - print(f" 下跌: {val_down}/{len(val_data[1])} ({val_down/len(val_data[1])*100:.1f}%)") - - print(f"\n 测试集标签分布:") - print(f" 上涨: {test_up}/{len(test_data[1])} ({test_up/len(test_data[1])*100:.1f}%)") - print(f" 下跌: {test_down}/{len(test_data[1])} ({test_down/len(test_data[1])*100:.1f}%)") - - # 3. 分析训练集对应的时间范围 - print("\n[3/5] 分析各数据集的时间范围和价格趋势...") - - # 由于序列创建,需要偏移lookback - lookback = 168 - train_end_idx = int(len(processed_df) * 0.7) - lookback - val_end_idx = int(len(processed_df) * 0.85) - lookback - - train_df = processed_df.iloc[lookback:train_end_idx+lookback] - val_df = processed_df.iloc[train_end_idx+lookback:val_end_idx+lookback] - test_df = processed_df.iloc[val_end_idx+lookback:] - - print(f"\n 训练集:") - print(f" 时间范围: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" 价格变化: ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {train_return*100:.2f}%") - - print(f"\n 验证集:") - print(f" 时间范围: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" 价格变化: ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {val_return*100:.2f}%") - - print(f"\n 测试集:") - print(f" 时间范围: {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - test_return = (test_df['close'].iloc[-1] - test_df['close'].iloc[0]) / test_df['close'].iloc[0] - print(f" 价格变化: ${test_df['close'].iloc[0]:,.0f} → ${test_df['close'].iloc[-1]:,.0f}") - print(f" 收益率: {test_return*100:.2f}%") - - # 4. 加载模型并分析输出 - print("\n[4/5] 加载模型并分析输出分布...") - model_path = './models/checkpoints/dlinear_btc_1h_forecast/best_model.pt' - 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() - - # 分析测试集输出 - _, _, test_loader = create_dataloaders( - test_data, test_data, test_data, - task='classification', - batch_size=64 - ) - - all_logits = [] - all_labels = [] - - with torch.no_grad(): - for batch_X, batch_y in test_loader: - batch_X = batch_X.to(device) - outputs = model(batch_X).squeeze() - all_logits.extend(outputs.cpu().numpy().flatten()) - all_labels.extend(batch_y.numpy().flatten()) - - all_logits = np.array(all_logits) - all_labels = np.array(all_labels) - all_probs = 1 / (1 + np.exp(-all_logits)) # sigmoid - - print(f"\n 模型输出统计:") - print(f" Logits均值: {all_logits.mean():.6f}") - print(f" Logits标准差: {all_logits.std():.6f}") - print(f" Logits中位数: {np.median(all_logits):.6f}") - print(f" 概率均值: {all_probs.mean():.6f}") - - # 按真实标签分组分析 - logits_when_up = all_logits[all_labels == 1] - logits_when_down = all_logits[all_labels == 0] - - print(f"\n 按真实标签分组:") - print(f" 真实上涨时,平均logit: {logits_when_up.mean():.6f}") - print(f" 真实下跌时,平均logit: {logits_when_down.mean():.6f}") - print(f" 差值: {logits_when_up.mean() - logits_when_down.mean():.6f}") - - # 5. 分析为什么偏向上涨 - print("\n[5/5] 分析预测偏差的根本原因...") - print("\n" + "="*80) - print("🔍 根本原因分析") - print("="*80) - - # 原因1: 数据趋势 - if total_return > 0.1: - print(f"\n✅ 原因1: 训练数据处于上涨趋势") - print(f" - 整体收益率: {total_return*100:.2f}%") - print(f" - 训练集收益率: {train_return*100:.2f}%") - print(f" - 模型学习到了'牛市偏好'") - elif total_return < -0.1: - print(f"\n⚠️ 原因1: 训练数据处于下跌趋势") - print(f" - 整体收益率: {total_return*100:.2f}%") - else: - print(f"\n⚪ 原因1: 训练数据趋势中性") - print(f" - 整体收益率: {total_return*100:.2f}%") - - # 原因2: 类别不平衡 - if train_up > train_down * 1.05: - print(f"\n✅ 原因2: 训练集标签不平衡") - print(f" - 上涨样本: {train_up} ({train_up/len(train_data[1])*100:.1f}%)") - print(f" - 下跌样本: {train_down} ({train_down/len(train_data[1])*100:.1f}%)") - print(f" - 不平衡比例: {train_up/train_down:.2f}:1") - else: - print(f"\n⚪ 原因2: 训练集标签基本平衡") - print(f" - 上涨/下跌比例: {train_up/train_down:.2f}:1") - - # 原因3: 模型输出偏差 - if all_logits.mean() > 0.01: - print(f"\n✅ 原因3: 模型输出存在正偏差") - print(f" - 平均logit: {all_logits.mean():.6f} > 0") - print(f" - 平均概率: {all_probs.mean():.6f} > 0.5") - print(f" - 这导致更多样本被预测为上涨") - elif all_logits.mean() < -0.01: - print(f"\n⚠️ 原因3: 模型输出存在负偏差") - print(f" - 平均logit: {all_logits.mean():.6f} < 0") - else: - print(f"\n⚪ 原因3: 模型输出基本无偏") - print(f" - 平均logit: {all_logits.mean():.6f} ≈ 0") - - # 原因4: 阈值问题 - print(f"\n⚪ 原因4: 决策阈值") - print(f" - 当前阈值: 0.5") - print(f" - 平均概率: {all_probs.mean():.6f}") - if all_probs.mean() > 0.5: - print(f" - 因为平均概率>0.5,导致多数样本被预测为上涨") - - # 计算最优阈值 - from sklearn.metrics import f1_score - best_threshold = 0.5 - best_f1 = 0 - for threshold in np.arange(0.3, 0.7, 0.01): - preds = (all_probs > threshold).astype(int) - f1 = f1_score(all_labels, preds) - if f1 > best_f1: - best_f1 = f1 - best_threshold = threshold - - print(f"\n💡 建议调整阈值:") - print(f" - 最优阈值: {best_threshold:.3f} (F1={best_f1:.4f})") - print(f" - 当前阈值: 0.500 (F1={f1_score(all_labels, (all_probs>0.5).astype(int)):.4f})") - - print("\n" + "="*80) - print("📌 总结") - print("="*80) - print(f"\n模型预测上涨的比例: {(all_probs > 0.5).sum()}/{len(all_probs)} ({(all_probs>0.5).mean()*100:.1f}%)") - print(f"主要原因:") - if total_return > 0.1: - print(f" 1. ✅ 训练数据处于上涨趋势 ({total_return*100:.1f}%)") - if train_up > train_down * 1.05: - print(f" 2. ✅ 训练集上涨样本更多 ({train_up/train_down:.2f}:1)") - if all_logits.mean() > 0.01: - print(f" 3. ✅ 模型学习到了上涨偏好 (平均logit={all_logits.mean():.4f})") - - print(f"\n这是正常现象,因为:") - print(f" - 如果训练期间BTC整体上涨,模型会学习到'上涨更可能'") - print(f" - 这类似于'在牛市训练的模型会更乐观'") - print(f" - 解决方案:使用更长的训练数据(包含牛市和熊市)") - -if __name__ == "__main__": - analyze_bias() diff --git a/diagnose_model.py b/diagnose_model.py deleted file mode 100644 index 089779e..0000000 --- a/diagnose_model.py +++ /dev/null @@ -1,177 +0,0 @@ -"""诊断模型预测偏差问题""" - -import torch -import numpy as np -from models.dlinear.model import DLinear, DLinearConfig -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -import matplotlib -matplotlib.use('Agg') -import matplotlib.pyplot as plt - -def diagnose_model(): - """诊断模型输出分布和损失函数问题""" - - print("="*80) - print("模型诊断报告") - print("="*80) - - # 1. 加载已训练的模型 - print("\n[1/5] 加载已训练模型...") - model_path = './models/checkpoints/dlinear_btc_1h_balanced/best_model.pt' - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - - checkpoint = torch.load(model_path, map_location=device, weights_only=False) - config = checkpoint['config'] - - model = DLinear(config) - model.load_state_dict(checkpoint['model_state_dict']) - model = model.to(device) - model.eval() - - print(f"✓ 模型已加载到设备: {device}") - print(f"✓ 模型配置: task={config.task}, input_dim={config.input_dim}") - - # 2. 准备测试数据 - print("\n[2/5] 准备测试数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - - preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24, price_change_threshold=0.0) - X, y, _ = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - - _, _, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - _, _, test_loader = create_dataloaders(test_data, test_data, test_data, batch_size=64) - - # 3. 分析模型输出 - print("\n[3/5] 分析模型原始输出...") - all_outputs = [] - all_labels = [] - - with torch.no_grad(): - for batch_X, batch_y in test_loader: - batch_X = batch_X.to(device) - outputs = model(batch_X).squeeze() - - all_outputs.extend(outputs.cpu().numpy().flatten()) - all_labels.extend(batch_y.numpy().flatten()) - - all_outputs = np.array(all_outputs) - all_labels = np.array(all_labels) - - print(f"\n📊 模型输出统计(经过Sigmoid):") - print(f" 最小值: {all_outputs.min():.6f}") - print(f" 最大值: {all_outputs.max():.6f}") - print(f" 均值: {all_outputs.mean():.6f}") - print(f" 中位数: {np.median(all_outputs):.6f}") - print(f" 标准差: {all_outputs.std():.6f}") - print(f" > 0.5 的比例: {(all_outputs > 0.5).mean()*100:.2f}%") - print(f" > 0.4 的比例: {(all_outputs > 0.4).mean()*100:.2f}%") - print(f" > 0.3 的比例: {(all_outputs > 0.3).mean()*100:.2f}%") - print(f" > 0.2 的比例: {(all_outputs > 0.2).mean()*100:.2f}%") - print(f" > 0.1 的比例: {(all_outputs > 0.1).mean()*100:.2f}%") - - print(f"\n📊 真实标签分布:") - print(f" 下跌(0): {(all_labels == 0).sum()} ({(all_labels == 0).mean()*100:.1f}%)") - print(f" 上涨(1): {(all_labels == 1).sum()} ({(all_labels == 1).mean()*100:.1f}%)") - - # 4. 检查损失函数问题 - print("\n[4/5] 检查损失函数兼容性...") - print("\n⚠️ 发现问题:") - print(" 模型使用: nn.Sigmoid() 作为输出激活函数") - print(" 当前损失函数: BCEWithLogitsLoss (期望logits输入)") - print("\n🔴 这导致了【双重Sigmoid问题】:") - print(" 1. 模型输出层: output = sigmoid(logits)") - print(" 2. 损失函数内部: sigmoid(output) = sigmoid(sigmoid(logits))") - print(" 3. 结果: 输出被过度压缩到接近0,梯度消失") - - # 验证双重sigmoid效果 - print("\n🧪 验证双重Sigmoid效果:") - test_logits = np.array([-2, -1, 0, 1, 2]) - single_sigmoid = 1 / (1 + np.exp(-test_logits)) - double_sigmoid = 1 / (1 + np.exp(-single_sigmoid)) - - print(" 原始logits: ", test_logits) - print(" 单次sigmoid: ", np.round(single_sigmoid, 4)) - print(" 双重sigmoid: ", np.round(double_sigmoid, 4)) - print(" → 双重sigmoid后,所有值都接近0.5(失去区分度)") - - # 5. 建议修复方案 - print("\n[5/5] 修复方案:") - print("\n✅ 方案1(推荐):移除模型输出层的Sigmoid") - print(" - 修改 models/dlinear/model.py:128") - print(" - 将 self.output_activation = nn.Sigmoid() 改为 nn.Identity()") - print(" - 保持损失函数为 BCEWithLogitsLoss") - - print("\n✅ 方案2:更换损失函数") - print(" - 修改 models/base.py:74") - print(" - 将 BCEWithLogitsLoss 改为 BCELoss") - print(" - 保持模型输出层的 Sigmoid") - - print("\n📌 其他发现:") - print(f" - 模型平均输出 {all_outputs.mean():.1%},远低于期望的50%") - print(f" - 几乎没有输出>0.5({(all_outputs>0.5).sum()}个)") - print(f" - 这解释了为什么模型从不预测上涨") - - # 6. 生成可视化 - print("\n[6/5] 生成输出分布图...") - fig, axes = plt.subplots(2, 2, figsize=(12, 10)) - - # 输出直方图 - axes[0, 0].hist(all_outputs, bins=50, alpha=0.7, edgecolor='black') - axes[0, 0].axvline(0.5, color='red', linestyle='--', label='决策阈值=0.5') - axes[0, 0].set_xlabel('模型输出(经过Sigmoid)') - axes[0, 0].set_ylabel('频数') - axes[0, 0].set_title('模型输出分布(双重Sigmoid问题)') - axes[0, 0].legend() - axes[0, 0].grid(alpha=0.3) - - # 按真实标签分组的输出分布 - outputs_down = all_outputs[all_labels == 0] - outputs_up = all_outputs[all_labels == 1] - axes[0, 1].hist([outputs_down, outputs_up], bins=30, alpha=0.6, - label=['真实下跌', '真实上涨'], edgecolor='black') - axes[0, 1].axvline(0.5, color='red', linestyle='--', label='决策阈值') - axes[0, 1].set_xlabel('模型输出') - axes[0, 1].set_ylabel('频数') - axes[0, 1].set_title('按真实标签分组的输出分布') - axes[0, 1].legend() - axes[0, 1].grid(alpha=0.3) - - # CDF(累积分布) - sorted_outputs = np.sort(all_outputs) - cdf = np.arange(1, len(sorted_outputs)+1) / len(sorted_outputs) - axes[1, 0].plot(sorted_outputs, cdf, linewidth=2) - axes[1, 0].axvline(0.5, color='red', linestyle='--', label='决策阈值=0.5') - axes[1, 0].axhline(0.5, color='green', linestyle='--', alpha=0.5) - axes[1, 0].set_xlabel('模型输出') - axes[1, 0].set_ylabel('累积概率') - axes[1, 0].set_title('输出累积分布函数(CDF)') - axes[1, 0].legend() - axes[1, 0].grid(alpha=0.3) - - # 双重sigmoid效果演示 - x = np.linspace(-5, 5, 100) - sigmoid_1 = 1 / (1 + np.exp(-x)) - sigmoid_2 = 1 / (1 + np.exp(-sigmoid_1)) - axes[1, 1].plot(x, sigmoid_1, label='单次Sigmoid', linewidth=2) - axes[1, 1].plot(x, sigmoid_2, label='双重Sigmoid', linewidth=2, linestyle='--') - axes[1, 1].axhline(0.5, color='red', linestyle=':', alpha=0.5, label='决策阈值') - axes[1, 1].set_xlabel('输入(logits)') - axes[1, 1].set_ylabel('输出') - axes[1, 1].set_title('双重Sigmoid效果对比') - axes[1, 1].legend() - axes[1, 1].grid(alpha=0.3) - - plt.tight_layout() - output_path = 'model_diagnosis.png' - plt.savefig(output_path, dpi=150, bbox_inches='tight') - print(f"✓ 可视化已保存至: {output_path}") - - print("\n" + "="*80) - print("诊断完成!") - print("="*80) - -if __name__ == "__main__": - diagnose_model() diff --git a/diagnose_simple.py b/diagnose_simple.py deleted file mode 100644 index 41f825b..0000000 --- a/diagnose_simple.py +++ /dev/null @@ -1,134 +0,0 @@ -"""简化版模型诊断(无需matplotlib)""" - -import torch -import numpy as np -from models.dlinear.model import DLinear, DLinearConfig -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders - -def diagnose(): - print("="*80) - print("🔍 模型预测偏差诊断报告") - print("="*80) - - # 加载模型 - print("\n[1/4] 加载已训练模型...") - model_path = './models/checkpoints/dlinear_btc_1h_balanced/best_model.pt' - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - checkpoint = torch.load(model_path, map_location=device, weights_only=False) - - config = checkpoint['config'] - model = DLinear(config) - model.load_state_dict(checkpoint['model_state_dict']) - model = model.to(device) - model.eval() - print(f"✓ 模型配置: task={config.task}, hidden_dim={config.hidden_dim}") - - # 准备数据 - print("\n[2/4] 加载测试数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24, price_change_threshold=0.0) - X, y, _ = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - _, _, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - _, _, test_loader = create_dataloaders(test_data, test_data, test_data, batch_size=64) - - # 收集输出 - print("\n[3/4] 分析模型输出...") - all_outputs = [] - all_labels = [] - - with torch.no_grad(): - for batch_X, batch_y in test_loader: - batch_X = batch_X.to(device) - outputs = model(batch_X).squeeze() - all_outputs.extend(outputs.cpu().numpy().flatten()) - all_labels.extend(batch_y.numpy().flatten()) - - all_outputs = np.array(all_outputs) - all_labels = np.array(all_labels) - - # 统计分析 - print("\n" + "="*80) - print("📊 关键发现") - print("="*80) - - print(f"\n1️⃣ 模型输出统计(已经过Sigmoid):") - print(f" 最小值: {all_outputs.min():.6f}") - print(f" 最大值: {all_outputs.max():.6f}") - print(f" 均值: {all_outputs.mean():.6f} ← 应该接近0.5") - print(f" 中位数: {np.median(all_outputs):.6f}") - print(f" 标准差: {all_outputs.std():.6f}") - - print(f"\n2️⃣ 预测阈值分析:") - for threshold in [0.5, 0.4, 0.3, 0.2, 0.1]: - count = (all_outputs > threshold).sum() - pct = (all_outputs > threshold).mean() * 100 - print(f" 输出 > {threshold}: {count:4d} 个 ({pct:5.2f}%)") - - print(f"\n3️⃣ 真实标签分布:") - n_down = (all_labels == 0).sum() - n_up = (all_labels == 1).sum() - print(f" 下跌(0): {n_down} ({n_down/len(all_labels)*100:.1f}%)") - print(f" 上涨(1): {n_up} ({n_up/len(all_labels)*100:.1f}%)") - - # 验证双重sigmoid问题 - print("\n" + "="*80) - print("🔴 问题诊断:双重Sigmoid Bug") - print("="*80) - - print("\n📋 当前架构:") - print(" 模型输出层: self.output_activation = nn.Sigmoid() (models/dlinear/model.py:128)") - print(" 损失函数: BCEWithLogitsLoss() (models/base.py:74)") - - print("\n⚠️ 问题:") - print(" BCEWithLogitsLoss 内部包含 Sigmoid,期望输入是 logits(未激活)") - print(" 但模型已经输出了 sigmoid(logits)") - print(" → 损失函数再次应用: sigmoid(sigmoid(logits)) ← 双重激活!") - - print("\n🧪 双重Sigmoid效果演示:") - test_logits = np.array([-3, -1, 0, 1, 3]) - single = 1 / (1 + np.exp(-test_logits)) - double = 1 / (1 + np.exp(-single)) - - print(f" 原始logits: {test_logits}") - print(f" 单次sigmoid: {np.round(single, 4)}") - print(f" 双重sigmoid: {np.round(double, 4)} ← 全部压缩到~0.5") - - print("\n💥 后果:") - print(f" - 模型输出均值仅 {all_outputs.mean():.1%}(期望~50%)") - print(f" - {(all_outputs>0.5).sum()} 个样本输出>0.5(共{len(all_outputs)}个)") - print(f" - 模型永远预测类别0(下跌),因为没有输出超过阈值0.5") - print(f" - 梯度消失,模型无法有效学习") - - # 解决方案 - print("\n" + "="*80) - print("✅ 修复方案") - print("="*80) - - print("\n方案1(推荐):移除模型的Sigmoid输出") - print(" 文件: models/dlinear/model.py") - print(" 位置: 第127-130行") - print(" 修改前: if config.task == 'classification':") - print(" self.output_activation = nn.Sigmoid()") - print(" 修改后: if config.task == 'classification':") - print(" self.output_activation = nn.Identity() # 输出logits") - print(" 说明: 保持损失函数为BCEWithLogitsLoss") - - print("\n方案2:更换损失函数(不推荐)") - print(" 文件: models/base.py") - print(" 位置: 第70-76行") - print(" 修改: 将 BCEWithLogitsLoss 改为 BCELoss") - print(" 说明: 保持模型输出Sigmoid,但BCELoss数值稳定性较差") - - print("\n" + "="*80) - print("📌 结论") - print("="*80) - print("✓ 预测偏差的根本原因是【双重Sigmoid Bug】") - print("✓ 与数据处理、类别平衡、特征工程无关") - print("✓ 修复后需要重新训练模型") - print("="*80) - -if __name__ == "__main__": - diagnose() diff --git a/evaluate_fixed.py b/evaluate_fixed.py deleted file mode 100644 index f2949a0..0000000 --- a/evaluate_fixed.py +++ /dev/null @@ -1,180 +0,0 @@ -"""重新评估已训练模型(使用修复后的evaluator)""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import torch -import numpy as np -from models.dlinear.model import DLinear -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from training.evaluator import evaluate_model -import json - -def main(): - print("="*80) - print("重新评估模型(已修复Sigmoid Bug)") - print("="*80) - - # 加载模型 - print("\n[1/4] 加载已训练的模型...") - model_path = './models/checkpoints/dlinear_btc_1h_forecast/best_model.pt' - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - - checkpoint = torch.load(model_path, map_location=device, weights_only=False) - config = checkpoint['config'] - - model = DLinear(config) - model.load_state_dict(checkpoint['model_state_dict']) - model = model.to(device) - model.eval() - - print(f"✓ 模型已加载: {model_path}") - print(f"✓ 设备: {device}") - print(f"✓ 模型参数: {model.count_parameters():,}") - - # 准备测试数据 - print("\n[2/4] 准备测试数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - - preprocessor = CryptoPreprocessor( - lookback=168, - forecast_horizon=1, # 预测未来1小时 - price_change_threshold=0.0 - ) - X, y, _ = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - - _, _, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - _, _, test_loader = create_dataloaders( - test_data, test_data, test_data, - task='classification', - batch_size=64 - ) - - print(f"✓ 测试集大小: {len(test_data[0])} 样本") - print(f"✓ 真实标签分布: 下跌={np.sum(test_data[1]==0)}, 上涨={np.sum(test_data[1]==1)}") - - # 先查看模型输出分布 - print("\n[3/4] 分析模型输出分布...") - all_logits = [] - all_probs = [] - all_preds = [] - all_labels = [] - - with torch.no_grad(): - for batch_X, batch_y in test_loader: - batch_X = batch_X.to(device) - outputs = model(batch_X).squeeze() - - # 模型输出logits - logits = outputs.cpu().numpy() - probs = torch.sigmoid(outputs).cpu().numpy() - preds = (probs > 0.5).astype(int) - - all_logits.extend(logits.flatten()) - all_probs.extend(probs.flatten()) - all_preds.extend(preds.flatten()) - all_labels.extend(batch_y.numpy().flatten()) - - all_logits = np.array(all_logits) - all_probs = np.array(all_probs) - all_preds = np.array(all_preds) - all_labels = np.array(all_labels) - - print(f"\n📊 模型输出统计(修复后):") - print(f" Logits范围: [{all_logits.min():.4f}, {all_logits.max():.4f}]") - print(f" Logits均值: {all_logits.mean():.4f}") - print(f" 概率范围: [{all_probs.min():.4f}, {all_probs.max():.4f}]") - print(f" 概率均值: {all_probs.mean():.4f} ← 应该接近0.5") - print(f" 预测为上涨: {np.sum(all_preds==1)}/{len(all_preds)} ({np.sum(all_preds==1)/len(all_preds)*100:.1f}%)") - print(f" 预测为下跌: {np.sum(all_preds==0)}/{len(all_preds)} ({np.sum(all_preds==0)/len(all_preds)*100:.1f}%)") - - # 正式评估 - print("\n[4/4] 使用修复后的evaluator进行完整评估...") - metrics = evaluate_model(model, test_loader, task='classification', device=str(device)) - - # 保存结果 - results_path = './models/checkpoints/dlinear_btc_1h_forecast/test_metrics_fixed.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 展示结果 - print("\n" + "="*80) - print("最终评估结果(修复后)") - print("="*80) - - print(f"\n📊 分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f} ({metrics.get('accuracy', 0)*100:.2f}%)") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n📈 混淆矩阵:") - tp = metrics['true_positives'] - tn = metrics['true_negatives'] - fp = metrics['false_positives'] - fn = metrics['false_negatives'] - print(f" 真阳性(预测涨且真涨): {tp}") - print(f" 真阴性(预测跌且真跌): {tn}") - print(f" 假阳性(预测涨但真跌): {fp}") - print(f" 假阴性(预测跌但真涨): {fn}") - - total = tp + tn + fp + fn - if total > 0: - print(f"\n 预测分布:") - print(f" 预测上涨: {tp + fp} ({(tp+fp)/total*100:.1f}%)") - print(f" 预测下跌: {tn + fn} ({(tn+fn)/total*100:.1f}%)") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - max_dd = metrics.get('backtest_max_drawdown', 0) - if np.isnan(max_dd): - print(f" 最大回撤: N/A (无交易)") - else: - print(f" 最大回撤: {max_dd*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 性能评估 - print(f"\n🎯 性能评估:") - acc = metrics.get('accuracy', 0) - if acc > 0.55: - print(f" ✅ 准确率 {acc*100:.1f}% > 55%,模型有一定预测能力") - elif acc > 0.50: - print(f" ⚠️ 准确率 {acc*100:.1f}% 略优于随机(50%),预测能力较弱") - else: - print(f" ❌ 准确率 {acc*100:.1f}% ≤ 50%,模型预测能力不足") - - total_return = metrics.get('backtest_total_return', 0) - if total_return > 0.05: - print(f" ✅ 回测收益 {total_return*100:.2f}% > 5%,策略有较好盈利潜力") - elif total_return > 0: - print(f" ⚠️ 回测收益 {total_return*100:.2f}% > 0,策略有微弱盈利") - else: - print(f" ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损") - - # 对比修复前后 - print(f"\n📊 修复前后对比:") - print(f" 修复前(有Bug):") - print(f" - 平均置信度: 0.69% (错误)") - print(f" - 预测上涨: 0个 (0%)") - print(f" - 准确率: 50.00% (偏差)") - print(f" 修复后(正确):") - print(f" - 平均置信度: {metrics.get('mean_confidence', 0)*100:.1f}%") - print(f" - 预测上涨: {tp + fp}个 ({(tp+fp)/total*100:.1f}%)") - print(f" - 准确率: {acc*100:.2f}%") - - print("\n" + "="*80) - print("✓ 评估完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/evaluate_model.py b/evaluate_model.py deleted file mode 100644 index 2cc0a65..0000000 --- a/evaluate_model.py +++ /dev/null @@ -1,81 +0,0 @@ -"""评估已训练的DLinear模型""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import torch -import json -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear -from training.evaluator import evaluate_model - -def main(): - print("="*80) - print("评估DLinear模型(真实数据)") - print("="*80) - - # 加载数据 - print("\n加载数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - - preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24) - X, y, _ = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - - train_data, val_data, test_data = split_data(X, y) - _, _, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', batch_size=64 - ) - - # 加载模型 - print("加载最佳模型...") - model_path = './models/checkpoints/dlinear_btc_1h_real/best_model.pt' - checkpoint = torch.load(model_path, map_location='cpu') - model = DLinear(checkpoint['config']) - model.load_state_dict(checkpoint['model_state_dict']) - - # 评估 - print("\n在测试集上评估...") - device = 'mps' if torch.backends.mps.is_available() else 'cpu' - metrics = evaluate_model(model, test_loader, 'classification', device) - - # 保存结果 - results_path = './models/checkpoints/dlinear_btc_1h_real/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - # 打印结果 - print("\n" + "="*80) - print("测试集评估结果") - print("="*80) - print(f"\n📊 分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f}") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n📈 混淆矩阵:") - print(f" 真阳性: {metrics['true_positives']}") - print(f" 真阴性: {metrics['true_negatives']}") - print(f" 假阳性: {metrics['false_positives']}") - print(f" 假阴性: {metrics['false_negatives']}") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - print(f" 最大回撤: {metrics.get('backtest_max_drawdown', 0)*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - print("\n" + "="*80) - print(f"✓ 结果已保存至:{results_path}") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/models/checkpoints/cross_asset_btc_to_eth/best_model.pt b/models/checkpoints/cross_asset_btc_to_eth/best_model.pt deleted file mode 100644 index 01567ba..0000000 Binary files a/models/checkpoints/cross_asset_btc_to_eth/best_model.pt and /dev/null differ diff --git a/models/checkpoints/cross_asset_btc_to_eth/final_model.pt b/models/checkpoints/cross_asset_btc_to_eth/final_model.pt deleted file mode 100644 index ba5ef51..0000000 Binary files a/models/checkpoints/cross_asset_btc_to_eth/final_model.pt and /dev/null differ diff --git a/models/checkpoints/cross_asset_btc_to_eth/test_metrics.json b/models/checkpoints/cross_asset_btc_to_eth/test_metrics.json deleted file mode 100644 index bfc23d8..0000000 --- a/models/checkpoints/cross_asset_btc_to_eth/test_metrics.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mse": 5.495949517353438e-05, - "rmse": 0.007413467149285439, - "mae": 0.004826944321393967, - "mape": 930.6216430664062, - "directional_accuracy": 0.4907678594167834, - "backtest_total_return": -0.9745769500732422, - "backtest_final_capital": 254.23023986816406, - "backtest_sharpe_ratio": -0.26884937315369317, - "backtest_max_drawdown": -0.978744387626648, - "backtest_win_rate": 0.47166687889978354, - "backtest_num_trades": 3251 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_811_split/best_model.pt b/models/checkpoints/dlinear_811_split/best_model.pt deleted file mode 100644 index 33c07cb..0000000 Binary files a/models/checkpoints/dlinear_811_split/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_811_split/final_model.pt b/models/checkpoints/dlinear_811_split/final_model.pt deleted file mode 100644 index 1fc8fba..0000000 Binary files a/models/checkpoints/dlinear_811_split/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_811_split/preprocessor.pkl b/models/checkpoints/dlinear_811_split/preprocessor.pkl deleted file mode 100644 index f4397f2..0000000 Binary files a/models/checkpoints/dlinear_811_split/preprocessor.pkl and /dev/null differ diff --git a/models/checkpoints/dlinear_811_split/test_metrics.json b/models/checkpoints/dlinear_811_split/test_metrics.json deleted file mode 100644 index 52d2f65..0000000 --- a/models/checkpoints/dlinear_811_split/test_metrics.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "price_mae": 4283.3626861511675, - "price_rmse": 4735.107859758518, - "price_mape": 3.928560493143656, - "direction_accuracy": 0.49637127578304047, - "true_positives": 258, - "true_negatives": 2341, - "false_positives": 255, - "false_negatives": 2382 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_btc_1h_forecast/best_model.pt b/models/checkpoints/dlinear_btc_1h_forecast/best_model.pt deleted file mode 100644 index 66dd462..0000000 Binary files a/models/checkpoints/dlinear_btc_1h_forecast/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_1h_forecast/final_model.pt b/models/checkpoints/dlinear_btc_1h_forecast/final_model.pt deleted file mode 100644 index 6bbf439..0000000 Binary files a/models/checkpoints/dlinear_btc_1h_forecast/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_1h_forecast/test_metrics.json b/models/checkpoints/dlinear_btc_1h_forecast/test_metrics.json deleted file mode 100644 index 0d8fac2..0000000 --- a/models/checkpoints/dlinear_btc_1h_forecast/test_metrics.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "accuracy": 0.5, - "precision": 0.0, - "recall": 0.0, - "f1": 0.0, - "true_negatives": 642, - "false_positives": 0, - "false_negatives": 642, - "true_positives": 0, - "specificity": 1.0, - "mean_confidence": 0.0068817585706710815, - "backtest_total_return": -1.0, - "backtest_final_capital": 0.0, - "backtest_sharpe_ratio": -9.643650760992955, - "backtest_max_drawdown": NaN, - "backtest_win_rate": 0.0, - "backtest_num_trades": 0 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_btc_1h_forecast/test_metrics_fixed.json b/models/checkpoints/dlinear_btc_1h_forecast/test_metrics_fixed.json deleted file mode 100644 index 58b43ce..0000000 --- a/models/checkpoints/dlinear_btc_1h_forecast/test_metrics_fixed.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "accuracy": 0.5070093457943925, - "precision": 0.5053254437869823, - "recall": 0.6651090342679128, - "f1": 0.574310692669805, - "true_negatives": 224, - "false_positives": 418, - "false_negatives": 215, - "true_positives": 427, - "specificity": 0.34890965732087226, - "mean_confidence": 0.5017203688621521, - "backtest_total_return": -1.0, - "backtest_final_capital": -0.0, - "backtest_sharpe_ratio": 2.314261942010565, - "backtest_max_drawdown": -1.0, - "backtest_win_rate": 0.3325545171339564, - "backtest_num_trades": 136 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_btc_2020_2025/best_model.pt b/models/checkpoints/dlinear_btc_2020_2025/best_model.pt deleted file mode 100644 index 81bcbf8..0000000 Binary files a/models/checkpoints/dlinear_btc_2020_2025/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_2020_2025/final_model.pt b/models/checkpoints/dlinear_btc_2020_2025/final_model.pt deleted file mode 100644 index f8b2818..0000000 Binary files a/models/checkpoints/dlinear_btc_2020_2025/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_2020_2025/test_metrics.json b/models/checkpoints/dlinear_btc_2020_2025/test_metrics.json deleted file mode 100644 index 34978e5..0000000 --- a/models/checkpoints/dlinear_btc_2020_2025/test_metrics.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "accuracy": 0.49764421240290335, - "precision": 0.502192108409725, - "recall": 0.3185035389282103, - "f1": 0.38979118329466356, - "true_negatives": 2648, - "false_positives": 1249, - "false_negatives": 2696, - "true_positives": 1260, - "specificity": 0.6794970490120605, - "mean_confidence": 0.49758902192115784, - "backtest_total_return": -1.0, - "backtest_final_capital": -0.0, - "backtest_sharpe_ratio": -2.5728736046923655, - "backtest_max_drawdown": NaN, - "backtest_win_rate": 0.16044823634279892, - "backtest_num_trades": 782 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_btc_full_data/best_model.pt b/models/checkpoints/dlinear_btc_full_data/best_model.pt deleted file mode 100644 index fa0ef4d..0000000 Binary files a/models/checkpoints/dlinear_btc_full_data/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_full_data/final_metrics.json b/models/checkpoints/dlinear_btc_full_data/final_metrics.json deleted file mode 100644 index 559bdbf..0000000 --- a/models/checkpoints/dlinear_btc_full_data/final_metrics.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mse": 2.142045195796527e-05, - "rmse": 0.004628223412710893, - "mae": 0.0030602782499045134, - "mape": 131.8273162841797, - "directional_accuracy": 0.49732620320855614, - "backtest_total_return": 0.2429506778717041, - "backtest_final_capital": 12429.5068359375, - "backtest_sharpe_ratio": 0.19540257211386183, - "backtest_max_drawdown": -0.13858233392238617, - "backtest_win_rate": 0.49732620320855614, - "backtest_num_trades": 0 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_btc_full_data/final_model.pt b/models/checkpoints/dlinear_btc_full_data/final_model.pt deleted file mode 100644 index 4ffa295..0000000 Binary files a/models/checkpoints/dlinear_btc_full_data/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_full_data/preprocessor.pkl b/models/checkpoints/dlinear_btc_full_data/preprocessor.pkl deleted file mode 100644 index 0e954ba..0000000 Binary files a/models/checkpoints/dlinear_btc_full_data/preprocessor.pkl and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_regression/best_model.pt b/models/checkpoints/dlinear_btc_regression/best_model.pt deleted file mode 100644 index e0fdea9..0000000 Binary files a/models/checkpoints/dlinear_btc_regression/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_regression/final_model.pt b/models/checkpoints/dlinear_btc_regression/final_model.pt deleted file mode 100644 index 924b627..0000000 Binary files a/models/checkpoints/dlinear_btc_regression/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_btc_regression/test_metrics.json b/models/checkpoints/dlinear_btc_regression/test_metrics.json deleted file mode 100644 index 1e15352..0000000 --- a/models/checkpoints/dlinear_btc_regression/test_metrics.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mse": 2.382730417593848e-05, - "rmse": 0.004881321970116137, - "mae": 0.0033776722848415375, - "mape": 780.3690185546875, - "directional_accuracy": 0.5066853431809499, - "backtest_total_return": -0.1671363264322281, - "backtest_final_capital": 8328.63671875, - "backtest_sharpe_ratio": -0.0266775386419239, - "backtest_max_drawdown": -0.40377509593963623, - "backtest_win_rate": 0.48974914045587675, - "backtest_num_trades": 1061 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_predict_price/best_model.pt b/models/checkpoints/dlinear_predict_price/best_model.pt deleted file mode 100644 index 98ec007..0000000 Binary files a/models/checkpoints/dlinear_predict_price/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_predict_price/final_model.pt b/models/checkpoints/dlinear_predict_price/final_model.pt deleted file mode 100644 index 35129c2..0000000 Binary files a/models/checkpoints/dlinear_predict_price/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_predict_price/metrics.json b/models/checkpoints/dlinear_predict_price/metrics.json deleted file mode 100644 index 1d22417..0000000 --- a/models/checkpoints/dlinear_predict_price/metrics.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "price_mae": 528.017854278076, - "price_rmse": 736.6082253699151, - "price_mape": 0.523728600511905, - "direction_accuracy": 0.5022918258212375 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_predict_price/preprocessor.pkl b/models/checkpoints/dlinear_predict_price/preprocessor.pkl deleted file mode 100644 index f4397f2..0000000 Binary files a/models/checkpoints/dlinear_predict_price/preprocessor.pkl and /dev/null differ diff --git a/models/checkpoints/dlinear_simple_features/best_model.pt b/models/checkpoints/dlinear_simple_features/best_model.pt deleted file mode 100644 index 8337399..0000000 Binary files a/models/checkpoints/dlinear_simple_features/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_simple_features/final_model.pt b/models/checkpoints/dlinear_simple_features/final_model.pt deleted file mode 100644 index 64b5e81..0000000 Binary files a/models/checkpoints/dlinear_simple_features/final_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_simple_features/metrics.json b/models/checkpoints/dlinear_simple_features/metrics.json deleted file mode 100644 index 6b1b49f..0000000 --- a/models/checkpoints/dlinear_simple_features/metrics.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "mse": 2.1433252186398022e-05, - "rmse": 0.004629606050885758, - "mae": 0.00306295626796782, - "mape": 180.17770385742188, - "directional_accuracy": 0.4992360580595875, - "backtest_total_return": 0.226644828915596, - "backtest_final_capital": 12266.4482421875, - "backtest_sharpe_ratio": 0.18486101832170032, - "backtest_max_drawdown": -0.13858233392238617, - "backtest_win_rate": 0.4977081741787624, - "backtest_num_trades": 38 -} \ No newline at end of file diff --git a/models/checkpoints/dlinear_simple_features/preprocessor.pkl b/models/checkpoints/dlinear_simple_features/preprocessor.pkl deleted file mode 100644 index b1c6b41..0000000 Binary files a/models/checkpoints/dlinear_simple_features/preprocessor.pkl and /dev/null differ diff --git a/models/checkpoints/dlinear_test/best_model.pt b/models/checkpoints/dlinear_test/best_model.pt deleted file mode 100644 index f9319f7..0000000 Binary files a/models/checkpoints/dlinear_test/best_model.pt and /dev/null differ diff --git a/models/checkpoints/dlinear_test/final_model.pt b/models/checkpoints/dlinear_test/final_model.pt deleted file mode 100644 index 6f58d99..0000000 Binary files a/models/checkpoints/dlinear_test/final_model.pt and /dev/null differ diff --git a/predict_2026.py b/predict_2026.py deleted file mode 100644 index 4e004e6..0000000 --- a/predict_2026.py +++ /dev/null @@ -1,263 +0,0 @@ -"""使用全量训练模型预测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() diff --git a/predict_2026_price.py b/predict_2026_price.py deleted file mode 100644 index b7fc700..0000000 --- a/predict_2026_price.py +++ /dev/null @@ -1,202 +0,0 @@ -"""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 data.fetcher import BinanceDataFetcher -from models.dlinear.model import DLinear - -class PricePreprocessor: - """预测未来价格的预处理器""" - def __init__(self, lookback=168, forecast_horizon=1): - self.lookback = lookback - self.forecast_horizon = forecast_horizon - self.scaler = StandardScaler() - self.price_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['label'] = df['future_price'] - 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]) - scaled_labels = self.price_scaler.fit_transform(df[['label']]) - 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]) - scaled_labels = self.price_scaler.transform(df[['label']]) - - X, y = [], [] - for i in range(len(scaled_features) - self.lookback): - X.append(scaled_features[i:i + self.lookback]) - y.append(scaled_labels[i + self.lookback, 0]) - - return np.array(X), np.array(y), df - - def inverse_transform_price(self, scaled_price): - return self.price_scaler.inverse_transform(scaled_price.reshape(-1, 1)).flatten() - -def main(): - print("="*80) - print("2026年BTC价格预测 - 直接预测未来价格") - print("="*80) - - # ========== 加载模型 ========== - print("\n[1/4] 加载模型...") - model_path = './models/checkpoints/dlinear_predict_price/best_model.pt' - preprocessor_path = './models/checkpoints/dlinear_predict_price/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"✓ 模型加载成功") - - # ========== 获取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, y, processed_df = preprocessor.preprocess(df_2026, fit=False) - - if len(X) == 0: - print("❌ 数据不足") - return - - # 模型预测 - X_tensor = torch.FloatTensor(X).to(device) - with torch.no_grad(): - predictions = model(X_tensor).squeeze().cpu().numpy() - - # 还原为真实价格 - pred_prices = preprocessor.inverse_transform_price(predictions) - true_prices = preprocessor.inverse_transform_price(y) - - # 获取当前价格 - current_prices = processed_df['close'].iloc[168:168+len(pred_prices)].values - - # ========== 方向判断 ========== - print("\n[4/4] 涨跌方向评估...") - - pred_direction = (pred_prices > current_prices).astype(int) - true_direction = (true_prices > current_prices).astype(int) - - correct = (pred_direction == true_direction).sum() - accuracy = correct / len(predictions) - - from sklearn.metrics import confusion_matrix - cm = confusion_matrix(true_direction, pred_direction) - tn, fp, fn, tp = cm.ravel() - - print("\n" + "="*80) - print("📊 2026年价格预测结果") - print("="*80) - - print(f"\n价格预测误差:") - price_mae = np.abs(pred_prices - true_prices).mean() - price_rmse = np.sqrt(((pred_prices - true_prices) ** 2).mean()) - price_mape = np.mean(np.abs((true_prices - pred_prices) / true_prices)) * 100 - print(f" MAE: ${price_mae:,.2f}") - print(f" RMSE: ${price_rmse:,.2f}") - print(f" MAPE: {price_mape:.2f}%") - - print(f"\n方向预测统计:") - print(f" 预测上涨: {(pred_direction==1).sum()} / 实际上涨: {(true_direction==1).sum()}") - print(f" 预测下跌: {(pred_direction==0).sum()} / 实际下跌: {(true_direction==0).sum()}") - - print(f"\n方向准确率:") - print(f" 正确: {correct}/{len(predictions)} ({accuracy*100:.2f}%)") - - print(f"\n详细分析:") - print(f" ✓ 预测涨且真涨: {tp} ({tp/len(predictions)*100:.1f}%)") - print(f" ✓ 预测跌且真跌: {tn} ({tn/len(predictions)*100:.1f}%)") - print(f" ✗ 预测涨但真跌: {fp} ({fp/len(predictions)*100:.1f}%)") - print(f" ✗ 预测跌但真涨: {fn} ({fn/len(predictions)*100:.1f}%)") - - # 性能评估 - 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} {'当前价格':<12} {'预测价格':<12} {'实际价格':<12} {'预测':<6} {'实际':<6} {'结果':<4}") - print("-"*90) - - for i in range(min(30, len(predictions))): - timestamp = processed_df['timestamp'].iloc[i + 168] - curr = current_prices[i] - pred = pred_prices[i] - true = true_prices[i] - pred_dir = "上涨" if pred > curr else "下跌" - true_dir = "上涨" if true > curr else "下跌" - result = "✓" if pred_direction[i] == true_direction[i] else "✗" - - print(f"{str(timestamp):<20} ${curr:>10,.2f} ${pred:>10,.2f} ${true:>10,.2f} " - f"{pred_dir:<6} {true_dir:<6} {result:<4}") - - print("\n" + "="*80) - print(f"总结: 在{len(predictions)}次预测中,方向正确{correct}次 ({accuracy*100:.2f}%)") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/predict_2026_simple.py b/predict_2026_simple.py deleted file mode 100644 index f51d9d3..0000000 --- a/predict_2026_simple.py +++ /dev/null @@ -1,230 +0,0 @@ -"""简化预测脚本:只判断涨跌方向正确性""" - -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 models.dlinear.model import DLinear - -class SimplePreprocessor: - """简化的预处理器:只使用OHLCV + RSI + MACD""" - - 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): - """添加RSI和MACD特征""" - 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): - """创建标签:未来1小时的收益率""" - 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'] - 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 = 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 = self.scaler.transform(df[feature_cols]) - - X, y = [], [] - for i in range(len(scaled) - self.lookback): - X.append(scaled[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年BTC涨跌方向预测") - print("="*80) - - # ========== 步骤1: 加载模型 ========== - print("\n[1/5] 加载模型...") - model_path = './models/checkpoints/dlinear_simple_features/best_model.pt' - preprocessor_path = './models/checkpoints/dlinear_simple_features/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"✓ 特征数量: {len(preprocessor.feature_columns)}") - - # ========== 步骤2: 获取2026年数据 ========== - print("\n[2/5] 获取2026年数据...") - 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 - ) - except: - print("⚠️ 获取最新可用数据...") - 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()}") - print(f"✓ 价格: ${df_2026['close'].iloc[0]:,.2f} → ${df_2026['close'].iloc[-1]:,.2f}") - - # ========== 步骤3: 预处理 ========== - print("\n[3/5] 预处理数据...") - X, y, processed_df = preprocessor.preprocess(df_2026, fit=False) - - print(f"✓ 预测序列: {len(X)} 个") - print(f"✓ 实际涨跌: 上涨{(y>0).sum()}次, 下跌{(y<=0).sum()}次") - - if len(X) == 0: - print("❌ 数据不足") - return - - # ========== 步骤4: 预测 ========== - print("\n[4/5] 模型预测...") - X_tensor = torch.FloatTensor(X).to(device) - - with torch.no_grad(): - predictions = model(X_tensor).squeeze().cpu().numpy() - - print(f"✓ 预测完成") - print(f"✓ 预测收益率范围: {predictions.min()*100:.4f}% ~ {predictions.max()*100:.4f}%") - - # ========== 步骤5: 方向判断 ========== - print("\n[5/5] 涨跌方向评估...") - - # 预测方向:正值=上涨,负值=下跌 - pred_direction = (predictions > 0).astype(int) # 1=涨, 0=跌 - true_direction = (y > 0).astype(int) - - # 统计 - n_pred_up = (pred_direction == 1).sum() - n_pred_down = (pred_direction == 0).sum() - n_true_up = (true_direction == 1).sum() - n_true_down = (true_direction == 0).sum() - - # 准确率 - correct = (pred_direction == true_direction).sum() - accuracy = correct / len(predictions) - - # 混淆矩阵 - from sklearn.metrics import confusion_matrix - cm = confusion_matrix(true_direction, pred_direction) - tn, fp, fn, tp = cm.ravel() - - print("\n" + "="*80) - print("📊 涨跌方向预测结果") - print("="*80) - - print(f"\n预测统计:") - print(f" 预测上涨: {n_pred_up} ({n_pred_up/len(predictions)*100:.1f}%)") - print(f" 预测下跌: {n_pred_down} ({n_pred_down/len(predictions)*100:.1f}%)") - - print(f"\n实际统计:") - print(f" 实际上涨: {n_true_up} ({n_true_up/len(y)*100:.1f}%)") - print(f" 实际下跌: {n_true_down} ({n_true_down/len(y)*100:.1f}%)") - - print(f"\n准确率:") - print(f" 方向正确: {correct}/{len(predictions)} ({accuracy*100:.2f}%)") - - 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}%)") - - # 预测偏差 - bias = n_pred_up - n_true_up - print(f"\n预测偏差:") - if abs(bias) < len(predictions) * 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%") - print(f" 模型有预测能力,可考虑实盘测试") - elif accuracy > 0.52: - print(f"\n ⚠️ 方向准确率 {accuracy*100:.2f}% > 52%") - print(f" 模型略优于随机,谨慎使用") - elif accuracy >= 0.48: - print(f"\n ⚠️ 方向准确率 {accuracy*100:.2f}% ≈ 50%") - print(f" 接近随机猜测,不建议实盘") - else: - print(f"\n ❌ 方向准确率 {accuracy*100:.2f}% < 48%") - print(f" 低于随机,模型失效") - - # 预测样本展示 - print("\n" + "="*80) - print("📋 预测样本展示(前30个)") - print("="*80) - print(f"\n{'时间':<20} {'预测':<6} {'实际':<6} {'结果':<4} {'预测值':<10}") - print("-"*60) - - for i in range(min(30, len(predictions))): - timestamp = processed_df['timestamp'].iloc[i + 168] - pred = "上涨" if pred_direction[i] == 1 else "下跌" - true = "上涨" if true_direction[i] == 1 else "下跌" - result = "✓" if pred_direction[i] == true_direction[i] else "✗" - pred_val = predictions[i] - - print(f"{str(timestamp):<20} {pred:<6} {true:<6} {result:<4} {pred_val*100:>8.4f}%") - - print("\n" + "="*80) - print(f"总结: 在{len(predictions)}次预测中,方向正确{correct}次 ({accuracy*100:.2f}%)") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/test_binance_api.py b/test_binance_api.py deleted file mode 100644 index d800002..0000000 --- a/test_binance_api.py +++ /dev/null @@ -1,65 +0,0 @@ -"""测试Binance API连接(使用SOCKS5代理)""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher - -def main(): - print("="*80) - print("测试Binance API连接 - SOCKS5代理") - print("="*80) - - # 测试连接 - fetcher = BinanceDataFetcher() - - if fetcher.client is None: - print("\n❌ API连接失败,将使用缓存数据") - return - - print("\n✓ API连接成功!") - - # 获取当前价格 - try: - btc_price = fetcher.get_latest_price('BTC') - eth_price = fetcher.get_latest_price('ETH') - sol_price = fetcher.get_latest_price('SOL') - - print(f"\n当前价格:") - print(f" BTC: ${btc_price:,.2f}") - print(f" ETH: ${eth_price:,.2f}") - print(f" SOL: ${sol_price:,.2f}") - except Exception as e: - print(f"\n❌ 获取价格失败: {e}") - return - - # 获取历史数据(1天测试) - try: - print("\n正在获取BTC 1小时数据(最近1天)...") - from datetime import datetime, timedelta - end_date = datetime.now() - start_date = end_date - timedelta(days=1) - - df = fetcher.fetch_ohlcv( - 'BTC', - '1h', - start_date=start_date.strftime('%Y-%m-%d'), - end_date=end_date.strftime('%Y-%m-%d'), - use_cache=False # 强制从API获取 - ) - - print(f"✓ 成功获取 {len(df)} 行数据") - print(f"\n最新数据:") - print(df.tail()) - - except Exception as e: - print(f"\n❌ 获取历史数据失败: {e}") - return - - print("\n" + "="*80) - print("✓ Binance API测试通过!代理工作正常") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/test_dlinear.py b/test_dlinear.py deleted file mode 100644 index 5cabc0e..0000000 --- a/test_dlinear.py +++ /dev/null @@ -1,169 +0,0 @@ -"""加密货币价格预测系统 - DLinear模型端到端测试""" - -import sys -import os - -# 添加项目根目录到路径 -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig - -def main(): - print("="*80) - print("加密货币价格预测系统 - DLINEAR模型测试") - print("="*80) - - # 步骤1: 获取数据 - print("\n[1/7] 从Binance获取BTC 1小时数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - print(f"✓ 已获取 {len(df)} 行数据,时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 步骤2: 数据预处理 - print("\n[2/7] 数据预处理(添加技术指标、创建序列)...") - preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - print(f"✓ 标签分布:下跌(0) = {(y==0).sum()}, 上涨(1) = {(y==1).sum()}") - - # 步骤3: 划分数据集 - print("\n[3/7] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - # 步骤4: 创建数据加载器 - print("\n[4/7] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', - batch_size=64 - ) - print(f"✓ 训练批次:{len(train_loader)}") - print(f"✓ 验证批次:{len(val_loader)}") - print(f"✓ 测试批次:{len(test_loader)}") - - # 步骤5: 创建模型 - print("\n[5/7] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='classification', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - - # 步骤6: 训练模型 - print("\n[6/7] 开始训练模型...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=20, # 测试用较少轮数 - learning_rate=0.001, - early_stopping_patience=5, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_test' - ) - - trainer = Trainer(model, train_config, task='classification') - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/7] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 打印结果摘要 - print("\n" + "="*80) - print("最终测试结果") - print("="*80) - print(f"\n分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f}") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n混淆矩阵:") - print(f" 真阳性: {metrics['true_positives']}") - print(f" 真阴性: {metrics['true_negatives']}") - print(f" 假阳性: {metrics['false_positives']}") - print(f" 假阴性: {metrics['false_negatives']}") - - print(f"\n回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - print(f" 最大回撤: {metrics.get('backtest_max_drawdown', 0)*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 测试ONNX导出 - print("\n" + "="*80) - print("测试ONNX导出") - print("="*80) - - try: - import onnx - import onnxruntime as ort - import torch - import numpy as np - - onnx_path = './models/checkpoints/dlinear_test/model.onnx' - os.makedirs(os.path.dirname(onnx_path), exist_ok=True) - - print("\n[1/3] 导出模型到ONNX格式...") - model.to_onnx(onnx_path) - print(f"✓ ONNX模型已保存至:{onnx_path}") - - print("\n[2/3] 验证ONNX模型...") - onnx_model = onnx.load(onnx_path) - onnx.checker.check_model(onnx_model) - print("✓ ONNX模型有效") - - print("\n[3/3] 测试ONNX推理与PyTorch对比...") - # 创建测试输入 - test_input = torch.randn(1, 168, X.shape[-1]) - - # PyTorch推理(在CPU上) - model.cpu() - model.eval() - with torch.no_grad(): - pytorch_output = model(test_input).numpy() - - # ONNX推理 - ort_session = ort.InferenceSession(onnx_path) - onnx_output = ort_session.run( - None, - {'input': test_input.numpy()} - )[0] - - # 比较输出 - diff = np.abs(pytorch_output - onnx_output).max() - print(f"✓ PyTorch与ONNX的最大差异:{diff:.8f}") - - if diff < 1e-5: - print("✓ ONNX导出成功!输出在容差范围内匹配。") - else: - print("⚠ 警告:ONNX输出与PyTorch有差异(但可能仍可接受)") - - except Exception as e: - print(f"✗ ONNX导出失败:{e}") - - print("\n" + "="*80) - print("测试成功完成!") - print("="*80) - print("\n下一步:") - print(" 1. 查看训练历史:./models/checkpoints/dlinear_test/") - print(" 2. 实现N-BEATS模型(阶段3)") - print(" 3. 在所有币种上训练(BTC、ETH、SOL)") - print(" 4. 创建FastAPI预测服务") - -if __name__ == "__main__": - main() diff --git a/test_historical_data.py b/test_historical_data.py deleted file mode 100644 index 77d99b6..0000000 --- a/test_historical_data.py +++ /dev/null @@ -1,149 +0,0 @@ -"""测试获取2020-2023年的历史数据""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -import pandas as pd - -def test_historical_data(): - print("="*80) - print("测试Binance API历史数据获取能力") - print("="*80) - - fetcher = BinanceDataFetcher() - - # 测试1: 获取2020年的数据 - print("\n[测试1] 尝试获取2020年全年数据...") - try: - df_2020 = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2020-12-31', - use_cache=False # 不使用缓存,强制从API获取 - ) - print(f"✅ 成功获取2020年数据") - print(f" 数据行数: {len(df_2020)}") - print(f" 时间范围: {df_2020['timestamp'].min()} 至 {df_2020['timestamp'].max()}") - print(f" 价格范围: ${df_2020['close'].min():,.2f} - ${df_2020['close'].max():,.2f}") - except Exception as e: - print(f"❌ 2020年数据获取失败: {e}") - return False - - # 测试2: 获取2021年的数据 - print("\n[测试2] 尝试获取2021年全年数据...") - try: - df_2021 = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2021-01-01', - end_date='2021-12-31', - use_cache=False - ) - print(f"✅ 成功获取2021年数据") - print(f" 数据行数: {len(df_2021)}") - print(f" 时间范围: {df_2021['timestamp'].min()} 至 {df_2021['timestamp'].max()}") - print(f" 价格范围: ${df_2021['close'].min():,.2f} - ${df_2021['close'].max():,.2f}") - except Exception as e: - print(f"❌ 2021年数据获取失败: {e}") - return False - - # 测试3: 获取2022年的数据 - print("\n[测试3] 尝试获取2022年全年数据...") - try: - df_2022 = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2022-01-01', - end_date='2022-12-31', - use_cache=False - ) - print(f"✅ 成功获取2022年数据") - print(f" 数据行数: {len(df_2022)}") - print(f" 时间范围: {df_2022['timestamp'].min()} 至 {df_2022['timestamp'].max()}") - print(f" 价格范围: ${df_2022['close'].min():,.2f} - ${df_2022['close'].max():,.2f}") - except Exception as e: - print(f"❌ 2022年数据获取失败: {e}") - return False - - # 测试4: 获取2023年的数据 - print("\n[测试4] 尝试获取2023年全年数据...") - try: - df_2023 = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2023-01-01', - end_date='2023-12-31', - use_cache=False - ) - print(f"✅ 成功获取2023年数据") - print(f" 数据行数: {len(df_2023)}") - print(f" 时间范围: {df_2023['timestamp'].min()} 至 {df_2023['timestamp'].max()}") - print(f" 价格范围: ${df_2023['close'].min():,.2f} - ${df_2023['close'].max():,.2f}") - except Exception as e: - print(f"❌ 2023年数据获取失败: {e}") - return False - - # 测试5: 获取完整的2020-2023年数据 - print("\n[测试5] 尝试获取2020-2023年完整数据(4年)...") - try: - df_full = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2023-12-31', - use_cache=False - ) - print(f"✅ 成功获取2020-2023年完整数据") - print(f" 数据行数: {len(df_full)}") - print(f" 时间范围: {df_full['timestamp'].min()} 至 {df_full['timestamp'].max()}") - print(f" 起始价格: ${df_full['close'].iloc[0]:,.2f}") - print(f" 结束价格: ${df_full['close'].iloc[-1]:,.2f}") - total_return = (df_full['close'].iloc[-1] - df_full['close'].iloc[0]) / df_full['close'].iloc[0] - print(f" 总收益率: {total_return*100:.2f}%") - - # 分析每年的表现 - print(f"\n 年度表现分析:") - df_full['year'] = pd.to_datetime(df_full['timestamp']).dt.year - for year in [2020, 2021, 2022, 2023]: - year_df = df_full[df_full['year'] == year] - if len(year_df) > 0: - year_return = (year_df['close'].iloc[-1] - year_df['close'].iloc[0]) / year_df['close'].iloc[0] - print(f" {year}年: {year_return*100:+.2f}% (${year_df['close'].iloc[0]:,.0f} → ${year_df['close'].iloc[-1]:,.0f})") - - except Exception as e: - print(f"❌ 完整数据获取失败: {e}") - return False - - # 总结 - print("\n" + "="*80) - print("📊 测试结果总结") - print("="*80) - print("✅ Binance API可以获取2020-2023年的完整历史数据") - print(f"✅ 总共可获取约 {len(df_full):,} 小时的K线数据") - print(f"✅ 数据质量:连续完整,无明显缺失") - print(f"\n💡 建议:") - print(f" - 使用2020-2023年数据进行训练(包含完整牛熊周期)") - print(f" - 2020年: 牛市初期") - print(f" - 2021年: 牛市顶峰") - print(f" - 2022年: 熊市") - print(f" - 2023年: 复苏") - print(f" 这样模型能学习到各种市场环境") - - return True - -if __name__ == "__main__": - try: - success = test_historical_data() - if success: - print("\n" + "="*80) - print("✅ 所有测试通过!可以获取2020-2023年的数据") - print("="*80) - else: - print("\n" + "="*80) - print("❌ 部分测试失败") - print("="*80) - except Exception as e: - print(f"\n❌ 测试过程出错: {e}") diff --git a/train_811_split.py b/train_811_split.py deleted file mode 100644 index 0b7eadd..0000000 --- a/train_811_split.py +++ /dev/null @@ -1,304 +0,0 @@ -"""8:1:1数据集划分训练 - 直接预测价格""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import numpy as np -import pandas as pd -import pandas_ta as ta -from sklearn.preprocessing import StandardScaler -import torch -import torch.nn as nn -from torch.utils.data import DataLoader, TensorDataset - -from data.fetcher import BinanceDataFetcher -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import pickle - -class PricePreprocessor: - """预测未来价格的预处理器""" - - def __init__(self, lookback=168, forecast_horizon=1): - self.lookback = lookback - self.forecast_horizon = forecast_horizon - self.scaler = StandardScaler() - self.price_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): - """创建标签:未来1小时的价格""" - df = df.copy() - df['future_price'] = df['close'].shift(-self.forecast_horizon) - df['label'] = df['future_price'] - 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]) - scaled_labels = self.price_scaler.fit_transform(df[['label']]) - 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]) - scaled_labels = self.price_scaler.transform(df[['label']]) - - X, y = [], [] - for i in range(len(scaled_features) - self.lookback): - X.append(scaled_features[i:i + self.lookback]) - y.append(scaled_labels[i + self.lookback, 0]) - - return np.array(X), np.array(y), df - - def inverse_transform_price(self, scaled_price): - """将标准化的价格还原为真实价格""" - return self.price_scaler.inverse_transform(scaled_price.reshape(-1, 1)).flatten() - -def main(): - print("="*80) - print("8:1:1数据集划分 - 直接预测未来1小时价格") - print("="*80) - - # ========== 步骤1: 获取数据 ========== - print("\n[1/8] 获取BTC 2020-2025年数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 数据量: {len(df):,} 行") - print(f"✓ 时间: {df['timestamp'].min()} 至 {df['timestamp'].max()}") - print(f"✓ 价格: ${df['close'].iloc[0]:,.2f} → ${df['close'].iloc[-1]:,.2f}") - - # ========== 步骤2: 预处理 ========== - print("\n[2/8] 数据预处理(只使用OHLCV + RSI + MACD)...") - preprocessor = PricePreprocessor(lookback=168, forecast_horizon=1) - X, y, processed_df = preprocessor.preprocess(df, fit=True) - - print(f"✓ 序列数量: {len(X):,}") - print(f"✓ 序列形状: {X.shape}") - print(f"✓ 特征列表: {preprocessor.feature_columns}") - - # ========== 步骤3: 8:1:1划分数据集 ========== - print("\n[3/8] 8:1:1划分数据集...") - n = len(X) - train_end = int(n * 0.8) - val_end = int(n * 0.9) - - X_train = X[:train_end] - y_train = y[:train_end] - X_val = X[train_end:val_end] - y_val = y[train_end:val_end] - X_test = X[val_end:] - y_test = y[val_end:] - - print(f" 总样本数: {n:,}") - print(f" 训练集: {len(X_train):,} ({len(X_train)/n*100:.1f}%)") - print(f" 验证集: {len(X_val):,} ({len(X_val)/n*100:.1f}%)") - print(f" 测试集: {len(X_test):,} ({len(X_test)/n*100:.1f}%)") - - # 分析各数据集时间范围 - lookback = 168 - train_df = processed_df.iloc[lookback:train_end+lookback] - val_df = processed_df.iloc[train_end+lookback:val_end+lookback] - test_df = processed_df.iloc[val_end+lookback:] - - print(f"\n 时间范围分析:") - print(f" 训练集: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f} ({train_return*100:+.1f}%)") - - print(f" 验证集: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f} ({val_return*100:+.1f}%)") - - print(f" 测试集: {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - test_return = (test_df['close'].iloc[-1] - test_df['close'].iloc[0]) / test_df['close'].iloc[0] - print(f" ${test_df['close'].iloc[0]:,.0f} → ${test_df['close'].iloc[-1]:,.0f} ({test_return*100:+.1f}%)") - - # ========== 步骤4: 创建数据加载器 ========== - print("\n[4/8] 创建数据加载器...") - train_dataset = TensorDataset(torch.FloatTensor(X_train), torch.FloatTensor(y_train)) - val_dataset = TensorDataset(torch.FloatTensor(X_val), torch.FloatTensor(y_val)) - test_dataset = TensorDataset(torch.FloatTensor(X_test), torch.FloatTensor(y_test)) - - train_loader = DataLoader(train_dataset, batch_size=64, shuffle=False) - val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) - test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False) - - # ========== 步骤5: 创建模型 ========== - print("\n[5/8] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=9, - lookback=168, - task='regression', - hidden_dim=256, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型参数: {model.count_parameters():,}") - - # ========== 步骤6: 训练 ========== - print("\n[6/8] 开始训练...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=15, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_811_split' - ) - - trainer = Trainer(model, train_config, task='regression') - history = trainer.fit(train_loader, val_loader) - - # ========== 步骤7: 在测试集上评估 ========== - print("\n[7/8] 在测试集上评估...") - - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - model = model.to(device) - model.eval() - - test_preds = [] - with torch.no_grad(): - for batch_X, _ in test_loader: - batch_X = batch_X.to(device) - pred = model(batch_X).squeeze() - test_preds.extend(pred.cpu().numpy()) - - test_preds = np.array(test_preds) - - # 还原为真实价格 - pred_prices = preprocessor.inverse_transform_price(test_preds) - true_prices = preprocessor.inverse_transform_price(y_test) - - # 获取当前价格 - test_start_idx = val_end + lookback - current_prices = processed_df['close'].iloc[test_start_idx:test_start_idx+len(pred_prices)].values - - # 方向判断 - pred_direction = (pred_prices > current_prices).astype(int) - true_direction = (true_prices > current_prices).astype(int) - direction_acc = (pred_direction == true_direction).mean() - - # 价格预测误差 - price_mae = np.abs(pred_prices - true_prices).mean() - price_rmse = np.sqrt(((pred_prices - true_prices) ** 2).mean()) - price_mape = np.mean(np.abs((true_prices - pred_prices) / true_prices)) * 100 - - from sklearn.metrics import confusion_matrix - cm = confusion_matrix(true_direction, pred_direction) - tn, fp, fn, tp = cm.ravel() - - print("\n" + "="*80) - print("训练完成 - 测试集性能(独立测试集)") - print("="*80) - - print(f"\n📊 价格预测误差:") - print(f" MAE: ${price_mae:,.2f}") - print(f" RMSE: ${price_rmse:,.2f}") - print(f" MAPE: {price_mape:.2f}%") - - print(f"\n📈 方向预测:") - print(f" 方向准确率: {direction_acc*100:.2f}%") - print(f" 预测上涨: {(pred_direction==1).sum()} / 实际上涨: {(true_direction==1).sum()}") - print(f" 预测下跌: {(pred_direction==0).sum()} / 实际下跌: {(true_direction==0).sum()}") - - print(f"\n详细分析:") - print(f" ✓ 预测涨且真涨: {tp} ({tp/len(pred_prices)*100:.1f}%)") - print(f" ✓ 预测跌且真跌: {tn} ({tn/len(pred_prices)*100:.1f}%)") - print(f" ✗ 预测涨但真跌: {fp} ({fp/len(pred_prices)*100:.1f}%)") - print(f" ✗ 预测跌但真涨: {fn} ({fn/len(pred_prices)*100:.1f}%)") - - # 性能评估 - print("\n" + "="*80) - print("🎯 性能评估") - print("="*80) - - if direction_acc > 0.55: - print(f"\n ✅ 方向准确率 {direction_acc*100:.2f}% > 55%,模型有预测能力") - elif direction_acc > 0.52: - print(f"\n ⚠️ 方向准确率 {direction_acc*100:.2f}% > 52%,略优于随机") - elif direction_acc >= 0.48: - print(f"\n ⚠️ 方向准确率 {direction_acc*100:.2f}% ≈ 50%,接近随机") - else: - print(f"\n ❌ 方向准确率 {direction_acc*100:.2f}% < 48%,低于随机") - - # ========== 步骤8: 保存 ========== - print("\n[8/8] 保存模型和结果...") - - with open('./models/checkpoints/dlinear_811_split/preprocessor.pkl', 'wb') as f: - pickle.dump(preprocessor, f) - - metrics = { - 'price_mae': float(price_mae), - 'price_rmse': float(price_rmse), - 'price_mape': float(price_mape), - 'direction_accuracy': float(direction_acc), - 'true_positives': int(tp), - 'true_negatives': int(tn), - 'false_positives': int(fp), - 'false_negatives': int(fn) - } - with open('./models/checkpoints/dlinear_811_split/test_metrics.json', 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 模型保存至: ./models/checkpoints/dlinear_811_split/") - - # 对比分析 - print("\n" + "="*80) - print("📊 数据集划分对比") - print("="*80) - - print("\n之前(95:5划分,无独立测试集):") - print(" 训练集: 49,733 (95%)") - print(" 验证集: 2,618 (5%)") - print(" 测试集: 无") - print(" 问题: 过拟合,验证集太小") - - print(f"\n当前(8:1:1划分,有独立测试集):") - print(f" 训练集: {len(X_train):,} (80%)") - print(f" 验证集: {len(X_val):,} (10%)") - print(f" 测试集: {len(X_test):,} (10%)") - print(f" 优势: 有足够大的独立测试集评估性能") - - print(f"\n测试集性能:") - print(f" 方向准确率: {direction_acc*100:.2f}%") - print(f" 价格误差MAPE: {price_mape:.2f}%") - - print("\n" + "="*80) - print("✓ 训练完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_cross_asset.py b/train_cross_asset.py deleted file mode 100644 index f6b06d7..0000000 --- a/train_cross_asset.py +++ /dev/null @@ -1,240 +0,0 @@ -"""跨资产迁移学习实验 - 用BTC训练,在ETH上验证和测试""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("跨资产迁移学习实验") - print("训练:BTC 2020-2025 | 验证+测试:ETH 2020-2025") - print("="*80) - - fetcher = BinanceDataFetcher() - - # ========== 步骤1: 获取BTC训练数据 ========== - print("\n[1/10] 获取BTC数据(用于训练)...") - df_btc = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - print(f"✓ BTC数据: {len(df_btc)} 行") - print(f" 时间: {df_btc['timestamp'].min()} 至 {df_btc['timestamp'].max()}") - print(f" 价格: ${df_btc['close'].iloc[0]:,.0f} → ${df_btc['close'].iloc[-1]:,.0f}") - btc_return = (df_btc['close'].iloc[-1] - df_btc['close'].iloc[0]) / df_btc['close'].iloc[0] - print(f" 收益率: {btc_return*100:+.1f}%") - - # ========== 步骤2: 获取ETH验证/测试数据 ========== - print("\n[2/10] 获取ETH数据(用于验证和测试)...") - df_eth = fetcher.fetch_ohlcv( - symbol='ETH', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - print(f"✓ ETH数据: {len(df_eth)} 行") - print(f" 时间: {df_eth['timestamp'].min()} 至 {df_eth['timestamp'].max()}") - print(f" 价格: ${df_eth['close'].iloc[0]:,.2f} → ${df_eth['close'].iloc[-1]:,.2f}") - eth_return = (df_eth['close'].iloc[-1] - df_eth['close'].iloc[0]) / df_eth['close'].iloc[0] - print(f" 收益率: {eth_return*100:+.1f}%") - - # ========== 步骤3: 预处理BTC数据 ========== - print("\n[3/10] 预处理BTC数据(回归任务)...") - preprocessor_btc = CryptoPreprocessor( - lookback=168, - forecast_horizon=1, - price_change_threshold=0.0 - ) - X_btc, y_btc, processed_btc = preprocessor_btc.preprocess_pipeline( - df_btc, task='regression', fit=True - ) - print(f"✓ BTC序列: X={X_btc.shape}, y={y_btc.shape}") - - # ========== 步骤4: 预处理ETH数据 ========== - print("\n[4/10] 预处理ETH数据(使用BTC的scaler)...") - # 重要:复用BTC的preprocessor(已fit的scaler)来处理ETH数据 - # 添加技术指标和时间特征 - df_eth_processed = preprocessor_btc.add_technical_indicators(df_eth) - df_eth_processed = preprocessor_btc.add_time_features(df_eth_processed) - df_eth_processed = preprocessor_btc.create_labels(df_eth_processed, task='regression') - - # 删除NaN行 - processed_eth = df_eth_processed.dropna().reset_index(drop=True) - - # 使用BTC训练的scaler来标准化ETH特征(不重新fit) - scaled_features = preprocessor_btc.transform(processed_eth) - - # 创建序列 - X_eth, y_eth = preprocessor_btc.create_sequences( - scaled_features, - processed_eth['label'].values - ) - print(f"✓ ETH序列: X={X_eth.shape}, y={y_eth.shape}") - - # ========== 步骤5: 划分数据集 ========== - print("\n[5/10] 划分数据集...") - - # BTC: 只用前70%作为训练集 - n_btc = len(X_btc) - train_end = int(n_btc * 0.7) - X_train = X_btc[:train_end] - y_train = y_btc[:train_end] - - print(f" 训练集(BTC): {len(X_train)} 样本") - print(f" 收益率范围: {y_train.min()*100:.4f}% ~ {y_train.max()*100:.4f}%") - - # ETH: 70%验证,30%测试 - n_eth = len(X_eth) - val_end = int(n_eth * 0.7) - X_val = X_eth[:val_end] - y_val = y_eth[:val_end] - X_test = X_eth[val_end:] - y_test = y_eth[val_end:] - - print(f" 验证集(ETH): {len(X_val)} 样本") - print(f" 收益率范围: {y_val.min()*100:.4f}% ~ {y_val.max()*100:.4f}%") - print(f" 测试集(ETH): {len(X_test)} 样本") - print(f" 收益率范围: {y_test.min()*100:.4f}% ~ {y_test.max()*100:.4f}%") - - # 分析时间范围 - lookback = 168 - train_df = processed_btc.iloc[lookback:train_end+lookback] - val_df = processed_eth.iloc[lookback:val_end+lookback] - test_df = processed_eth.iloc[val_end+lookback:] - - print(f"\n 时间范围分析:") - print(f" 训练(BTC): {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - print(f" 验证(ETH): {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - print(f" 测试(ETH): {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - - # ========== 步骤6: 创建数据加载器 ========== - print("\n[6/10] 创建数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - (X_train, y_train), - (X_val, y_val), - (X_test, y_test), - task='regression', - batch_size=64 - ) - - # ========== 步骤7: 创建模型 ========== - print("\n[7/10] 创建DLinear回归模型...") - model_config = DLinearConfig( - input_dim=X_btc.shape[-1], - lookback=168, - task='regression', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型参数: {model.count_parameters():,} 个") - - # ========== 步骤8: 训练模型 ========== - print("\n[8/10] 训练模型(BTC训练,ETH验证)...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/cross_asset_btc_to_eth' - ) - - trainer = Trainer(model, train_config, task='regression') - history = trainer.fit(train_loader, val_loader) - - # ========== 步骤9: 在ETH测试集上评估 ========== - print("\n[9/10] 在ETH测试集上评估...") - metrics_eth = trainer.evaluate(test_loader) - - results_path = './models/checkpoints/cross_asset_btc_to_eth/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics_eth, f, indent=2) - print(f"✓ 结果保存至: {results_path}") - - # ========== 步骤10: 对比分析 ========== - print("\n[10/10] 性能分析...") - print("\n" + "="*80) - print("跨资产迁移学习实验结果") - print("="*80) - - print("\n📋 实验设置:") - print(f" 训练集: BTC 2020-2024 ({len(X_train):,} 样本)") - print(f" 验证集: ETH 2020-2024 ({len(X_val):,} 样本)") - print(f" 测试集: ETH 2024-2025 ({len(X_test):,} 样本)") - - print("\n📊 ETH测试集性能:") - print(f" RMSE: {metrics_eth.get('rmse', 0)*100:.4f}%") - print(f" MAE: {metrics_eth.get('mae', 0)*100:.4f}%") - print(f" 方向准确率: {metrics_eth.get('directional_accuracy', 0)*100:.2f}%") - - print(f"\n💰 ETH回测结果:") - print(f" 总收益率: {metrics_eth.get('backtest_total_return', 0)*100:.2f}%") - print(f" 夏普比率: {metrics_eth.get('backtest_sharpe_ratio', 0):.4f}") - max_dd = metrics_eth.get('backtest_max_drawdown', 0) - if not np.isnan(max_dd): - print(f" 最大回撤: {max_dd*100:.2f}%") - print(f" 胜率: {metrics_eth.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics_eth.get('backtest_num_trades', 0):.0f}") - - # 对比之前的结果 - print("\n" + "="*80) - print("📊 性能对比") - print("="*80) - - print("\n实验1:BTC训练 → BTC测试(之前结果)") - print(" 方向准确率: 50.67%") - print(" RMSE: 0.4881%") - print(" 回测收益: -16.71%") - - print(f"\n实验2:BTC训练 → ETH测试(当前结果)") - print(f" 方向准确率: {metrics_eth.get('directional_accuracy', 0)*100:.2f}%") - print(f" RMSE: {metrics_eth.get('rmse', 0)*100:.4f}%") - print(f" 回测收益: {metrics_eth.get('backtest_total_return', 0)*100:.2f}%") - - # 性能评估 - print("\n🎯 迁移学习效果评估:") - - dir_acc = metrics_eth.get('directional_accuracy', 0) - if dir_acc > 0.52: - print(f" ✅ 方向准确率 {dir_acc*100:.1f}% > 52%,跨资产迁移有效") - elif dir_acc > 0.50: - print(f" ⚠️ 方向准确率 {dir_acc*100:.1f}% 略优于随机") - else: - print(f" ❌ 方向准确率 {dir_acc*100:.1f}% ≤ 50%,迁移失败") - - total_return = metrics_eth.get('backtest_total_return', 0) - if total_return > 0: - print(f" ✅ 回测收益 {total_return*100:.2f}% > 0,策略盈利") - else: - print(f" ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损") - - print("\n💡 结论:") - if dir_acc > 0.52 and total_return > 0: - print(" ✅ 跨资产迁移学习成功!BTC学到的模式可应用于ETH") - elif dir_acc > 0.50: - print(" ⚠️ 跨资产迁移部分有效,但收益不佳") - else: - print(" ❌ 跨资产迁移失败,BTC和ETH市场动态差异太大") - - print("\n" + "="*80) - print("✓ 实验完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_dlinear_1h.py b/train_dlinear_1h.py deleted file mode 100644 index bc1cb78..0000000 --- a/train_dlinear_1h.py +++ /dev/null @@ -1,156 +0,0 @@ -"""训练DLinear模型 - 预测未来1小时涨跌(已修复双重Sigmoid Bug)""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("DLinear模型训练 - 预测未来1小时涨跌(已修复双重Sigmoid Bug)") - print("="*80) - - # 步骤1: 获取真实数据 - print("\n[1/7] 从Binance获取BTC 1h真实数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - print(f"✓ 已获取 {len(df)} 行真实数据") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 步骤2: 数据预处理(预测未来1小时) - print("\n[2/7] 数据预处理(预测未来1小时的涨跌)...") - preprocessor = CryptoPreprocessor( - lookback=168, # 输入:过去168小时(7天) - forecast_horizon=1, # ← 改为预测未来1小时 - price_change_threshold=0.0 # 任何涨幅>0都算上涨 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - - # 计算类别分布 - n_down = (y == 0).sum() - n_up = (y == 1).sum() - print(f"✓ 标签分布:下跌(0) = {n_down} ({n_down/len(y)*100:.1f}%), 上涨(1) = {n_up} ({n_up/len(y)*100:.1f}%)") - - # 计算类别权重 - pos_weight = n_down / n_up if n_up > 0 else 1.0 - print(f"✓ 计算类别权重:pos_weight = {pos_weight:.2f}") - - # 步骤3: 划分数据集 - print("\n[3/7] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - print(f" 训练集:下跌 {(train_data[1]==0).sum()}, 上涨 {(train_data[1]==1).sum()}") - print(f" 验证集:下跌 {(val_data[1]==0).sum()}, 上涨 {(val_data[1]==1).sum()}") - print(f" 测试集:下跌 {(test_data[1]==0).sum()}, 上涨 {(test_data[1]==1).sum()}") - - # 步骤4: 创建数据加载器 - print("\n[4/7] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', - batch_size=64 - ) - - # 步骤5: 创建模型 - print("\n[5/7] 创建DLinear模型(已修复双重Sigmoid Bug)...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='classification', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - print(f"✓ Bug修复:输出层使用Identity(输出logits),由BCEWithLogitsLoss处理Sigmoid") - - # 步骤6: 训练模型 - print("\n[6/7] 开始训练模型...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_1h_forecast' - ) - - trainer = Trainer( - model, - train_config, - task='classification', - pos_weight=pos_weight - ) - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/7] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 保存评估结果 - results_path = './models/checkpoints/dlinear_btc_1h_forecast/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 打印结果摘要 - print("\n" + "="*80) - print("训练完成 - 最终测试结果(预测未来1小时涨跌)") - print("="*80) - - print(f"\n📊 分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f} ({metrics.get('accuracy', 0)*100:.2f}%)") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n📈 混淆矩阵:") - print(f" 真阳性(预测涨且真涨): {metrics['true_positives']}") - print(f" 真阴性(预测跌且真跌): {metrics['true_negatives']}") - print(f" 假阳性(预测涨但真跌): {metrics['false_positives']}") - print(f" 假阴性(预测跌但真涨): {metrics['false_negatives']}") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - print(f" 最大回撤: {metrics.get('backtest_max_drawdown', 0)*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 性能分析 - print(f"\n🎯 性能评估:") - acc = metrics.get('accuracy', 0) - if acc > 0.55: - print(f" ✅ 准确率 {acc*100:.1f}% > 55%,模型有一定预测能力") - elif acc > 0.50: - print(f" ⚠️ 准确率 {acc*100:.1f}% 略优于随机(50%),预测能力较弱") - else: - print(f" ❌ 准确率 {acc*100:.1f}% ≤ 50%,模型预测能力不足") - - total_return = metrics.get('backtest_total_return', 0) - if total_return > 0: - print(f" ✅ 回测收益 {total_return*100:.2f}% > 0,策略有盈利潜力") - else: - print(f" ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损") - - print("\n" + "="*80) - print("✓ 训练完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_dlinear_balanced.py b/train_dlinear_balanced.py deleted file mode 100644 index 758422f..0000000 --- a/train_dlinear_balanced.py +++ /dev/null @@ -1,190 +0,0 @@ -"""使用真实Binance数据训练DLinear模型 - 优化版(平衡类别 + 类别权重)""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("DLinear模型训练 - 优化版(平衡类别 + 类别权重)") - print("="*80) - - # 步骤1: 获取真实数据 - print("\n[1/8] 从Binance获取BTC 1h真实数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - print(f"✓ 已获取 {len(df)} 行真实数据") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 步骤2: 数据预处理(使用真实涨跌:threshold=0.0) - print("\n[2/8] 数据预处理(使用真实涨跌标签,threshold=0.0)...") - preprocessor = CryptoPreprocessor( - lookback=168, - forecast_horizon=24, - price_change_threshold=0.0 # 任何涨幅>0都算上涨 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - - # 计算类别分布 - n_down = (y == 0).sum() - n_up = (y == 1).sum() - print(f"✓ 标签分布(优化后):下跌(0) = {n_down} ({n_down/len(y)*100:.1f}%), 上涨(1) = {n_up} ({n_up/len(y)*100:.1f}%)") - - # 计算类别权重 - pos_weight = n_down / n_up if n_up > 0 else 1.0 - print(f"✓ 计算类别权重:pos_weight = {pos_weight:.2f} (给上涨类{pos_weight:.2f}倍权重)") - - # 步骤3: 划分数据集 - print("\n[3/8] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - # 显示各数据集的类别分布 - print(f" 训练集:下跌 {(train_data[1]==0).sum()}, 上涨 {(train_data[1]==1).sum()}") - print(f" 验证集:下跌 {(val_data[1]==0).sum()}, 上涨 {(val_data[1]==1).sum()}") - print(f" 测试集:下跌 {(test_data[1]==0).sum()}, 上涨 {(test_data[1]==1).sum()}") - - # 步骤4: 创建数据加载器 - print("\n[4/8] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', - batch_size=64 - ) - - # 步骤5: 创建模型 - print("\n[5/8] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='classification', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - - # 步骤6: 训练模型(使用类别权重) - print("\n[6/8] 开始训练模型(使用加权损失函数)...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_1h_balanced' - ) - - trainer = Trainer( - model, - train_config, - task='classification', - pos_weight=pos_weight # 传入类别权重 - ) - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/8] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 保存评估结果 - results_path = './models/checkpoints/dlinear_btc_1h_balanced/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 打印结果摘要 - print("\n" + "="*80) - print("训练完成 - 最终测试结果(优化版)") - print("="*80) - print(f"\n📊 分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f}") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n📈 混淆矩阵:") - print(f" 真阳性(预测涨且真涨): {metrics['true_positives']}") - print(f" 真阴性(预测跌且真跌): {metrics['true_negatives']}") - print(f" 假阳性(预测涨但真跌): {metrics['false_positives']}") - print(f" 假阴性(预测跌但真涨): {metrics['false_negatives']}") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - print(f" 最大回撤: {metrics.get('backtest_max_drawdown', 0)*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 步骤8: ONNX导出 - print("\n[8/8] 测试ONNX导出...") - try: - import onnx - import onnxruntime as ort - import torch - - onnx_path = './models/checkpoints/dlinear_btc_1h_balanced/model.onnx' - - print(" 导出模型到ONNX格式...") - model.to_onnx(onnx_path) - print(f" ✓ ONNX模型已保存至:{onnx_path}") - - print(" 验证ONNX模型...") - onnx_model = onnx.load(onnx_path) - onnx.checker.check_model(onnx_model) - print(" ✓ ONNX模型有效") - - # 测试推理 - test_input = torch.randn(1, 168, X.shape[-1]) - model.cpu() - model.eval() - with torch.no_grad(): - pytorch_output = model(test_input).numpy() - - ort_session = ort.InferenceSession(onnx_path) - onnx_output = ort_session.run(None, {'input': test_input.numpy()})[0] - - diff = np.abs(pytorch_output - onnx_output).max() - print(f" ✓ PyTorch与ONNX的最大差异:{diff:.8f}") - - if diff < 1e-5: - print(" ✓ ONNX导出成功!") - else: - print(" ⚠ ONNX输出有差异(可能仍可接受)") - - except Exception as e: - print(f" ✗ ONNX导出失败:{e}") - - print("\n" + "="*80) - print("✓ DLinear模型训练完成(优化版)") - print("="*80) - - # 对比旧版本 - print("\n📊 优化效果对比:") - print(" 旧版本(1.5%阈值):") - print(" - 类别分布:上涨 15.5%, 下跌 84.5%") - print(" - 准确率:84.47% F1:0.00 召回率:0.00") - print(" - 模型策略:永远预测下跌") - print(" ") - print(" 新版本(0%阈值 + 类别权重):") - print(f" - 类别分布:上涨 {n_up/len(y)*100:.1f}%, 下跌 {n_down/len(y)*100:.1f}%") - print(f" - 准确率:{metrics.get('accuracy', 0):.2%} F1:{metrics.get('f1', 0):.2f} 召回率:{metrics.get('recall', 0):.2f}") - print(f" - 回测收益:{metrics.get('backtest_total_return', 0)*100:.2f}%") - -if __name__ == "__main__": - main() diff --git a/train_dlinear_long_term.py b/train_dlinear_long_term.py deleted file mode 100644 index 337d221..0000000 --- a/train_dlinear_long_term.py +++ /dev/null @@ -1,242 +0,0 @@ -"""使用2020-2025年数据训练DLinear模型 - 包含完整牛熊周期""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("DLinear模型训练 - 使用2020-2025年完整数据(5年,包含牛熊周期)") - print("="*80) - - # 步骤1: 获取2020-2025年完整数据 - print("\n[1/8] 从Binance获取BTC 2020-2025年完整数据...") - fetcher = BinanceDataFetcher() - - # 获取5年数据 - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 已获取 {len(df)} 行数据(约{len(df)/8760:.1f}年)") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 分析数据覆盖的市场周期 - print(f"\n✓ 价格变化分析:") - print(f" 起始价格: ${df['close'].iloc[0]:,.2f} (2020年)") - print(f" 结束价格: ${df['close'].iloc[-1]:,.2f} (2025年)") - total_return = (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0] - print(f" 总收益率: {total_return*100:+.2f}%") - - # 按年分析 - df['year'] = df['timestamp'].dt.year - print(f"\n✓ 年度表现分析(确保包含牛熊周期):") - for year in sorted(df['year'].unique()): - year_df = df[df['year'] == year] - if len(year_df) > 100: # 确保有足够数据 - year_return = (year_df['close'].iloc[-1] - year_df['close'].iloc[0]) / year_df['close'].iloc[0] - trend = "🚀 牛市" if year_return > 0.3 else ("🔴 熊市" if year_return < -0.3 else "📊 震荡") - print(f" {year}年: {year_return*100:+7.2f}% {trend}") - - # 步骤2: 数据预处理(预测未来1小时) - print("\n[2/8] 数据预处理(预测未来1小时的涨跌)...") - preprocessor = CryptoPreprocessor( - lookback=168, # 输入:过去168小时(7天) - forecast_horizon=1, # 预测:未来1小时 - price_change_threshold=0.0 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - - # 计算类别分布 - n_down = (y == 0).sum() - n_up = (y == 1).sum() - print(f"✓ 标签分布:下跌(0) = {n_down} ({n_down/len(y)*100:.1f}%), 上涨(1) = {n_up} ({n_up/len(y)*100:.1f}%)") - - # 计算类别权重 - pos_weight = n_down / n_up if n_up > 0 else 1.0 - print(f"✓ 计算类别权重:pos_weight = {pos_weight:.2f}") - - # 步骤3: 划分数据集(时间序列分割) - print("\n[3/8] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - print(f" 训练集:{len(train_data[0])} 样本") - print(f" 下跌: {(train_data[1]==0).sum()}, 上涨: {(train_data[1]==1).sum()}") - print(f" 验证集:{len(val_data[0])} 样本") - print(f" 下跌: {(val_data[1]==0).sum()}, 上涨: {(val_data[1]==1).sum()}") - print(f" 测试集:{len(test_data[0])} 样本") - print(f" 下跌: {(test_data[1]==0).sum()}, 上涨: {(test_data[1]==1).sum()}") - - # 分析各数据集对应的时间范围 - lookback = 168 - train_end_idx = int(len(processed_df) * 0.7) - lookback - val_end_idx = int(len(processed_df) * 0.85) - lookback - - train_df = processed_df.iloc[lookback:train_end_idx+lookback] - val_df = processed_df.iloc[train_end_idx+lookback:val_end_idx+lookback] - test_df = processed_df.iloc[val_end_idx+lookback:] - - print(f"\n 训练集时间范围: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" 价格: ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f} ({train_return*100:+.1f}%)") - - print(f" 验证集时间范围: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" 价格: ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f} ({val_return*100:+.1f}%)") - - print(f" 测试集时间范围: {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - test_return = (test_df['close'].iloc[-1] - test_df['close'].iloc[0]) / test_df['close'].iloc[0] - print(f" 价格: ${test_df['close'].iloc[0]:,.0f} → ${test_df['close'].iloc[-1]:,.0f} ({test_return*100:+.1f}%)") - - # 步骤4: 创建数据加载器 - print("\n[4/8] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', - batch_size=64 - ) - - # 步骤5: 创建模型 - print("\n[5/8] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='classification', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - - # 步骤6: 训练模型 - print("\n[6/8] 开始训练模型(数据量大,预计需要更长时间)...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_2020_2025' - ) - - trainer = Trainer( - model, - train_config, - task='classification', - pos_weight=pos_weight - ) - - print(f"✓ 训练数据规模:{len(train_data[0]):,} 样本(是之前的 {len(train_data[0])/5990:.1f}x)") - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/8] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 保存评估结果 - results_path = './models/checkpoints/dlinear_btc_2020_2025/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 步骤8: 对比分析 - print("\n[8/8] 性能对比分析...") - print("\n" + "="*80) - print("训练完成 - 最终测试结果(使用2020-2025年数据)") - print("="*80) - - print(f"\n📊 分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f} ({metrics.get('accuracy', 0)*100:.2f}%)") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - tp = metrics['true_positives'] - tn = metrics['true_negatives'] - fp = metrics['false_positives'] - fn = metrics['false_negatives'] - - print(f"\n📈 混淆矩阵:") - print(f" 真阳性(预测涨且真涨): {tp}") - print(f" 真阴性(预测跌且真跌): {tn}") - print(f" 假阳性(预测涨但真跌): {fp}") - print(f" 假阴性(预测跌但真涨): {fn}") - - total = tp + tn + fp + fn - if total > 0: - print(f"\n 预测分布:") - print(f" 预测上涨: {tp + fp} ({(tp+fp)/total*100:.1f}%)") - print(f" 预测下跌: {tn + fn} ({(tn+fn)/total*100:.1f}%)") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - max_dd = metrics.get('backtest_max_drawdown', 0) - if not np.isnan(max_dd): - print(f" 最大回撤: {max_dd*100:.2f}%") - else: - print(f" 最大回撤: N/A") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 性能评估 - print(f"\n🎯 性能评估:") - acc = metrics.get('accuracy', 0) - if acc > 0.55: - print(f" ✅ 准确率 {acc*100:.1f}% > 55%,模型有一定预测能力") - elif acc > 0.50: - print(f" ⚠️ 准确率 {acc*100:.1f}% 略优于随机(50%),预测能力较弱") - else: - print(f" ❌ 准确率 {acc*100:.1f}% ≤ 50%,模型预测能力不足") - - total_return = metrics.get('backtest_total_return', 0) - if total_return > 0.05: - print(f" ✅ 回测收益 {total_return*100:.2f}% > 5%,策略有较好盈利潜力") - elif total_return > 0: - print(f" ⚠️ 回测收益 {total_return*100:.2f}% > 0,策略有微弱盈利") - else: - print(f" ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损") - - # 对比之前的结果 - print(f"\n📊 与之前结果对比:") - print(f" 使用1年数据(2025年):") - print(f" - 训练集: 2025.02-10 (牛市+16%)") - print(f" - 测试集: 2025.12-2026.01 (熊市-2%)") - print(f" - 准确率: 50.7%") - print(f" - 预测上涨: 65.8% (偏向上涨)") - print(f" - 问题: 训练测试分布不一致") - - print(f"\n 使用5年数据(2020-2025年):") - print(f" - 训练集: 包含牛市(2020-2021)和熊市(2022)") - print(f" - 测试集: 2024-2025数据") - print(f" - 准确率: {acc*100:.1f}%") - if total > 0: - print(f" - 预测上涨: {(tp+fp)/total*100:.1f}%") - print(f" - 改进: 见过完整市场周期,泛化更好") - - print("\n" + "="*80) - print("✓ 训练完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_dlinear_real.py b/train_dlinear_real.py deleted file mode 100644 index cfbbb52..0000000 --- a/train_dlinear_real.py +++ /dev/null @@ -1,163 +0,0 @@ -"""使用真实Binance数据训练DLinear模型""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json - -def main(): - print("="*80) - print("DLinear模型训练 - 真实Binance数据") - print("="*80) - - # 步骤1: 获取真实数据 - print("\n[1/7] 从Binance获取BTC 1h真实数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv('BTC', '1h', use_cache=True) - print(f"✓ 已获取 {len(df)} 行真实数据") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 步骤2: 数据预处理 - print("\n[2/7] 数据预处理(技术指标 + 特征工程)...") - preprocessor = CryptoPreprocessor(lookback=168, forecast_horizon=24) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='classification', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - print(f"✓ 标签分布:下跌(0) = {(y==0).sum()}, 上涨(1) = {(y==1).sum()}") - - # 步骤3: 划分数据集 - print("\n[3/7] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - # 步骤4: 创建数据加载器 - print("\n[4/7] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='classification', - batch_size=64 - ) - print(f"✓ 训练批次:{len(train_loader)}") - print(f"✓ 验证批次:{len(val_loader)}") - print(f"✓ 测试批次:{len(test_loader)}") - - # 步骤5: 创建模型 - print("\n[5/7] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='classification', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - - # 步骤6: 训练模型 - print("\n[6/7] 开始训练模型...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_1h_real' - ) - - trainer = Trainer(model, train_config, task='classification') - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/7] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 保存评估结果 - results_path = './models/checkpoints/dlinear_btc_1h_real/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 打印结果摘要 - print("\n" + "="*80) - print("训练完成 - 最终测试结果") - print("="*80) - print(f"\n分类指标:") - print(f" 准确率: {metrics.get('accuracy', 0):.4f}") - print(f" F1分数: {metrics.get('f1', 0):.4f}") - print(f" 精确率: {metrics.get('precision', 0):.4f}") - print(f" 召回率: {metrics.get('recall', 0):.4f}") - - if 'true_positives' in metrics: - print(f"\n混淆矩阵:") - print(f" 真阳性: {metrics['true_positives']}") - print(f" 真阴性: {metrics['true_negatives']}") - print(f" 假阳性: {metrics['false_positives']}") - print(f" 假阴性: {metrics['false_negatives']}") - - print(f"\n回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - print(f" 最大回撤: {metrics.get('backtest_max_drawdown', 0)*100:.2f}%") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # ONNX导出测试 - print("\n" + "="*80) - print("测试ONNX导出") - print("="*80) - - try: - import onnx - import onnxruntime as ort - import torch - import numpy as np - - onnx_path = './models/checkpoints/dlinear_btc_1h_real/model.onnx' - - print("\n[1/3] 导出模型到ONNX格式...") - model.to_onnx(onnx_path) - print(f"✓ ONNX模型已保存至:{onnx_path}") - - print("\n[2/3] 验证ONNX模型...") - onnx_model = onnx.load(onnx_path) - onnx.checker.check_model(onnx_model) - print("✓ ONNX模型有效") - - print("\n[3/3] 测试ONNX推理与PyTorch对比...") - test_input = torch.randn(1, 168, X.shape[-1]) - - model.cpu() - model.eval() - with torch.no_grad(): - pytorch_output = model(test_input).numpy() - - ort_session = ort.InferenceSession(onnx_path) - onnx_output = ort_session.run(None, {'input': test_input.numpy()})[0] - - diff = np.abs(pytorch_output - onnx_output).max() - print(f"✓ PyTorch与ONNX的最大差异:{diff:.8f}") - - if diff < 1e-5: - print("✓ ONNX导出成功!输出在容差范围内匹配。") - else: - print("⚠ 警告:ONNX输出与PyTorch有差异") - - except Exception as e: - print(f"✗ ONNX导出失败:{e}") - - print("\n" + "="*80) - print("✓ DLinear模型训练完成(真实数据)") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_dlinear_regression.py b/train_dlinear_regression.py deleted file mode 100644 index d0066e2..0000000 --- a/train_dlinear_regression.py +++ /dev/null @@ -1,258 +0,0 @@ -"""使用回归任务训练DLinear模型 - 预测未来1小时的实际价格变化百分比""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("DLinear回归模型训练 - 预测未来1小时价格变化百分比") - print("="*80) - - # 步骤1: 获取2020-2025年完整数据 - print("\n[1/8] 从Binance获取BTC 2020-2025年完整数据...") - fetcher = BinanceDataFetcher() - - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 已获取 {len(df)} 行数据(约{len(df)/8760:.1f}年)") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - - # 分析数据覆盖的市场周期 - print(f"\n✓ 价格变化分析:") - print(f" 起始价格: ${df['close'].iloc[0]:,.2f} (2020年)") - print(f" 结束价格: ${df['close'].iloc[-1]:,.2f} (2025年)") - total_return = (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0] - print(f" 总收益率: {total_return*100:+.2f}%") - - # 按年分析 - df['year'] = df['timestamp'].dt.year - print(f"\n✓ 年度表现分析(确保包含牛熊周期):") - for year in sorted(df['year'].unique()): - year_df = df[df['year'] == year] - if len(year_df) > 100: - year_return = (year_df['close'].iloc[-1] - year_df['close'].iloc[0]) / year_df['close'].iloc[0] - trend = "🚀 牛市" if year_return > 0.3 else ("🔴 熊市" if year_return < -0.3 else "📊 震荡") - print(f" {year}年: {year_return*100:+7.2f}% {trend}") - - # 步骤2: 数据预处理(回归任务) - print("\n[2/8] 数据预处理(回归任务 - 预测未来1小时的收益率)...") - preprocessor = CryptoPreprocessor( - lookback=168, # 输入:过去168小时(7天) - forecast_horizon=1, # 预测:未来1小时 - price_change_threshold=0.0 # 回归任务不使用阈值 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='regression', fit=True) - print(f"✓ 已创建序列:X形状 = {X.shape}, y形状 = {y.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - - # 分析标签分布(收益率) - print(f"\n✓ 标签统计(收益率):") - print(f" 最小值: {y.min()*100:.4f}%") - print(f" 最大值: {y.max()*100:.4f}%") - print(f" 均值: {y.mean()*100:.4f}%") - print(f" 标准差: {y.std()*100:.4f}%") - print(f" 中位数: {np.median(y)*100:.4f}%") - - # 方向统计 - n_positive = (y > 0).sum() - n_negative = (y <= 0).sum() - print(f"\n✓ 方向分布:") - print(f" 正收益(上涨): {n_positive} ({n_positive/len(y)*100:.1f}%)") - print(f" 负收益(下跌): {n_negative} ({n_negative/len(y)*100:.1f}%)") - - # 步骤3: 划分数据集 - print("\n[3/8] 划分训练/验证/测试集 (70/15/15)...") - train_data, val_data, test_data = split_data(X, y, train_split=0.7, val_split=0.15) - - print(f" 训练集:{len(train_data[0])} 样本") - print(f" 收益率范围: {train_data[1].min()*100:.4f}% ~ {train_data[1].max()*100:.4f}%") - print(f" 均值: {train_data[1].mean()*100:.4f}%") - - print(f" 验证集:{len(val_data[0])} 样本") - print(f" 收益率范围: {val_data[1].min()*100:.4f}% ~ {val_data[1].max()*100:.4f}%") - print(f" 均值: {val_data[1].mean()*100:.4f}%") - - print(f" 测试集:{len(test_data[0])} 样本") - print(f" 收益率范围: {test_data[1].min()*100:.4f}% ~ {test_data[1].max()*100:.4f}%") - print(f" 均值: {test_data[1].mean()*100:.4f}%") - - # 分析各数据集对应的时间范围 - lookback = 168 - train_end_idx = int(len(processed_df) * 0.7) - lookback - val_end_idx = int(len(processed_df) * 0.85) - lookback - - train_df = processed_df.iloc[lookback:train_end_idx+lookback] - val_df = processed_df.iloc[train_end_idx+lookback:val_end_idx+lookback] - test_df = processed_df.iloc[val_end_idx+lookback:] - - print(f"\n 训练集时间范围: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" 价格: ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f} ({train_return*100:+.1f}%)") - - print(f" 验证集时间范围: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" 价格: ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f} ({val_return*100:+.1f}%)") - - print(f" 测试集时间范围: {test_df['timestamp'].min()} 至 {test_df['timestamp'].max()}") - test_return = (test_df['close'].iloc[-1] - test_df['close'].iloc[0]) / test_df['close'].iloc[0] - print(f" 价格: ${test_df['close'].iloc[0]:,.0f} → ${test_df['close'].iloc[-1]:,.0f} ({test_return*100:+.1f}%)") - - # 步骤4: 创建数据加载器 - print("\n[4/8] 创建PyTorch数据加载器...") - train_loader, val_loader, test_loader = create_dataloaders( - train_data, val_data, test_data, - task='regression', # 回归任务 - batch_size=64 - ) - - # 步骤5: 创建模型 - print("\n[5/8] 创建DLinear回归模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='regression', # 回归任务 - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型已创建,可训练参数:{model.count_parameters():,} 个") - print(f"✓ 任务类型:回归(预测收益率百分比)") - print(f"✓ 损失函数:MSELoss") - - # 步骤6: 训练模型 - print("\n[6/8] 开始训练回归模型...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=10, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_regression' - ) - - trainer = Trainer( - model, - train_config, - task='regression' # 回归任务,不需要pos_weight - ) - - print(f"✓ 训练数据规模:{len(train_data[0]):,} 样本") - history = trainer.fit(train_loader, val_loader) - - # 步骤7: 评估模型 - print("\n[7/8] 在测试集上评估模型...") - metrics = trainer.evaluate(test_loader) - - # 保存评估结果 - results_path = './models/checkpoints/dlinear_btc_regression/test_metrics.json' - with open(results_path, 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 评估结果已保存至:{results_path}") - - # 步骤8: 结果分析 - print("\n[8/8] 回归模型性能分析...") - print("\n" + "="*80) - print("训练完成 - 回归模型测试结果") - print("="*80) - - print(f"\n📊 回归指标:") - print(f" RMSE (均方根误差): {metrics.get('rmse', 0):.6f} ({metrics.get('rmse', 0)*100:.4f}%)") - print(f" MAE (平均绝对误差): {metrics.get('mae', 0):.6f} ({metrics.get('mae', 0)*100:.4f}%)") - print(f" MAPE (平均绝对百分比误差): {metrics.get('mape', 0):.2f}%") - print(f" 方向准确率: {metrics.get('directional_accuracy', 0)*100:.2f}%") - - print(f"\n💰 回测结果:") - print(f" 总收益率: {metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 最终资金: ${metrics.get('backtest_final_capital', 0):,.2f}") - print(f" 夏普比率: {metrics.get('backtest_sharpe_ratio', 0):.4f}") - max_dd = metrics.get('backtest_max_drawdown', 0) - if not np.isnan(max_dd): - print(f" 最大回撤: {max_dd*100:.2f}%") - else: - print(f" 最大回撤: N/A") - print(f" 胜率: {metrics.get('backtest_win_rate', 0)*100:.2f}%") - print(f" 交易次数: {metrics.get('backtest_num_trades', 0)}") - - # 与分类任务对比 - print("\n" + "="*80) - print("📊 回归 vs 分类任务对比") - print("="*80) - - print(f"\n二分类模型(之前的结果):") - print(f" 准确率: 49.76%") - print(f" F1分数: 0.3898") - print(f" 预测偏差: 68.1%预测下跌(过度悲观)") - print(f" 回测收益: -3.29%") - - print(f"\n回归模型(当前结果):") - print(f" 方向准确率: {metrics.get('directional_accuracy', 0)*100:.2f}%") - print(f" RMSE: {metrics.get('rmse', 0)*100:.4f}%") - print(f" MAE: {metrics.get('mae', 0)*100:.4f}%") - print(f" 回测收益: {metrics.get('backtest_total_return', 0)*100:.2f}%") - - # 性能评估 - print(f"\n🎯 回归模型性能评估:") - - dir_acc = metrics.get('directional_accuracy', 0) - 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%,方向预测能力不足") - - rmse = metrics.get('rmse', 0) - mae = metrics.get('mae', 0) - print(f"\n 误差分析:") - print(f" RMSE = {rmse*100:.4f}% - 平均预测误差的平方根") - print(f" MAE = {mae*100:.4f}% - 平均绝对误差") - if rmse < 0.01: # 小于1% - print(f" ✅ RMSE < 1%,预测精度较高") - elif rmse < 0.02: # 小于2% - print(f" ⚠️ RMSE在1-2%之间,预测精度中等") - else: - print(f" ❌ RMSE > 2%,预测误差较大") - - total_return = metrics.get('backtest_total_return', 0) - if total_return > 0.05: - print(f"\n ✅ 回测收益 {total_return*100:.2f}% > 5%,策略有盈利潜力") - elif total_return > 0: - print(f"\n ⚠️ 回测收益 {total_return*100:.2f}% > 0,策略微弱盈利") - else: - print(f"\n ❌ 回测收益 {total_return*100:.2f}% ≤ 0,策略亏损") - - print("\n💡 回归任务的优势:") - print(" ✅ 提供预测幅度信息(可用于动态仓位管理)") - print(" ✅ 评估风险收益比") - print(" ✅ 更适合量化交易策略") - - print("\n💡 回归任务的挑战:") - print(" ❌ 预测具体数值比预测方向更难") - print(" ❌ 对异常值敏感") - print(" ❌ 需要更多训练数据才能稳定") - - print("\n" + "="*80) - print("✓ 回归模型训练完成!") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_full_data.py b/train_full_data.py deleted file mode 100644 index 9123598..0000000 --- a/train_full_data.py +++ /dev/null @@ -1,233 +0,0 @@ -"""方案A:使用2020-2025年BTC全部数据训练 - 准备实盘使用""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -from data.fetcher import BinanceDataFetcher -from data.preprocessor import CryptoPreprocessor, split_data -from data.dataset import create_dataloaders -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import numpy as np - -def main(): - print("="*80) - print("方案A:BTC全量数据训练 (2020-2025)") - print("训练集:95% | 验证集:5%(仅用于early stopping)| 测试集:无") - print("="*80) - - # ========== 步骤1: 获取全部BTC数据 ========== - print("\n[1/7] 获取BTC 2020-2025年全部数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 已获取 {len(df):,} 行数据(约{len(df)/8760:.1f}年)") - print(f"✓ 时间范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") - print(f"✓ 价格范围:${df['close'].iloc[0]:,.2f} → ${df['close'].iloc[-1]:,.2f}") - total_return = (df['close'].iloc[-1] - df['close'].iloc[0]) / df['close'].iloc[0] - print(f"✓ 总收益率:{total_return*100:+.2f}%") - - # 市场周期分析 - df['year'] = df['timestamp'].dt.year - print(f"\n✓ 市场周期覆盖:") - for year in sorted(df['year'].unique()): - year_df = df[df['year'] == year] - if len(year_df) > 100: - year_return = (year_df['close'].iloc[-1] - year_df['close'].iloc[0]) / year_df['close'].iloc[0] - trend = "🚀 牛市" if year_return > 0.3 else ("🔴 熊市" if year_return < -0.3 else "📊 震荡") - print(f" {year}年: {year_return*100:+7.2f}% {trend}") - - # ========== 步骤2: 数据预处理 ========== - print("\n[2/7] 数据预处理(回归任务)...") - preprocessor = CryptoPreprocessor( - lookback=168, # 输入:过去7天 - forecast_horizon=1, # 预测:未来1小时 - price_change_threshold=0.0 - ) - X, y, processed_df = preprocessor.preprocess_pipeline(df, task='regression', fit=True) - - print(f"✓ 序列数量:{len(X):,}") - print(f"✓ 序列形状:{X.shape}") - print(f"✓ 特征数量:{X.shape[-1]}") - - print(f"\n✓ 标签统计(收益率):") - print(f" 最小值: {y.min()*100:.4f}%") - print(f" 最大值: {y.max()*100:.4f}%") - print(f" 均值: {y.mean()*100:.4f}%") - print(f" 标准差: {y.std()*100:.4f}%") - print(f" 中位数: {np.median(y)*100:.4f}%") - - n_positive = (y > 0).sum() - n_negative = (y <= 0).sum() - print(f"\n✓ 方向分布:") - print(f" 上涨: {n_positive:,} ({n_positive/len(y)*100:.1f}%)") - print(f" 下跌: {n_negative:,} ({n_negative/len(y)*100:.1f}%)") - - # ========== 步骤3: 划分数据集(95% 训练 + 5% 验证)========== - print("\n[3/7] 划分数据集(95%训练 + 5%验证)...") - print("⚠️ 注意:此次训练使用几乎全部数据,无独立测试集") - print(" 验证集仅用于early stopping防止过拟合") - - n = len(X) - train_end = int(n * 0.95) - - X_train = X[:train_end] - y_train = y[:train_end] - X_val = X[train_end:] - y_val = y[train_end:] - - print(f"\n 训练集:{len(X_train):,} 样本 ({len(X_train)/n*100:.1f}%)") - print(f" 收益率范围: {y_train.min()*100:.4f}% ~ {y_train.max()*100:.4f}%") - - print(f" 验证集:{len(X_val):,} 样本 ({len(X_val)/n*100:.1f}%)") - print(f" 收益率范围: {y_val.min()*100:.4f}% ~ {y_val.max()*100:.4f}%") - - # 时间范围分析 - lookback = 168 - train_df = processed_df.iloc[lookback:train_end+lookback] - val_df = processed_df.iloc[train_end+lookback:] - - print(f"\n 时间范围:") - print(f" 训练集: {train_df['timestamp'].min()} 至 {train_df['timestamp'].max()}") - train_return = (train_df['close'].iloc[-1] - train_df['close'].iloc[0]) / train_df['close'].iloc[0] - print(f" ${train_df['close'].iloc[0]:,.0f} → ${train_df['close'].iloc[-1]:,.0f} ({train_return*100:+.1f}%)") - - print(f" 验证集: {val_df['timestamp'].min()} 至 {val_df['timestamp'].max()}") - val_return = (val_df['close'].iloc[-1] - val_df['close'].iloc[0]) / val_df['close'].iloc[0] - print(f" ${val_df['close'].iloc[0]:,.0f} → ${val_df['close'].iloc[-1]:,.0f} ({val_return*100:+.1f}%)") - - # ========== 步骤4: 创建数据加载器 ========== - print("\n[4/7] 创建数据加载器...") - train_loader, val_loader, _ = create_dataloaders( - (X_train, y_train), - (X_val, y_val), - (X_val, y_val), # 测试集用验证集替代(不使用) - task='regression', - batch_size=64 - ) - - # ========== 步骤5: 创建模型 ========== - print("\n[5/7] 创建DLinear回归模型...") - model_config = DLinearConfig( - input_dim=X.shape[-1], - lookback=168, - task='regression', - hidden_dim=512, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型参数:{model.count_parameters():,} 个") - print(f"✓ 任务类型:回归(预测未来1小时收益率)") - print(f"✓ 损失函数:MSELoss") - - # ========== 步骤6: 训练模型 ========== - print("\n[6/7] 开始全量数据训练...") - print(f"✓ 训练样本:{len(X_train):,} (使用了{len(X_train)/len(X)*100:.1f}%的数据)") - print(f"✓ 对比之前:之前训练集36,645样本,现在{len(X_train):,}样本(+{(len(X_train)-36645)/36645*100:.1f}%)") - - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=15, # 增加patience,允许更充分训练 - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_btc_full_data' - ) - - trainer = Trainer(model, train_config, task='regression') - history = trainer.fit(train_loader, val_loader) - - # ========== 步骤7: 保存最终模型 ========== - print("\n[7/7] 保存最终模型...") - - # 在验证集上快速评估 - print("\n在验证集上进行sanity check...") - val_metrics = trainer.evaluate(val_loader) - - results_path = './models/checkpoints/dlinear_btc_full_data/final_metrics.json' - with open(results_path, 'w') as f: - json.dump(val_metrics, f, indent=2) - print(f"✓ 结果保存至:{results_path}") - - # 同时保存预处理器(实盘使用时需要) - import pickle - preprocessor_path = './models/checkpoints/dlinear_btc_full_data/preprocessor.pkl' - with open(preprocessor_path, 'wb') as f: - pickle.dump(preprocessor, f) - print(f"✓ 预处理器保存至:{preprocessor_path}") - - # ========== 最终报告 ========== - print("\n" + "="*80) - print("全量数据训练完成 - 最终报告") - print("="*80) - - print(f"\n📊 训练概况:") - print(f" 数据时间范围:2020-01-01 至 2025-12-31 (6年)") - print(f" 训练样本数量:{len(X_train):,} (95%)") - print(f" 验证样本数量:{len(X_val):,} (5%)") - print(f" 特征数量:{X.shape[-1]}") - print(f" 模型参数:{model.count_parameters():,}") - - print(f"\n📈 验证集性能(仅供参考,非独立测试):") - print(f" RMSE: {val_metrics.get('rmse', 0)*100:.4f}%") - print(f" MAE: {val_metrics.get('mae', 0)*100:.4f}%") - print(f" 方向准确率: {val_metrics.get('directional_accuracy', 0)*100:.2f}%") - - print(f"\n💰 验证集回测(仅供参考):") - print(f" 总收益率: {val_metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 夏普比率: {val_metrics.get('backtest_sharpe_ratio', 0):.4f}") - max_dd = val_metrics.get('backtest_max_drawdown', 0) - if not np.isnan(max_dd): - print(f" 最大回撤: {max_dd*100:.2f}%") - print(f" 胜率: {val_metrics.get('backtest_win_rate', 0)*100:.2f}%") - - print("\n" + "="*80) - print("🎯 全量训练 vs 之前训练对比") - print("="*80) - - print("\n之前训练(70/15/15分割):") - print(" 训练样本: 36,645") - print(" 时间范围: 2020-01 至 2024-03") - print(" 测试集方向准确率: 50.67%") - print(" 测试集回测收益: -16.71%") - - print(f"\n当前全量训练(95/5分割):") - print(f" 训练样本: {len(X_train):,} (+{(len(X_train)-36645)/36645*100:.1f}%)") - print(f" 时间范围: 2020-01 至 {train_df['timestamp'].max().strftime('%Y-%m')}") - print(f" 验证集方向准确率: {val_metrics.get('directional_accuracy', 0)*100:.2f}%") - print(f" 验证集回测收益: {val_metrics.get('backtest_total_return', 0)*100:.2f}%") - - print("\n" + "="*80) - print("⚠️ 重要说明") - print("="*80) - print("\n1. 此模型使用了95%的历史数据训练") - print("2. 验证集仅占5%,性能指标仅供参考,不代表真实泛化能力") - print("3. 模型已针对2020-2025年市场环境充分优化") - print("4. 适用场景:") - print(" ✅ 用于2026年未来数据的预测") - print(" ✅ 用于实盘交易(假设未来市场特征与历史相似)") - print(" ❌ 不适合评估在未见过数据上的泛化性能") - - print("\n💡 下一步建议:") - print(" 1. 使用此模型预测2026年1月的BTC价格") - print(" 2. 在实盘中小仓位测试策略") - print(" 3. 定期(每月/季度)用新数据重新训练模型") - print(" 4. 监控实盘性能,如果偏差大说明市场环境变化") - - print("\n" + "="*80) - print("✓ 训练完成!模型已保存到 ./models/checkpoints/dlinear_btc_full_data/") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_predict_price.py b/train_predict_price.py deleted file mode 100644 index 03e9e34..0000000 --- a/train_predict_price.py +++ /dev/null @@ -1,260 +0,0 @@ -"""直接预测未来1小时的价格(而非收益率)""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import numpy as np -import pandas as pd -import pandas_ta as ta -from sklearn.preprocessing import StandardScaler -import torch -import torch.nn as nn -from torch.utils.data import DataLoader, TensorDataset - -from data.fetcher import BinanceDataFetcher -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import pickle - -class PricePreprocessor: - """预测未来价格的预处理器""" - - def __init__(self, lookback=168, forecast_horizon=1): - self.lookback = lookback - self.forecast_horizon = forecast_horizon - self.scaler = StandardScaler() - self.price_scaler = StandardScaler() # 单独的价格缩放器 - self.is_fitted = False - - def add_features(self, df): - """添加技术指标""" - df = df.copy() - - # RSI - df['rsi'] = ta.rsi(df['close'], length=14) - - # 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] - - return df - - def create_labels(self, df): - """创建标签:未来1小时的价格(绝对价格)""" - df = df.copy() - # 标签是未来1小时的收盘价 - df['future_price'] = df['close'].shift(-self.forecast_horizon) - df['label'] = df['future_price'] - return df[:-self.forecast_horizon] - - def preprocess(self, df, fit=True): - """完整预处理流程""" - # 添加技术指标 - df = self.add_features(df) - df = self.create_labels(df) - - # 删除NaN - 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]) - # 单独标准化价格标签(用于训练) - scaled_labels = self.price_scaler.fit_transform(df[['label']]) - 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]) - scaled_labels = self.price_scaler.transform(df[['label']]) - - # 创建序列 - X, y = [], [] - for i in range(len(scaled_features) - self.lookback): - X.append(scaled_features[i:i + self.lookback]) - y.append(scaled_labels[i + self.lookback, 0]) # 标准化后的价格 - - return np.array(X), np.array(y), df - - def inverse_transform_price(self, scaled_price): - """将标准化的价格还原为真实价格""" - return self.price_scaler.inverse_transform(scaled_price.reshape(-1, 1)).flatten() - -def main(): - print("="*80) - print("训练模型:直接预测未来1小时价格") - print("="*80) - - # ========== 步骤1: 获取数据 ========== - print("\n[1/7] 获取BTC 2020-2025年数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 数据量: {len(df):,} 行") - print(f"✓ 时间: {df['timestamp'].min()} 至 {df['timestamp'].max()}") - print(f"✓ 价格: ${df['close'].iloc[0]:,.2f} → ${df['close'].iloc[-1]:,.2f}") - - # ========== 步骤2: 预处理 ========== - print("\n[2/7] 数据预处理(预测绝对价格)...") - preprocessor = PricePreprocessor(lookback=168, forecast_horizon=1) - X, y, processed_df = preprocessor.preprocess(df, fit=True) - - print(f"✓ 序列数量: {len(X):,}") - print(f"✓ 序列形状: {X.shape}") - print(f"✓ 特征数量: {len(preprocessor.feature_columns)}") - - # 还原真实价格看看统计 - y_real = preprocessor.inverse_transform_price(y) - print(f"\n✓ 目标价格统计(真实值):") - print(f" 最小值: ${y_real.min():,.2f}") - print(f" 最大值: ${y_real.max():,.2f}") - print(f" 均值: ${y_real.mean():,.2f}") - - # ========== 步骤3: 划分数据集(95/5)========== - print("\n[3/7] 划分数据集(95%训练 + 5%验证)...") - n = len(X) - train_end = int(n * 0.95) - - X_train, y_train = X[:train_end], y[:train_end] - X_val, y_val = X[train_end:], y[train_end:] - - print(f" 训练集: {len(X_train):,} 样本") - print(f" 验证集: {len(X_val):,} 样本") - - # ========== 步骤4: 创建数据加载器 ========== - print("\n[4/7] 创建数据加载器...") - train_dataset = TensorDataset( - torch.FloatTensor(X_train), - torch.FloatTensor(y_train) - ) - val_dataset = TensorDataset( - torch.FloatTensor(X_val), - torch.FloatTensor(y_val) - ) - - train_loader = DataLoader(train_dataset, batch_size=64, shuffle=False) - val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) - - # ========== 步骤5: 创建模型 ========== - print("\n[5/7] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=9, - lookback=168, - task='regression', # 仍然是回归,但预测价格而非收益率 - hidden_dim=256, - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型参数: {model.count_parameters():,}") - print(f"✓ 预测目标: 未来1小时的绝对价格") - - # ========== 步骤6: 训练 ========== - print("\n[6/7] 开始训练...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=15, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_predict_price' - ) - - trainer = Trainer(model, train_config, task='regression') - history = trainer.fit(train_loader, val_loader) - - # ========== 步骤7: 评估 ========== - print("\n[7/7] 评估模型...") - - # 在验证集上预测 - device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu') - model = model.to(device) - model.eval() - - val_preds = [] - with torch.no_grad(): - for batch_X, _ in val_loader: - batch_X = batch_X.to(device) - pred = model(batch_X).squeeze() - val_preds.extend(pred.cpu().numpy()) - - val_preds = np.array(val_preds) - - # 还原为真实价格 - pred_prices = preprocessor.inverse_transform_price(val_preds) - true_prices = preprocessor.inverse_transform_price(y_val) - - # 计算当前价格(用于判断方向) - val_start_idx = train_end + 168 # +168是lookback - current_prices = processed_df['close'].iloc[val_start_idx:val_start_idx+len(pred_prices)].values - - # 方向准确率 - pred_direction = (pred_prices > current_prices).astype(int) - true_direction = (true_prices > current_prices).astype(int) - direction_acc = (pred_direction == true_direction).mean() - - # 价格预测误差 - price_mae = np.abs(pred_prices - true_prices).mean() - price_rmse = np.sqrt(((pred_prices - true_prices) ** 2).mean()) - price_mape = np.mean(np.abs((true_prices - pred_prices) / true_prices)) * 100 - - print("\n" + "="*80) - print("训练完成 - 验证集性能") - print("="*80) - - print(f"\n📊 价格预测误差:") - print(f" MAE: ${price_mae:,.2f}") - print(f" RMSE: ${price_rmse:,.2f}") - print(f" MAPE: {price_mape:.2f}%") - - print(f"\n📈 方向预测:") - print(f" 方向准确率: {direction_acc*100:.2f}%") - print(f" 预测上涨: {(pred_direction==1).sum()} / 实际上涨: {(true_direction==1).sum()}") - print(f" 预测下跌: {(pred_direction==0).sum()} / 实际下跌: {(true_direction==0).sum()}") - - print(f"\n💡 价格预测示例(前10个):") - print(f"{'当前价格':<12} {'预测价格':<12} {'实际价格':<12} {'预测方向':<8} {'实际方向':<8}") - print("-"*70) - for i in range(min(10, len(pred_prices))): - pred_dir = "上涨" if pred_prices[i] > current_prices[i] else "下跌" - true_dir = "上涨" if true_prices[i] > current_prices[i] else "下跌" - print(f"${current_prices[i]:<11,.2f} ${pred_prices[i]:<11,.2f} ${true_prices[i]:<11,.2f} " - f"{pred_dir:<8} {true_dir:<8}") - - # 保存模型和预处理器 - with open('./models/checkpoints/dlinear_predict_price/preprocessor.pkl', 'wb') as f: - pickle.dump(preprocessor, f) - - # 保存评估指标 - metrics = { - 'price_mae': float(price_mae), - 'price_rmse': float(price_rmse), - 'price_mape': float(price_mape), - 'direction_accuracy': float(direction_acc) - } - with open('./models/checkpoints/dlinear_predict_price/metrics.json', 'w') as f: - json.dump(metrics, f, indent=2) - - print(f"\n✓ 模型保存至: ./models/checkpoints/dlinear_predict_price/") - print("="*80) - -if __name__ == "__main__": - main() diff --git a/train_simple_features.py b/train_simple_features.py deleted file mode 100644 index 6e047ac..0000000 --- a/train_simple_features.py +++ /dev/null @@ -1,206 +0,0 @@ -"""简化特征训练:只使用OHLCV + RSI + MACD""" - -import sys -import os -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) - -import numpy as np -import pandas as pd -import pandas_ta as ta -from sklearn.preprocessing import StandardScaler -import torch -import torch.nn as nn -from torch.utils.data import DataLoader, TensorDataset - -from data.fetcher import BinanceDataFetcher -from models.dlinear.model import DLinear, DLinearConfig -from training.trainer import Trainer -from training.config import TrainingConfig -import json -import pickle - -class SimplePreprocessor: - """简化的预处理器:只使用OHLCV + RSI + MACD""" - - 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): - """添加RSI和MACD特征""" - df = df.copy() - - # RSI (14 periods) - df['rsi'] = ta.rsi(df['close'], length=14) - - # 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] - - return df - - def create_labels(self, df): - """创建标签:未来1小时的收益率""" - 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'] - return df[:-self.forecast_horizon] - - def preprocess(self, df, fit=True): - """完整预处理流程""" - # 添加技术指标 - df = self.add_features(df) - df = self.create_labels(df) - - # 删除NaN - df = df.dropna().reset_index(drop=True) - - # 选择特征列:OHLCV + RSI + MACD - feature_cols = ['open', 'high', 'low', 'close', 'volume', - 'rsi', 'macd', 'macd_hist', 'macd_signal'] - - # 标准化 - if fit: - scaled = 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 = self.scaler.transform(df[feature_cols]) - - # 创建序列 - X, y = [], [] - for i in range(len(scaled) - self.lookback): - X.append(scaled[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("简化特征训练:OHLCV + RSI + MACD") - print("="*80) - - # ========== 步骤1: 获取数据 ========== - print("\n[1/7] 获取BTC 2020-2025年数据...") - fetcher = BinanceDataFetcher() - df = fetcher.fetch_ohlcv( - symbol='BTC', - timeframe='1h', - start_date='2020-01-01', - end_date='2025-12-31', - use_cache=True - ) - - print(f"✓ 数据量: {len(df):,} 行") - print(f"✓ 时间: {df['timestamp'].min()} 至 {df['timestamp'].max()}") - print(f"✓ 价格: ${df['close'].iloc[0]:,.2f} → ${df['close'].iloc[-1]:,.2f}") - - # ========== 步骤2: 预处理 ========== - print("\n[2/7] 数据预处理(只使用9个特征)...") - preprocessor = SimplePreprocessor(lookback=168, forecast_horizon=1) - X, y, processed_df = preprocessor.preprocess(df, fit=True) - - print(f"✓ 序列数量: {len(X):,}") - print(f"✓ 序列形状: {X.shape}") - print(f"✓ 特征列表:") - for i, col in enumerate(preprocessor.feature_columns, 1): - print(f" {i}. {col}") - - print(f"\n✓ 标签统计:") - print(f" 收益率范围: {y.min()*100:.4f}% ~ {y.max()*100:.4f}%") - print(f" 均值: {y.mean()*100:.4f}%") - print(f" 上涨样本: {(y>0).sum()} ({(y>0).sum()/len(y)*100:.1f}%)") - print(f" 下跌样本: {(y<=0).sum()} ({(y<=0).sum()/len(y)*100:.1f}%)") - - # ========== 步骤3: 划分数据集(95/5)========== - print("\n[3/7] 划分数据集(95%训练 + 5%验证)...") - n = len(X) - train_end = int(n * 0.95) - - X_train, y_train = X[:train_end], y[:train_end] - X_val, y_val = X[train_end:], y[train_end:] - - print(f" 训练集: {len(X_train):,} 样本") - print(f" 验证集: {len(X_val):,} 样本") - - # ========== 步骤4: 创建数据加载器 ========== - print("\n[4/7] 创建数据加载器...") - train_dataset = TensorDataset( - torch.FloatTensor(X_train), - torch.FloatTensor(y_train) - ) - val_dataset = TensorDataset( - torch.FloatTensor(X_val), - torch.FloatTensor(y_val) - ) - - train_loader = DataLoader(train_dataset, batch_size=64, shuffle=False) - val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False) - - # ========== 步骤5: 创建模型 ========== - print("\n[5/7] 创建DLinear模型...") - model_config = DLinearConfig( - input_dim=9, # OHLCV + RSI + MACD(3个) - lookback=168, - task='regression', - hidden_dim=256, # 减小隐藏层 - dropout=0.1, - moving_avg_kernel=25 - ) - model = DLinear(model_config) - print(f"✓ 模型参数: {model.count_parameters():,}") - - # ========== 步骤6: 训练 ========== - print("\n[6/7] 开始训练...") - train_config = TrainingConfig( - batch_size=64, - max_epochs=50, - learning_rate=0.001, - early_stopping_patience=15, - lr_scheduler='plateau', - save_best=True, - checkpoint_dir='./models/checkpoints/dlinear_simple_features' - ) - - trainer = Trainer(model, train_config, task='regression') - history = trainer.fit(train_loader, val_loader) - - # ========== 步骤7: 评估并保存 ========== - print("\n[7/7] 评估并保存...") - val_metrics = trainer.evaluate(val_loader) - - # 保存结果 - with open('./models/checkpoints/dlinear_simple_features/metrics.json', 'w') as f: - json.dump(val_metrics, f, indent=2) - - # 保存预处理器 - with open('./models/checkpoints/dlinear_simple_features/preprocessor.pkl', 'wb') as f: - pickle.dump(preprocessor, f) - - print("\n" + "="*80) - print("训练完成") - print("="*80) - - print(f"\n📊 验证集性能:") - print(f" RMSE: {val_metrics.get('rmse', 0)*100:.4f}%") - print(f" MAE: {val_metrics.get('mae', 0)*100:.4f}%") - print(f" 方向准确率: {val_metrics.get('directional_accuracy', 0)*100:.2f}%") - - print(f"\n💰 验证集回测:") - print(f" 总收益率: {val_metrics.get('backtest_total_return', 0)*100:.2f}%") - print(f" 夏普比率: {val_metrics.get('backtest_sharpe_ratio', 0):.4f}") - - print(f"\n✓ 模型保存至: ./models/checkpoints/dlinear_simple_features/") - print(f"✓ 特征数量: 9 (OHLCV + RSI + MACD)") - print("="*80) - -if __name__ == "__main__": - main()