Files
crpto_predictor/analyze_split.py
T
crypto_predictorandClaude Sonnet 4.5 9bbdc8d42b 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>
2026-01-29 21:27:15 +08:00

196 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""详细分析训练/验证/测试集的分配方式"""
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()