"""在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 sklearn.metrics import confusion_matrix, classification_report from data.fetcher import BinanceDataFetcher from models.dlinear.model import DLinear class ClassificationPreprocessor: """分类任务预处理器""" 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): 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['future_return'] = (df['future_price'] - df['close']) / df['close'] df['label'] = (df['future_return'] > 0).astype(int) 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]) 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]) X, y = [], [] for i in range(len(scaled_features) - self.lookback): X.append(scaled_features[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年分类模型测试") print("="*80) # ========== 加载模型 ========== print("\n[1/4] 加载模型...") model_path = './models/checkpoints/dlinear_classification_2026/best_model.pt' preprocessor_path = './models/checkpoints/dlinear_classification_2026/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"✓ 任务类型: 分类(预测涨/跌)") # ========== 获取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_test, y_test, processed_df = preprocessor.preprocess(df_2026, fit=False) if len(X_test) == 0: print("❌ 数据不足") return print(f"✓ 测试序列: {len(X_test)}") # 真实标签统计 n_true_up = (y_test == 1).sum() n_true_down = (y_test == 0).sum() print(f"✓ 实际涨跌: 上涨{n_true_up}次 ({n_true_up/len(y_test)*100:.1f}%), " f"下跌{n_true_down}次 ({n_true_down/len(y_test)*100:.1f}%)") # 模型预测 X_tensor = torch.FloatTensor(X_test).to(device) with torch.no_grad(): logits = model(X_tensor).squeeze().cpu().numpy() probs = 1 / (1 + np.exp(-logits)) # sigmoid preds = (probs > 0.5).astype(int) # ========== 评估 ========== print("\n[4/4] 评估结果...") # 基础统计 n_pred_up = (preds == 1).sum() n_pred_down = (preds == 0).sum() accuracy = (preds == y_test).mean() # 混淆矩阵 cm = confusion_matrix(y_test, preds) tn, fp, fn, tp = cm.ravel() print("\n" + "="*80) print("📊 2026年测试结果(分类任务)") print("="*80) print(f"\n预测统计:") print(f" 预测上涨: {n_pred_up} ({n_pred_up/len(preds)*100:.1f}%)") print(f" 预测下跌: {n_pred_down} ({n_pred_down/len(preds)*100:.1f}%)") print(f"\n实际统计:") print(f" 实际上涨: {n_true_up} ({n_true_up/len(y_test)*100:.1f}%)") print(f" 实际下跌: {n_true_down} ({n_true_down/len(y_test)*100:.1f}%)") print(f"\n✨ 准确率: {accuracy*100:.2f}%") print(f"\n详细分析:") print(f" ✓ 预测涨且真涨 (TP): {tp} ({tp/len(preds)*100:.1f}%)") print(f" ✓ 预测跌且真跌 (TN): {tn} ({tn/len(preds)*100:.1f}%)") print(f" ✗ 预测涨但真跌 (FP): {fp} ({fp/len(preds)*100:.1f}%)") print(f" ✗ 预测跌但真涨 (FN): {fn} ({fn/len(preds)*100:.1f}%)") # 精确率、召回率、F1 precision = tp / (tp + fp) if (tp + fp) > 0 else 0 recall = tp / (tp + fn) if (tp + fn) > 0 else 0 f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0 print(f"\n分类指标:") print(f" 精确率 (Precision): {precision*100:.2f}%") print(f" 召回率 (Recall): {recall*100:.2f}%") print(f" F1分数: {f1:.4f}") # 预测偏差分析 bias = n_pred_up - n_true_up print(f"\n预测偏差:") if abs(bias) < len(preds) * 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%,模型有预测能力!") 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} {'预测概率':<10} {'预测':<6} {'实际':<6} {'结果':<4}") print("-"*60) for i in range(min(30, len(preds))): timestamp = processed_df['timestamp'].iloc[i + 168] prob = probs[i] pred = "上涨" if preds[i] == 1 else "下跌" true = "上涨" if y_test[i] == 1 else "下跌" result = "✓" if preds[i] == y_test[i] else "✗" print(f"{str(timestamp):<20} {prob:>8.4f} {pred:<6} {true:<6} {result:<4}") print("\n" + "="*80) print(f"总结: 在{len(preds)}次预测中,准确率 {accuracy*100:.2f}%") print("="*80) # 保存结果 results = { 'accuracy': float(accuracy), 'precision': float(precision), 'recall': float(recall), 'f1_score': float(f1), 'true_positives': int(tp), 'true_negatives': int(tn), 'false_positives': int(fp), 'false_negatives': int(fn), 'n_samples': int(len(preds)) } import json with open('./models/checkpoints/dlinear_classification_2026/test_2026_results.json', 'w') as f: json.dump(results, f, indent=2) print(f"\n✓ 结果已保存至: ./models/checkpoints/dlinear_classification_2026/test_2026_results.json") if __name__ == "__main__": main()