Initial commit: BTC 1h prediction model (53.10% accuracy)

🎯 项目成果:
- 准确率: 53.10% (2026年测试集)
- 任务类型: 二分类 (预测涨/跌)
- 特征: OHLCV + RSI + MACD (9个特征)
- 训练数据: 2020-2025年BTC小时数据 (52,351序列)
- 测试数据: 2026年1-29日 (484序列)

📊 性能指标:
- 精确率: 51.79%
- 召回率: 49.36%
- F1分数: 0.5054
- 预测分布: 非常平衡 (偏差仅-11个)

🔬 实验历史:
- 尝试了5种方案 (回归/分类, 不同数据划分)
- 分类任务优于回归任务 (53.10% vs 49.5%)
- 全量训练避免了过拟合问题

📦 主要模型文件:
- models/checkpoints/dlinear_classification_2026/
  - best_model.pt (最佳模型)
  - preprocessor.pkl (预处理器)
  - test_2026_results.json (测试结果)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
crypto_predictorandClaude Sonnet 4.5 committed 2026-01-29 21:27:15 +08:00
commit 9bbdc8d42b
82 files changed
+8742

No files matched your search

+195
View File
@@ -0,0 +1,195 @@
"""详细分析训练/验证/测试集的分配方式"""
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()