Files
crpto_predictor/save_all_results.py
T
crypto_predictor 1bd9d25f60 refactor: 清理测试文件和2019相关代码
- 删除测试脚本: test_2026_classification.py, test_lagllama.py, test_timesfm.py, test_ttm.py
- 删除2019相关文件: analyze_2019_*, apply_2019_*, dlinear_2019_*, three_models_2019_*
- 添加集成模型相关代码和分析脚本
- 添加N-BEATS模型和CatBoost分类模型
2026-02-03 20:40:32 +08:00

459 lines
18 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 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
from data.fetcher import BinanceDataFetcher
import json
from datetime import datetime
# ==================== 预处理器定义 ====================
class ClassificationPreprocessor:
"""DLinear分类预处理器"""
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:
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
class NBeatsPreprocessor:
"""N-BEATS预处理器"""
def __init__(self, lookback=168, forecast_horizon=1):
self.lookback = lookback
self.forecast_horizon = forecast_horizon
self.price_scaler = StandardScaler()
self.is_fitted = False
def preprocess(self, df, fit=True):
df = df.copy()
df['future_price'] = df['close'].shift(-self.forecast_horizon)
df = df[:-self.forecast_horizon].reset_index(drop=True)
prices = df['close'].values.reshape(-1, 1)
future_prices = df['future_price'].values.reshape(-1, 1)
if fit:
scaled_prices = self.price_scaler.fit_transform(prices)
scaled_future = self.price_scaler.transform(future_prices)
self.is_fitted = True
else:
scaled_prices = self.price_scaler.transform(prices)
scaled_future = self.price_scaler.transform(future_prices)
X, y = [], []
for i in range(len(scaled_prices) - self.lookback):
X.append(scaled_prices[i:i + self.lookback, 0])
y.append(scaled_future[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("保存完整测试结果到项目")
print("="*80)
# 创建结果目录
results_dir = "./results"
os.makedirs(results_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# ========== 加载模型 ==========
print("\n[1/5] 加载模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
from models.dlinear.model import DLinear
dlinear_checkpoint = torch.load(
'./models/checkpoints/dlinear_classification_2026/best_model.pt',
map_location=device
)
dlinear_model = DLinear(dlinear_checkpoint['config'])
dlinear_model.load_state_dict(dlinear_checkpoint['model_state_dict'])
dlinear_model = dlinear_model.to(device)
dlinear_model.eval()
with open('./models/checkpoints/dlinear_classification_2026/preprocessor.pkl', 'rb') as f:
dlinear_preprocessor = pickle.load(f)
from models.nbeats.model import NBeats
nbeats_checkpoint = torch.load(
'./models/checkpoints/nbeats_btc_1h/best_model.pt',
map_location=device
)
nbeats_model = NBeats(nbeats_checkpoint['config'])
nbeats_model.load_state_dict(nbeats_checkpoint['model_state_dict'])
nbeats_model = nbeats_model.to(device)
nbeats_model.eval()
with open('./models/checkpoints/nbeats_btc_1h/preprocessor.pkl', 'rb') as f:
nbeats_preprocessor = pickle.load(f)
from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction
ttm_model = TinyTimeMixerForPrediction.from_pretrained(
"ibm-granite/granite-timeseries-ttm-r2",
trust_remote_code=True
)
ttm_model = ttm_model.to(device)
ttm_model.eval()
print("✓ 三个模型已加载")
# ========== 获取数据 ==========
print("\n[2/5] 获取2026年1月测试数据...")
fetcher = BinanceDataFetcher()
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2026-01-01',
end_date='2026-01-30', use_cache=False)
print(f"✓ 数据: {len(df_2026)} 行, {df_2026['timestamp'].min()} 至 {df_2026['timestamp'].max()}")
# ========== 预处理和预测 ==========
print("\n[3/5] 预处理和预测...")
X_dlinear, y_dlinear, df_dlinear = dlinear_preprocessor.preprocess(df_2026, fit=False)
X_nbeats, y_nbeats, df_nbeats = nbeats_preprocessor.preprocess(df_2026, fit=False)
min_samples = min(len(X_dlinear), len(X_nbeats))
X_dlinear = X_dlinear[:min_samples]
y_dlinear = y_dlinear[:min_samples]
X_nbeats = X_nbeats[:min_samples]
# DLinear预测
X_dlinear_tensor = torch.FloatTensor(X_dlinear).to(device)
with torch.no_grad():
dlinear_logits = dlinear_model(X_dlinear_tensor).cpu().numpy()
dlinear_probs = 1 / (1 + np.exp(-dlinear_logits.flatten()))
dlinear_preds = (dlinear_probs > 0.5).astype(int)
# N-BEATS预测
X_nbeats_tensor = torch.FloatTensor(X_nbeats).to(device)
with torch.no_grad():
nbeats_pred_scaled = nbeats_model(X_nbeats_tensor).cpu().numpy()
nbeats_pred_prices = nbeats_preprocessor.inverse_transform_price(nbeats_pred_scaled)
current_open_prices = df_nbeats['open'].iloc[168:168+len(nbeats_pred_prices)].values
nbeats_preds = (nbeats_pred_prices > current_open_prices).astype(int)
# TTM-R2预测
context_length = 512
ttm_preds_list = []
all_prices = df_2026['close'].values
all_open_prices = df_2026['open'].values
for i in range(min_samples):
start_idx = i
end_idx = start_idx + context_length
if end_idx > len(all_prices):
context_data = all_prices[-context_length:].reshape(1, -1, 1)
current_open_price = all_open_prices[min(168 + i, len(all_open_prices) - 1)]
else:
context_data = all_prices[start_idx:end_idx].reshape(1, -1, 1)
current_open_price = all_open_prices[end_idx] if end_idx < len(all_open_prices) else all_open_prices[-1]
context_tensor = torch.FloatTensor(context_data).to(device)
with torch.no_grad():
output = ttm_model(context_tensor)
pred_price = output.prediction_outputs.cpu().numpy()[0, 0, 0]
ttm_preds_list.append(1 if pred_price > current_open_price else 0)
ttm_preds = np.array(ttm_preds_list)
# 双模型交集
two_model_agree_up = (dlinear_preds == 1) & (nbeats_preds == 1)
two_model_agree_down = (dlinear_preds == 0) & (nbeats_preds == 0)
two_model_preds = np.full(len(dlinear_preds), -1)
two_model_preds[two_model_agree_up] = 1
two_model_preds[two_model_agree_down] = 0
# 三模型投票
vote_sum = dlinear_preds + nbeats_preds + ttm_preds
three_model_preds = np.where(vote_sum >= 2, 1, 0)
true_labels = y_dlinear
print(f"✓ 预测完成,样本数: {min_samples}")
# ========== 生成详细结果 ==========
print("\n[4/5] 生成详细结果...")
# 时间戳和日期
timestamps = df_2026['timestamp'].iloc[168:168+min_samples].values
timestamps_utc = pd.to_datetime(timestamps)
timestamps_et = timestamps_utc.tz_localize('UTC').tz_convert('America/New_York')
dates = timestamps_et.date
# 创建详细结果DataFrame
results_df = pd.DataFrame({
'timestamp_utc': timestamps_utc,
'timestamp_et': timestamps_et,
'date': dates,
'true_label': true_labels,
'dlinear_prob': dlinear_probs,
'dlinear_pred': dlinear_preds,
'nbeats_pred': nbeats_preds,
'ttm_pred': ttm_preds,
'two_model_pred': two_model_preds,
'three_model_pred': three_model_preds,
'dlinear_correct': dlinear_preds == true_labels,
'two_model_correct': (two_model_preds == true_labels) & (two_model_preds != -1),
'three_model_correct': three_model_preds == true_labels
})
# 按天统计
daily_stats = results_df.groupby('date').agg({
'true_label': 'count',
'two_model_pred': lambda x: (x != -1).sum(),
'two_model_correct': 'sum',
'three_model_correct': 'sum',
'dlinear_correct': 'sum'
})
daily_stats.columns = ['total_samples', 'two_model_valid', 'two_model_correct',
'three_model_correct', 'dlinear_correct']
daily_stats['dlinear_accuracy'] = daily_stats['dlinear_correct'] / daily_stats['total_samples'] * 100
daily_stats['two_model_accuracy'] = daily_stats['two_model_correct'] / daily_stats['two_model_valid'] * 100
daily_stats['three_model_accuracy'] = daily_stats['three_model_correct'] / daily_stats['total_samples'] * 100
daily_stats['two_model_coverage'] = daily_stats['two_model_valid'] / daily_stats['total_samples'] * 100
daily_stats['two_model_accuracy'] = daily_stats['two_model_accuracy'].fillna(0)
# ========== 保存结果 ==========
print("\n[5/5] 保存结果文件...")
# 1. 保存逐小时详细结果
hourly_file = f"{results_dir}/hourly_predictions_2026_01.csv"
results_df.to_csv(hourly_file, index=False)
print(f"✓ 保存逐小时预测: {hourly_file}")
# 2. 保存每日统计
daily_file = f"{results_dir}/daily_statistics_2026_01.csv"
daily_stats.to_csv(daily_file)
print(f"✓ 保存每日统计: {daily_file}")
# 3. 生成总结报告
summary = {
"测试期间": {
"开始日期": str(df_2026['timestamp'].min()),
"结束日期": str(df_2026['timestamp'].max()),
"总天数": len(daily_stats),
"总样本数": int(min_samples)
},
"单模型性能": {
"DLinear": {
"准确率": f"{(dlinear_preds == true_labels).mean() * 100:.2f}%",
"预测上涨比例": f"{(dlinear_preds == 1).mean() * 100:.1f}%"
},
"N-BEATS": {
"准确率": f"{(nbeats_preds == true_labels).mean() * 100:.2f}%",
"预测上涨比例": f"{(nbeats_preds == 1).mean() * 100:.1f}%"
},
"TTM-R2": {
"准确率": f"{(ttm_preds == true_labels).mean() * 100:.2f}%",
"预测上涨比例": f"{(ttm_preds == 1).mean() * 100:.1f}%"
}
},
"集成策略性能": {
"双模型交集": {
"准确率": f"{((two_model_preds == true_labels) & (two_model_preds != -1)).sum() / (two_model_preds != -1).sum() * 100:.2f}%",
"覆盖率": f"{(two_model_preds != -1).mean() * 100:.2f}%",
"有效预测数": int((two_model_preds != -1).sum())
},
"三模型投票": {
"准确率": f"{(three_model_preds == true_labels).mean() * 100:.2f}%",
"覆盖率": "100.00%",
"有效预测数": int(len(three_model_preds))
}
},
"DLinear概率分布": {
"0.4-0.5区间": f"{((dlinear_probs >= 0.4) & (dlinear_probs < 0.5)).sum()}个 ({((dlinear_probs >= 0.4) & (dlinear_probs < 0.5)).mean() * 100:.1f}%)",
"0.5-0.6区间": f"{((dlinear_probs >= 0.5) & (dlinear_probs < 0.6)).sum()}个 ({((dlinear_probs >= 0.5) & (dlinear_probs < 0.6)).mean() * 100:.1f}%)",
"0.6-0.7区间": f"{((dlinear_probs >= 0.6) & (dlinear_probs < 0.7)).sum()}个 ({((dlinear_probs >= 0.6) & (dlinear_probs < 0.7)).mean() * 100:.1f}%)",
"平均概率": f"{dlinear_probs.mean():.3f}"
},
"最佳/最差交易日": {
"双模型最佳日": {
"日期": str(daily_stats['two_model_accuracy'].idxmax()),
"准确率": f"{daily_stats['two_model_accuracy'].max():.1f}%"
},
"双模型最差日": {
"日期": str(daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].idxmin()),
"准确率": f"{daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].min():.1f}%"
},
"三模型最佳日": {
"日期": str(daily_stats['three_model_accuracy'].idxmax()),
"准确率": f"{daily_stats['three_model_accuracy'].max():.1f}%"
},
"三模型最差日": {
"日期": str(daily_stats['three_model_accuracy'].idxmin()),
"准确率": f"{daily_stats['three_model_accuracy'].min():.1f}%"
}
}
}
summary_file = f"{results_dir}/summary_report_2026_01.json"
with open(summary_file, 'w', encoding='utf-8') as f:
json.dump(summary, f, indent=2, ensure_ascii=False)
print(f"✓ 保存总结报告: {summary_file}")
# 4. 生成Markdown报告
markdown_report = f"""# 加密货币价格预测模型测试报告
## 测试概况
- **测试期间**: 2026年1月1日 - 2026年1月30日
- **总天数**: {len(daily_stats)}天
- **总样本数**: {min_samples}个小时
- **生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
---
## 单模型性能
### DLinear分类模型
- **准确率**: {(dlinear_preds == true_labels).mean() * 100:.2f}%
- **预测上涨比例**: {(dlinear_preds == 1).mean() * 100:.1f}%
- **预测下跌比例**: {(dlinear_preds == 0).mean() * 100:.1f}%
### N-BEATS回归模型
- **准确率**: {(nbeats_preds == true_labels).mean() * 100:.2f}%
- **预测上涨比例**: {(nbeats_preds == 1).mean() * 100:.1f}%
- **预测下跌比例**: {(nbeats_preds == 0).mean() * 100:.1f}%
### TTM-R2零样本模型
- **准确率**: {(ttm_preds == true_labels).mean() * 100:.2f}%
- **预测上涨比例**: {(ttm_preds == 1).mean() * 100:.1f}%
- **预测下跌比例**: {(ttm_preds == 0).mean() * 100:.1f}%
---
## 集成策略性能
### 双模型交集(DLinear + N-BEATS)
- **准确率**: {((two_model_preds == true_labels) & (two_model_preds != -1)).sum() / (two_model_preds != -1).sum() * 100:.2f}%
- **覆盖率**: {(two_model_preds != -1).mean() * 100:.2f}%
- **有效预测数**: {(two_model_preds != -1).sum()}次
### 三模型多数投票(DLinear + N-BEATS + TTM-R2)
- **准确率**: {(three_model_preds == true_labels).mean() * 100:.2f}%
- **覆盖率**: 100.00%
- **有效预测数**: {len(three_model_preds)}次
---
## DLinear概率分布分析
| 概率区间 | 样本数 | 占比 |
|---------|--------|------|
| 0.4-0.5 | {((dlinear_probs >= 0.4) & (dlinear_probs < 0.5)).sum()} | {((dlinear_probs >= 0.4) & (dlinear_probs < 0.5)).mean() * 100:.1f}% |
| 0.5-0.6 | {((dlinear_probs >= 0.5) & (dlinear_probs < 0.6)).sum()} | {((dlinear_probs >= 0.5) & (dlinear_probs < 0.6)).mean() * 100:.1f}% |
| 0.6-0.7 | {((dlinear_probs >= 0.6) & (dlinear_probs < 0.7)).sum()} | {((dlinear_probs >= 0.6) & (dlinear_probs < 0.7)).mean() * 100:.1f}% |
**平均概率**: {dlinear_probs.mean():.3f}
---
## 最佳/最差交易日
### 双模型交集
- **最佳日**: {daily_stats['two_model_accuracy'].idxmax()} ({daily_stats['two_model_accuracy'].max():.1f}%)
- **最差日**: {daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].idxmin()} ({daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].min():.1f}%)
### 三模型投票
- **最佳日**: {daily_stats['three_model_accuracy'].idxmax()} ({daily_stats['three_model_accuracy'].max():.1f}%)
- **最差日**: {daily_stats['three_model_accuracy'].idxmin()} ({daily_stats['three_model_accuracy'].min():.1f}%)
---
## 结论
1. **最佳单模型**: DLinear ({(dlinear_preds == true_labels).mean() * 100:.2f}%)
2. **最佳集成策略**: 双模型交集 ({((two_model_preds == true_labels) & (two_model_preds != -1)).sum() / (two_model_preds != -1).sum() * 100:.2f}%)
3. **DLinear置信度**: 99.2%的预测集中在0.45-0.55区间,缺乏高置信度预测
## 数据文件
- 逐小时预测结果: `hourly_predictions_2026_01.csv`
- 每日统计数据: `daily_statistics_2026_01.csv`
- 总结报告: `summary_report_2026_01.json`
---
生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
"""
markdown_file = f"{results_dir}/README_2026_01.md"
with open(markdown_file, 'w', encoding='utf-8') as f:
f.write(markdown_report)
print(f"✓ 保存Markdown报告: {markdown_file}")
print("\n" + "="*80)
print("✓ 所有结果已保存到 ./results/ 目录")
print("="*80)
print(f"\n保存的文件:")
print(f" 1. {hourly_file}")
print(f" 2. {daily_file}")
print(f" 3. {summary_file}")
print(f" 4. {markdown_file}")
print("\n" + "="*80)
if __name__ == "__main__":
main()