Clean up: keep only best model (53.10% accuracy)

🧹 清理内容:
- 删除所有实验模型(保留 dlinear_classification_2026)
- 删除所有测试和分析脚本
- 删除所有实验训练脚本

✅ 保留文件:
- models/checkpoints/dlinear_classification_2026/ (最佳模型)
- train_classification_full.py (训练脚本)
- test_2026_classification.py (测试脚本)
- data/, models/, training/ (核心代码)
- README.md, requirements.txt

📦 删除文件统计:
- 9个实验模型目录
- 21个测试/分析/实验脚本

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
crypto_predictorandClaude Sonnet 4.5 committed 2026-01-29 21:58:23 +08:00
1 parent 9bbdc8d42b
commit c74f4f6949
55 files changed
+3 -4698

No files matched your search

+3 -1
View File
@@ -4,7 +4,9 @@
"Bash(/Users/xxz/miniforge3/bin/conda run:*)",
"Bash(tee:*)",
"Bash(python:*)",
"Bash(source:*)"
"Bash(source:*)",
"Bash(ls:*)",
"Bash(git add:*)"
]
}
}
-195
View File
@@ -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()
-244
View File
@@ -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()
-234
View File
@@ -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()
-177
View File
@@ -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()
-134
View File
@@ -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()
-180
View File
@@ -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()
-81
View File
@@ -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()
@@ -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
}
Binary file not shown.
Binary file not shown.
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -1,6 +0,0 @@
{
"price_mae": 528.017854278076,
"price_rmse": 736.6082253699151,
"price_mape": 0.523728600511905,
"direction_accuracy": 0.5022918258212375
}
@@ -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
}
Binary file not shown.
Binary file not shown.
-263
View File
@@ -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()
-202
View File
@@ -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()
-230
View File
@@ -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()
-65
View File
@@ -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()
-169
View File
@@ -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()
-149
View File
@@ -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}")
-304
View File
@@ -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()
-240
View File
@@ -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()
-156
View File
@@ -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()
-190
View File
@@ -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()
-242
View File
@@ -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()
-163
View File
@@ -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()
-258
View File
@@ -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()
-233
View File
@@ -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()
-260
View File
@@ -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()
-206
View File
@@ -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()