"""详细分析训练/验证/测试集的分配方式""" 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()