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分类模型
This commit is contained in:
crypto_predictor committed 2026-02-03 20:40:32 +08:00
1 parent c74f4f6949
commit 1bd9d25f60
33 files changed
+6557 -233

No files matched your search

+7 -1
View File
@@ -6,7 +6,13 @@
"Bash(python:*)", "Bash(python:*)",
"Bash(source:*)", "Bash(source:*)",
"Bash(ls:*)", "Bash(ls:*)",
"Bash(git add:*)" "Bash(git add:*)",
"Bash(git commit:*)",
"Bash(tree:*)",
"Bash(git push:*)",
"Bash(conda activate:*)",
"WebSearch",
"Bash(pip install:*)"
] ]
} }
} }
+459
View File
@@ -0,0 +1,459 @@
"""分析2019年和2026年1月准确率<40%交易日的前一天技术特征"""
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 data.fetcher import BinanceDataFetcher
from datetime import datetime, timedelta
# ==================== 预处理器定义 ====================
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
def calculate_technical_features(df):
"""计算丰富的技术特征"""
df = df.copy()
# 基础价格特征
df['price_change'] = df['close'] - df['open']
df['price_change_pct'] = (df['close'] - df['open']) / df['open'] * 100
df['high_low_range'] = df['high'] - df['low']
df['high_low_range_pct'] = (df['high'] - df['low']) / df['low'] * 100
df['body_ratio'] = abs(df['close'] - df['open']) / (df['high'] - df['low'] + 1e-10)
# 移动平均线
df['sma_5'] = ta.sma(df['close'], length=5)
df['sma_10'] = ta.sma(df['close'], length=10)
df['sma_20'] = ta.sma(df['close'], length=20)
df['sma_50'] = ta.sma(df['close'], length=50)
# 价格相对MA位置
df['price_vs_sma5'] = (df['close'] - df['sma_5']) / df['sma_5'] * 100
df['price_vs_sma20'] = (df['close'] - df['sma_20']) / df['sma_20'] * 100
# RSI
df['rsi_14'] = ta.rsi(df['close'], length=14)
df['rsi_7'] = ta.rsi(df['close'], length=7)
# MACD
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]
# 布林带
bbands = ta.bbands(df['close'], length=20)
if bbands is not None and not bbands.empty:
df['bb_upper'] = bbands.iloc[:, 0]
df['bb_mid'] = bbands.iloc[:, 1]
df['bb_lower'] = bbands.iloc[:, 2]
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_mid'] * 100
df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'] + 1e-10) * 100
# ATR波动率
df['atr_14'] = ta.atr(df['high'], df['low'], df['close'], length=14)
df['atr_pct'] = df['atr_14'] / df['close'] * 100
# 成交量特征
df['volume_sma_20'] = ta.sma(df['volume'], length=20)
df['volume_ratio'] = df['volume'] / (df['volume_sma_20'] + 1e-10)
# 价格动量
df['momentum_1'] = df['close'].pct_change(1) * 100
df['momentum_3'] = df['close'].pct_change(3) * 100
df['momentum_7'] = df['close'].pct_change(7) * 100
df['momentum_24'] = df['close'].pct_change(24) * 100
# 波动率
df['volatility_5'] = df['close'].pct_change().rolling(5).std() * 100
df['volatility_10'] = df['close'].pct_change().rolling(10).std() * 100
df['volatility_20'] = df['close'].pct_change().rolling(20).std() * 100
# 连续涨跌天数
df['is_up'] = (df['close'] > df['open']).astype(int)
df['consecutive_days'] = 0
return df
def analyze_period(model, preprocessor, device, period_name, start_date, end_date):
"""分析单个时期的数据,返回低准确率日期"""
print(f"\n{'='*80}")
print(f"分析 {period_name}")
print(f"{'='*80}")
# 获取数据(需要额外获取前一天的数据)
fetcher = BinanceDataFetcher()
# 获取包含前一天的数据
start_dt = pd.to_datetime(start_date) - pd.Timedelta(days=2)
df = fetcher.fetch_ohlcv('BTC', '1h', start_date=start_dt.strftime('%Y-%m-%d'),
end_date=end_date, use_cache=False)
print(f"✓ 数据: {len(df)} 行")
# 预处理
X, y, df_processed = preprocessor.preprocess(df, fit=False)
print(f"✓ 有效样本: {len(X)}")
# DLinear预测
X_tensor = torch.FloatTensor(X).to(device)
with torch.no_grad():
logits = model(X_tensor).cpu().numpy()
probs = 1 / (1 + np.exp(-logits))
preds = (probs > 0.5).astype(int).flatten()
true_labels = y
# 构建每日结果
timestamps = df['timestamp'].iloc[168:168+len(X)].values
timestamps_utc = pd.to_datetime(timestamps)
timestamps_et = timestamps_utc.tz_localize('UTC').tz_convert('America/New_York')
dates = timestamps_et.date
results_df = pd.DataFrame({
'date': dates,
'timestamp_et': timestamps_et,
'correct': preds == true_labels,
})
# 按天统计准确率
daily_stats = results_df.groupby('date').agg({
'correct': ['count', 'sum']
})
daily_stats.columns = ['total', 'correct']
daily_stats['accuracy'] = daily_stats['correct'] / daily_stats['total'] * 100
# 找出准确率<40%的日期
low_acc_dates = daily_stats[daily_stats['accuracy'] < 40].index.tolist()
print(f"✓ 准确率<40%的日期: {len(low_acc_dates)}天")
for date in low_acc_dates:
acc = daily_stats.loc[date, 'accuracy']
print(f" {date}: {acc:.1f}%")
# 计算技术特征
df_with_features = calculate_technical_features(df)
# 为每天聚合技术特征(使用当天的24小时数据)
# 先确保timestamp是datetime类型
if not pd.api.types.is_datetime64_any_dtype(df_with_features['timestamp']):
df_with_features['timestamp'] = pd.to_datetime(df_with_features['timestamp'])
df_with_features['date'] = df_with_features['timestamp'].dt.tz_localize('UTC').tz_convert('America/New_York').dt.date
# 返回低准确率日期和完整数据
return low_acc_dates, df_with_features, daily_stats
def main():
print("="*80)
print("分析准确率<40%交易日的前一天技术特征共同点")
print("="*80)
# ========== 加载模型 ==========
print("\n[1/3] 加载DLinear模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
from models.dlinear.model import DLinear
checkpoint = torch.load(
'./models/checkpoints/dlinear_classification_2026/best_model.pt',
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('./models/checkpoints/dlinear_classification_2026/preprocessor.pkl', 'rb') as f:
preprocessor = pickle.load(f)
print("✓ DLinear模型已加载")
# ========== 分析两个时期 ==========
print("\n[2/3] 分析2019年和2026年1月...")
# 分析2019年1月
low_acc_2019, df_2019, stats_2019 = analyze_period(
model, preprocessor, device,
"2019年1月", "2019-01-01", "2019-01-31"
)
# 分析2026年1月
low_acc_2026, df_2026, stats_2026 = analyze_period(
model, preprocessor, device,
"2026年1月", "2026-01-01", "2026-01-31"
)
# ========== 提取前一天特征 ==========
print("\n[3/3] 分析前一天技术特征...")
# 合并所有低准确率日期
all_low_acc_dates = low_acc_2019 + low_acc_2026
print(f"\n总计{len(all_low_acc_dates)}个低准确率交易日")
# 提取前一天的特征
prev_day_features = []
for date in all_low_acc_dates:
# 获取前一天日期
prev_date = date - timedelta(days=1)
# 确定使用哪个数据集
if date in low_acc_2019:
df = df_2019
period = "2019年"
else:
df = df_2026
period = "2026年"
# 获取前一天的数据
prev_day_data = df[df['date'] == prev_date]
if len(prev_day_data) == 0:
continue
# 聚合前一天的技术特征(使用24小时的平均值、最大值、最小值等)
features = {
'date': date,
'prev_date': prev_date,
'period': period,
'accuracy': stats_2019.loc[date, 'accuracy'] if date in low_acc_2019 else stats_2026.loc[date, 'accuracy'],
# 价格特征
'prev_close': prev_day_data['close'].iloc[-1],
'prev_open': prev_day_data['open'].iloc[0],
'prev_price_change_pct': prev_day_data['price_change_pct'].mean(),
'prev_high_low_range_pct': prev_day_data['high_low_range_pct'].mean(),
'prev_body_ratio': prev_day_data['body_ratio'].mean(),
# RSI
'prev_rsi_14': prev_day_data['rsi_14'].iloc[-1],
'prev_rsi_7': prev_day_data['rsi_7'].iloc[-1],
# MACD
'prev_macd': prev_day_data['macd'].iloc[-1] if 'macd' in prev_day_data.columns else None,
'prev_macd_hist': prev_day_data['macd_hist'].iloc[-1] if 'macd_hist' in prev_day_data.columns else None,
# 布林带
'prev_bb_width': prev_day_data['bb_width'].iloc[-1] if 'bb_width' in prev_day_data.columns else None,
'prev_bb_position': prev_day_data['bb_position'].iloc[-1] if 'bb_position' in prev_day_data.columns else None,
# 波动率
'prev_atr_pct': prev_day_data['atr_pct'].iloc[-1] if 'atr_pct' in prev_day_data.columns else None,
'prev_volatility_5': prev_day_data['volatility_5'].iloc[-1] if 'volatility_5' in prev_day_data.columns else None,
'prev_volatility_20': prev_day_data['volatility_20'].iloc[-1] if 'volatility_20' in prev_day_data.columns else None,
# 成交量
'prev_volume_ratio': prev_day_data['volume_ratio'].iloc[-1] if 'volume_ratio' in prev_day_data.columns else None,
# 动量
'prev_momentum_1': prev_day_data['momentum_1'].iloc[-1] if 'momentum_1' in prev_day_data.columns else None,
'prev_momentum_3': prev_day_data['momentum_3'].iloc[-1] if 'momentum_3' in prev_day_data.columns else None,
'prev_momentum_7': prev_day_data['momentum_7'].iloc[-1] if 'momentum_7' in prev_day_data.columns else None,
'prev_momentum_24': prev_day_data['momentum_24'].iloc[-1] if 'momentum_24' in prev_day_data.columns else None,
# 价格相对MA位置
'prev_price_vs_sma5': prev_day_data['price_vs_sma5'].iloc[-1] if 'price_vs_sma5' in prev_day_data.columns else None,
'prev_price_vs_sma20': prev_day_data['price_vs_sma20'].iloc[-1] if 'price_vs_sma20' in prev_day_data.columns else None,
}
prev_day_features.append(features)
# 转换为DataFrame
features_df = pd.DataFrame(prev_day_features)
# ========== 分析共同点 ==========
print("\n" + "="*80)
print("📊 前一天技术特征统计分析")
print("="*80)
print(f"\n分析样本数: {len(features_df)}个低准确率交易日")
print(f" 2019年: {(features_df['period']=='2019年').sum()}天")
print(f" 2026年: {(features_df['period']=='2026年').sum()}天")
# 统计各特征的平均值、中位数、标准差
numeric_cols = [col for col in features_df.columns if col.startswith('prev_') and features_df[col].dtype in [np.float64, np.int64]]
print(f"\n前一天技术特征统计:")
print(f"{'特征':<30} {'平均值':>10} {'中位数':>10} {'标准差':>10} {'共同点':<30}")
print("-" * 95)
for col in numeric_cols:
if features_df[col].notna().sum() > 0:
mean_val = features_df[col].mean()
median_val = features_df[col].median()
std_val = features_df[col].std()
# 判断共同点
common_trait = ""
if 'rsi' in col:
if mean_val > 70:
common_trait = "超买区域"
elif mean_val < 30:
common_trait = "超卖区域"
elif 45 <= mean_val <= 55:
common_trait = "中性区域"
elif 'volatility' in col:
if mean_val > 3:
common_trait = "高波动"
elif mean_val < 1:
common_trait = "低波动"
elif 'momentum' in col:
if mean_val > 2:
common_trait = "强势上涨"
elif mean_val < -2:
common_trait = "强势下跌"
elif abs(mean_val) < 0.5:
common_trait = "横盘震荡"
elif 'bb_position' in col:
if mean_val > 80:
common_trait = "接近布林上轨"
elif mean_val < 20:
common_trait = "接近布林下轨"
elif 'volume_ratio' in col:
if mean_val > 1.5:
common_trait = "成交量放大"
elif mean_val < 0.8:
common_trait = "成交量萎缩"
print(f"{col:<30} {mean_val:>10.2f} {median_val:>10.2f} {std_val:>10.2f} {common_trait:<30}")
# ========== 对比分析 ==========
print("\n" + "="*80)
print("🔍 2019年 vs 2026年对比分析")
print("="*80)
features_2019 = features_df[features_df['period'] == '2019年']
features_2026 = features_df[features_df['period'] == '2026年']
if len(features_2019) > 0 and len(features_2026) > 0:
print(f"\n{'特征':<30} {'2019年平均':>12} {'2026年平均':>12} {'差异':>10}")
print("-" * 70)
for col in numeric_cols[:15]: # 显示前15个特征
if features_2019[col].notna().sum() > 0 and features_2026[col].notna().sum() > 0:
mean_2019 = features_2019[col].mean()
mean_2026 = features_2026[col].mean()
diff = mean_2026 - mean_2019
print(f"{col:<30} {mean_2019:>12.2f} {mean_2026:>12.2f} {diff:>10.2f}")
# ========== 关键共同点总结 ==========
print("\n" + "="*80)
print("💡 关键共同点总结")
print("="*80)
# RSI分析
rsi_mean = features_df['prev_rsi_14'].mean()
print(f"\n1. RSI指标:")
print(f" 平均RSI: {rsi_mean:.1f}")
if rsi_mean > 70:
print(f" 🔴 共同点: 前一天处于超买状态")
elif rsi_mean < 30:
print(f" 🟢 共同点: 前一天处于超卖状态")
else:
print(f" ➖ 共同点: 前一天RSI在中性区域")
# 波动率分析
vol_mean = features_df['prev_volatility_20'].mean()
print(f"\n2. 波动率:")
print(f" 平均波动率: {vol_mean:.2f}%")
if vol_mean > 3:
print(f" ⚠️ 共同点: 前一天波动率较高")
elif vol_mean < 1:
print(f" ✅ 共同点: 前一天波动率较低")
# 动量分析
mom_24_mean = features_df['prev_momentum_24'].mean()
print(f"\n3. 24小时动量:")
print(f" 平均动量: {mom_24_mean:.2f}%")
if abs(mom_24_mean) > 5:
print(f" 🚀 共同点: 前一天有强趋势({'+' if mom_24_mean > 0 else ''}{mom_24_mean:.1f}%)")
else:
print(f" 🔄 共同点: 前一天横盘震荡")
# 布林带位置
bb_pos_mean = features_df['prev_bb_position'].mean()
print(f"\n4. 布林带位置:")
print(f" 平均位置: {bb_pos_mean:.1f}%")
if bb_pos_mean > 80:
print(f" 🔴 共同点: 前一天价格接近布林上轨")
elif bb_pos_mean < 20:
print(f" 🟢 共同点: 前一天价格接近布林下轨")
else:
print(f" ➖ 共同点: 前一天价格在布林带中部")
# 成交量
vol_ratio_mean = features_df['prev_volume_ratio'].mean()
print(f"\n5. 成交量:")
print(f" 平均成交量比: {vol_ratio_mean:.2f}x")
if vol_ratio_mean > 1.5:
print(f" 📈 共同点: 前一天成交量显著放大")
elif vol_ratio_mean < 0.8:
print(f" 📉 共同点: 前一天成交量萎缩")
print("\n" + "="*80)
print("✓ 技术特征分析完成!")
print("="*80)
# 保存详细数据
output_file = "./results/low_accuracy_prev_day_features.csv"
os.makedirs("./results", exist_ok=True)
features_df.to_csv(output_file, index=False)
print(f"\n✓ 详细数据已保存至: {output_file}")
if __name__ == "__main__":
main()
+144
View File
@@ -0,0 +1,144 @@
{
"meta":{"test_sets":["test"],"test_metrics":[{"best_value":"Max","name":"Accuracy"},{"best_value":"Min","name":"Logloss"}],"learn_metrics":[{"best_value":"Max","name":"Accuracy"},{"best_value":"Min","name":"Logloss"}],"launch_mode":"Train","parameters":"","iteration_count":1000,"learn_sets":["learn"],"name":"experiment"},
"iterations":[
{"learn":[0.5470774019,0.6925856518],"iteration":0,"passed_time":0.06120060416,"remaining_time":61.13940356,"test":[0.5215327622,0.6929069842]},
{"learn":[0.5485192475,0.6920296194],"iteration":1,"passed_time":0.06652548291,"remaining_time":33.19621597,"test":[0.5201435517,0.6926980686]},
{"learn":[0.5508078912,0.6915437145],"iteration":2,"passed_time":0.07133867461,"remaining_time":23.70821953,"test":[0.5235008104,0.6925995058]},
{"learn":[0.5511969607,0.6911042525],"iteration":3,"passed_time":0.07626899597,"remaining_time":18.99098,"test":[0.5243111831,0.6923741119]},
{"learn":[0.5528218977,0.6906429178],"iteration":4,"passed_time":0.08172279652,"remaining_time":16.26283651,"test":[0.5240796481,0.6922359346]},
{"learn":[0.553096535,0.6902328044],"iteration":5,"passed_time":0.08686295957,"remaining_time":14.39029697,"test":[0.5217642973,0.6921532273]},
{"learn":[0.5537144688,0.6899070645],"iteration":6,"passed_time":0.0917179446,"remaining_time":13.01084557,"test":[0.5241954156,0.6920533177]},
{"learn":[0.552730352,0.6895966245],"iteration":7,"passed_time":0.09718091218,"remaining_time":12.05043311,"test":[0.520838157,0.6919667553]},
{"learn":[0.5539204467,0.6892625204],"iteration":8,"passed_time":0.1018938082,"remaining_time":11.21964044,"test":[0.520838157,0.6919667553]},
{"learn":[0.554561267,0.6889386437],"iteration":9,"passed_time":0.1079246734,"remaining_time":10.68454267,"test":[0.520838157,0.6918984047]},
{"learn":[0.554561267,0.6886345552],"iteration":10,"passed_time":0.1134390597,"remaining_time":10.19920273,"test":[0.5213012271,0.6918473786]},
{"learn":[0.5548587907,0.6883728473],"iteration":11,"passed_time":0.1182243753,"remaining_time":9.7338069,"test":[0.5217642973,0.691760433]},
{"learn":[0.5552020872,0.6881185803],"iteration":12,"passed_time":0.1234375829,"remaining_time":9.371761104,"test":[0.5231535078,0.6917269838]},
{"learn":[0.5564150684,0.6879043975],"iteration":13,"passed_time":0.1290524316,"remaining_time":9.088978394,"test":[0.5210696921,0.6916992448]},
{"learn":[0.5559802261,0.6877314929],"iteration":14,"passed_time":0.1346781556,"remaining_time":8.843865552,"test":[0.522343135,0.6916911046]},
{"learn":[0.5564150684,0.6875118582],"iteration":15,"passed_time":0.1392779638,"remaining_time":8.565594775,"test":[0.5213012271,0.6916992139]},
{"learn":[0.5569414565,0.6872755986],"iteration":16,"passed_time":0.1448462273,"remaining_time":8.375520082,"test":[0.5204908544,0.6917199845]},
{"learn":[0.5561862041,0.6870435386],"iteration":17,"passed_time":0.1499537223,"remaining_time":8.18080863,"test":[0.5226904376,0.6917129587]},
{"learn":[0.5561633176,0.6868442431],"iteration":18,"passed_time":0.1555536537,"remaining_time":8.031480752,"test":[0.5225746701,0.6916992772]},
{"learn":[0.5575593903,0.6866694237],"iteration":19,"passed_time":0.1608244053,"remaining_time":7.88039586,"test":[0.5207223894,0.6917050845]},
{"learn":[0.5575593903,0.686503203],"iteration":20,"passed_time":0.1667731839,"remaining_time":7.774807002,"test":[0.5247742533,0.6916027729]},
{"learn":[0.557124548,0.6863452751],"iteration":21,"passed_time":0.171689088,"remaining_time":7.632360368,"test":[0.5239638805,0.6916046624]},
{"learn":[0.5569185701,0.6862172686],"iteration":22,"passed_time":0.1765527817,"remaining_time":7.49965512,"test":[0.5241954156,0.6916203654]},
{"learn":[0.5573762988,0.6860730724],"iteration":23,"passed_time":0.1818841191,"remaining_time":7.396620842,"test":[0.5243111831,0.6916491893]},
{"learn":[0.5571703209,0.6859302623],"iteration":24,"passed_time":0.186612224,"remaining_time":7.277876738,"test":[0.5244269507,0.6916666458]},
{"learn":[0.5570101158,0.6858087212],"iteration":25,"passed_time":0.1914440415,"remaining_time":7.171788323,"test":[0.5235008104,0.691666967]},
{"learn":[0.5571474344,0.6857036471],"iteration":26,"passed_time":0.1967539613,"remaining_time":7.090429791,"test":[0.5229219727,0.6916827648]},
{"learn":[0.5571932073,0.6855803136],"iteration":27,"passed_time":0.2023491842,"remaining_time":7.024407393,"test":[0.5245427182,0.6917018334]},
{"learn":[0.5572160937,0.6854590307],"iteration":28,"passed_time":0.2073690924,"remaining_time":6.943289267,"test":[0.5222273674,0.6916998315]},
{"learn":[0.5568041379,0.6853245403],"iteration":29,"passed_time":0.2177755069,"remaining_time":7.041408056,"test":[0.5224589025,0.6917500551]},
{"learn":[0.5570558887,0.6852010211],"iteration":30,"passed_time":0.2259016222,"remaining_time":7.061247481,"test":[0.5222273674,0.691867972]},
{"learn":[0.5570330022,0.6850648161],"iteration":31,"passed_time":0.2344519627,"remaining_time":7.092171873,"test":[0.5215327622,0.6918745283]},
{"learn":[0.5578340276,0.6849486439],"iteration":32,"passed_time":0.2411928145,"remaining_time":7.067680354,"test":[0.5214169947,0.691873578]},
{"learn":[0.5576738225,0.6848167763],"iteration":33,"passed_time":0.2475354838,"remaining_time":7.032919923,"test":[0.5215327622,0.6918621914]},
{"learn":[0.5577424818,0.6847064338],"iteration":34,"passed_time":0.255081701,"remaining_time":7.0329669,"test":[0.5222273674,0.691870106]},
{"learn":[0.5582917563,0.6846073419],"iteration":35,"passed_time":0.2616022524,"remaining_time":7.005126981,"test":[0.5219958324,0.6919348629]},
{"learn":[0.5587037122,0.6845016816],"iteration":36,"passed_time":0.2691936381,"remaining_time":7.006310094,"test":[0.5245427182,0.6920133967]},
{"learn":[0.5587494851,0.6844106082],"iteration":37,"passed_time":0.2758961134,"remaining_time":6.984527923,"test":[0.5246584858,0.6920145372]},
{"learn":[0.5591156681,0.6843432648],"iteration":38,"passed_time":0.2833089086,"remaining_time":6.981022081,"test":[0.5247742533,0.6920231409]},
{"learn":[0.5589325766,0.6842678359],"iteration":39,"passed_time":0.2897567071,"remaining_time":6.95416097,"test":[0.5248900208,0.6920429941]},
{"learn":[0.5590012359,0.6841515192],"iteration":40,"passed_time":0.3004245903,"remaining_time":7.027004441,"test":[0.5248900208,0.6920318228]},
{"learn":[0.5590698952,0.6840610381],"iteration":41,"passed_time":0.3083540311,"remaining_time":7.033408614,"test":[0.5252373235,0.6920202842]},
{"learn":[0.5593445324,0.6839565281],"iteration":42,"passed_time":0.3168845792,"remaining_time":7.052524239,"test":[0.525353091,0.6920430337]},
{"learn":[0.5592529867,0.6838539206],"iteration":43,"passed_time":0.3241559521,"remaining_time":7.043024778,"test":[0.5247742533,0.6920265347]},
{"learn":[0.559321646,0.6837417759],"iteration":44,"passed_time":0.3312213169,"remaining_time":7.029252391,"test":[0.525353091,0.6920697067]},
{"learn":[0.559687829,0.6836669298],"iteration":45,"passed_time":0.3364545253,"remaining_time":6.977774285,"test":[0.5263949988,0.692116771]},
{"learn":[0.5603744221,0.6835634424],"iteration":46,"passed_time":0.3416096056,"remaining_time":6.926679875,"test":[0.5259319287,0.692098178]},
{"learn":[0.5599853527,0.6834698484],"iteration":47,"passed_time":0.3493788734,"remaining_time":6.929347655,"test":[0.5260476962,0.692152499]},
{"learn":[0.5601913306,0.6833860083],"iteration":48,"passed_time":0.3567442501,"remaining_time":6.923750649,"test":[0.5262792313,0.6922137468]},
{"learn":[0.5602599899,0.6833086288],"iteration":49,"passed_time":0.3643377608,"remaining_time":6.922417456,"test":[0.5270896041,0.6922167687]},
{"learn":[0.5606490594,0.683239475],"iteration":50,"passed_time":0.3696800569,"remaining_time":6.87894851,"test":[0.5272053716,0.6922875182]},
{"learn":[0.5609694695,0.6831625258],"iteration":51,"passed_time":0.3754608288,"remaining_time":6.844939726,"test":[0.526858069,0.6923662256]},
{"learn":[0.5611983339,0.683056699],"iteration":52,"passed_time":0.3809127543,"remaining_time":6.806120346,"test":[0.526858069,0.6923707756]},
{"learn":[0.5617018355,0.6829611591],"iteration":53,"passed_time":0.3866230651,"remaining_time":6.773063325,"test":[0.5270896041,0.6923776257]},
{"learn":[0.5619993592,0.6828690012],"iteration":54,"passed_time":0.3917164763,"remaining_time":6.730401274,"test":[0.5259319287,0.692400255]},
{"learn":[0.5620451321,0.6827776665],"iteration":55,"passed_time":0.3965791283,"remaining_time":6.685191019,"test":[0.5266265339,0.692269877]},
{"learn":[0.5624342015,0.6826927644],"iteration":56,"passed_time":0.4017419589,"remaining_time":6.646362583,"test":[0.5260476962,0.6922932744]},
{"learn":[0.5626401794,0.6825957686],"iteration":57,"passed_time":0.4064048529,"remaining_time":6.60057537,"test":[0.5266265339,0.6923264029]},
{"learn":[0.5626630659,0.6825449467],"iteration":58,"passed_time":0.4116929385,"remaining_time":6.566153478,"test":[0.5263949988,0.692314059]},
{"learn":[0.5627088387,0.6824559998],"iteration":59,"passed_time":0.4168931457,"remaining_time":6.531325949,"test":[0.5263949988,0.6923015601]},
{"learn":[0.562983476,0.6823684725],"iteration":60,"passed_time":0.4216150837,"remaining_time":6.4901076,"test":[0.5263949988,0.6923592968]},
{"learn":[0.5632352268,0.6822923813],"iteration":61,"passed_time":0.4271669298,"remaining_time":6.462622261,"test":[0.5267423015,0.6923476579]},
{"learn":[0.5630521353,0.6822333406],"iteration":62,"passed_time":0.4320567079,"remaining_time":6.425986275,"test":[0.5265107664,0.692348824]},
{"learn":[0.5634412047,0.6821367311],"iteration":63,"passed_time":0.4365471784,"remaining_time":6.384502485,"test":[0.5259319287,0.6923600086]},
{"learn":[0.5636242962,0.6820867227],"iteration":64,"passed_time":0.441430998,"remaining_time":6.349815124,"test":[0.5254688585,0.692372751]},
{"learn":[0.563715842,0.6820256999],"iteration":65,"passed_time":0.4463418185,"remaining_time":6.316413008,"test":[0.5260476962,0.6924455742]},
{"learn":[0.5638531606,0.6819329129],"iteration":66,"passed_time":0.4521836345,"remaining_time":6.296825836,"test":[0.5255846261,0.6924748157]},
{"learn":[0.5638989335,0.6818706983],"iteration":67,"passed_time":0.4572557949,"remaining_time":6.26709413,"test":[0.5273211392,0.6924789867]},
{"learn":[0.5639447064,0.6818045601],"iteration":68,"passed_time":0.4627077204,"remaining_time":6.24320127,"test":[0.5267423015,0.6924876711]},
{"learn":[0.564082025,0.68171304],"iteration":69,"passed_time":0.4675020363,"remaining_time":6.211098482,"test":[0.5266265339,0.692489792]},
{"learn":[0.5640362521,0.6816251881],"iteration":70,"passed_time":0.4724773595,"remaining_time":6.182133337,"test":[0.5266265339,0.6924869911]},
{"learn":[0.5645168673,0.6815203712],"iteration":71,"passed_time":0.4776511072,"remaining_time":6.156392048,"test":[0.5267423015,0.6925206162]},
{"learn":[0.564448208,0.6814559429],"iteration":72,"passed_time":0.4825719282,"remaining_time":6.12800243,"test":[0.5262792313,0.6925313913]},
{"learn":[0.5646541859,0.6813334168],"iteration":73,"passed_time":0.4878153037,"remaining_time":6.104283395,"test":[0.5258161611,0.6925367884]},
{"learn":[0.5649059367,0.6812138418],"iteration":74,"passed_time":0.493698288,"remaining_time":6.088945552,"test":[0.5265107664,0.6925626459]},
{"learn":[0.5649059367,0.6811428702],"iteration":75,"passed_time":0.4991032116,"remaining_time":6.068044309,"test":[0.5273211392,0.6925711713]},
{"learn":[0.565180574,0.6810679335],"iteration":76,"passed_time":0.5041589963,"remaining_time":6.043360436,"test":[0.526858069,0.6926776812]},
{"learn":[0.5653407791,0.6810189357],"iteration":77,"passed_time":0.509317035,"remaining_time":6.020388543,"test":[0.5274369067,0.6926884897]},
{"learn":[0.5655696434,0.6809239905],"iteration":78,"passed_time":0.5151628512,"remaining_time":6.005885898,"test":[0.5266265339,0.6926819413]},
{"learn":[0.5651119147,0.6808452293],"iteration":79,"passed_time":0.5199159989,"remaining_time":5.979033987,"test":[0.5272053716,0.692682082]},
{"learn":[0.5653407791,0.6807843648],"iteration":80,"passed_time":0.5252913379,"remaining_time":5.959786908,"test":[0.5270896041,0.6927326486]},
{"learn":[0.565180574,0.6807084812],"iteration":81,"passed_time":0.5302294096,"remaining_time":5.935982903,"test":[0.5266265339,0.69272742]},
{"learn":[0.5654780977,0.6806237044],"iteration":82,"passed_time":0.5348115921,"remaining_time":5.908701566,"test":[0.5272053716,0.6927260891]},
{"learn":[0.5656840756,0.680541791],"iteration":83,"passed_time":0.5399401714,"remaining_time":5.887919012,"test":[0.5267423015,0.6927850957]},
{"learn":[0.5653636655,0.6804402561],"iteration":84,"passed_time":0.5459297433,"remaining_time":5.876773119,"test":[0.5266265339,0.6928797523]},
{"learn":[0.565546757,0.6803841011],"iteration":85,"passed_time":0.5557997614,"remaining_time":5.906988161,"test":[0.526858069,0.6928753106]},
{"learn":[0.5657298485,0.6803302076],"iteration":86,"passed_time":0.5625750729,"remaining_time":5.903805075,"test":[0.525353091,0.6929332764]},
{"learn":[0.5663248959,0.6802294187],"iteration":87,"passed_time":0.5682492989,"remaining_time":5.889129097,"test":[0.5265107664,0.6929225447]},
{"learn":[0.5660273722,0.6801427052],"iteration":88,"passed_time":0.5754560859,"remaining_time":5.890342632,"test":[0.5262792313,0.6929127979]},
{"learn":[0.5664393281,0.680092343],"iteration":89,"passed_time":0.5809644303,"remaining_time":5.874195906,"test":[0.5278999768,0.6929168776]},
{"learn":[0.5666224196,0.6800188515],"iteration":90,"passed_time":0.5858589586,"remaining_time":5.852151575,"test":[0.5261634638,0.6930123071]},
{"learn":[0.567377672,0.6799633931],"iteration":91,"passed_time":0.5921711266,"remaining_time":5.844471554,"test":[0.5260476962,0.6930271402]},
{"learn":[0.5676523092,0.6799142593],"iteration":92,"passed_time":0.597831102,"remaining_time":5.830460318,"test":[0.525353091,0.693074147]},
{"learn":[0.5677667414,0.6798218244],"iteration":93,"passed_time":0.602484454,"remaining_time":5.806924632,"test":[0.5257003936,0.693098238]},
{"learn":[0.5679727194,0.6797624264],"iteration":94,"passed_time":0.6079672558,"remaining_time":5.791688068,"test":[0.5252373235,0.6931168231]},
{"learn":[0.5682702431,0.6796825487],"iteration":95,"passed_time":0.6136230643,"remaining_time":5.778283856,"test":[0.5252373235,0.6930889734]},
{"learn":[0.5683160159,0.6796119175],"iteration":96,"passed_time":0.6183110011,"remaining_time":5.756029216,"test":[0.5265107664,0.6931814079]},
{"learn":[0.5681786973,0.6795251979],"iteration":97,"passed_time":0.6239766434,"remaining_time":5.743131962,"test":[0.5265107664,0.6935736538]},
{"learn":[0.5684533346,0.6794647206],"iteration":98,"passed_time":0.6295706578,"remaining_time":5.729728916,"test":[0.5257003936,0.6935709125]},
{"learn":[0.5690026091,0.6793963783],"iteration":99,"passed_time":0.634798366,"remaining_time":5.713185294,"test":[0.5262792313,0.6935839531]},
{"learn":[0.5689797226,0.6793126045],"iteration":100,"passed_time":0.6405399697,"remaining_time":5.701439929,"test":[0.5250057884,0.6936165151]},
{"learn":[0.569208587,0.6792105367],"iteration":101,"passed_time":0.6456661739,"remaining_time":5.684394354,"test":[0.5250057884,0.6936424871]},
{"learn":[0.5693687921,0.6791494493],"iteration":102,"passed_time":0.6504003208,"remaining_time":5.6641659,"test":[0.5246584858,0.6936432847]},
{"learn":[0.5695518836,0.6790732211],"iteration":103,"passed_time":0.6554747729,"remaining_time":5.647167274,"test":[0.5245427182,0.6936493519]},
{"learn":[0.5698265208,0.6789905315],"iteration":104,"passed_time":0.6618330678,"remaining_time":5.641339006,"test":[0.5248900208,0.6936865636]},
{"learn":[0.5698951801,0.67895488],"iteration":105,"passed_time":0.667103236,"remaining_time":5.626323519,"test":[0.5252373235,0.693675143]},
{"learn":[0.5700324987,0.6788948137],"iteration":106,"passed_time":0.6723126518,"remaining_time":5.61098316,"test":[0.5255846261,0.6936874053]},
{"learn":[0.5704902275,0.6788457101],"iteration":107,"passed_time":0.6775288179,"remaining_time":5.595886163,"test":[0.5254688585,0.693689784]},
{"learn":[0.5704902275,0.6787737664],"iteration":108,"passed_time":0.6826580222,"remaining_time":5.580259613,"test":[0.5251215559,0.6936707742]},
{"learn":[0.5708106376,0.6786897689],"iteration":109,"passed_time":0.6879676087,"remaining_time":5.566283379,"test":[0.5241954156,0.6937073345]},
{"learn":[0.5709021834,0.6786357569],"iteration":110,"passed_time":0.6932970293,"remaining_time":5.552622153,"test":[0.5243111831,0.6937614267]},
{"learn":[0.5711768206,0.6785404316],"iteration":111,"passed_time":0.6991878889,"remaining_time":5.543561119,"test":[0.5219958324,0.6937989599]},
{"learn":[0.5714972307,0.6784635222],"iteration":112,"passed_time":0.7044325144,"remaining_time":5.529483542,"test":[0.5218800648,0.6938027615]},
{"learn":[0.5716345494,0.6783830081],"iteration":113,"passed_time":0.7094082543,"remaining_time":5.513471169,"test":[0.5232692753,0.6939119665]},
{"learn":[0.5716803222,0.6783261377],"iteration":114,"passed_time":0.714549459,"remaining_time":5.498924098,"test":[0.5235008104,0.6939374584]},
{"learn":[0.5717947544,0.6782551829],"iteration":115,"passed_time":0.7192722305,"remaining_time":5.481350446,"test":[0.5229219727,0.6939266092]},
{"learn":[0.5717260951,0.6782039946],"iteration":116,"passed_time":0.7258457006,"remaining_time":5.477963706,"test":[0.5222273674,0.6939398216]},
{"learn":[0.5719549595,0.6781381441],"iteration":117,"passed_time":0.7316315144,"remaining_time":5.468635557,"test":[0.5221115999,0.6939581303]},
{"learn":[0.5720007324,0.6780959678],"iteration":118,"passed_time":0.7375646257,"remaining_time":5.460457439,"test":[0.522343135,0.6940189884]},
{"learn":[0.5721609374,0.6780580972],"iteration":119,"passed_time":0.7444090649,"remaining_time":5.45899981,"test":[0.5224589025,0.6940203223]},
{"learn":[0.5722982561,0.6779909469],"iteration":120,"passed_time":0.7511739177,"remaining_time":5.456874989,"test":[0.5228062051,0.6940373975]},
{"learn":[0.5722753696,0.6779155506],"iteration":121,"passed_time":0.7581201944,"remaining_time":5.455979759,"test":[0.5216485298,0.6940836851]},
{"learn":[0.5726186662,0.67787157],"iteration":122,"passed_time":0.7638367137,"remaining_time":5.446217869,"test":[0.5217642973,0.6940971538]},
{"learn":[0.5729161899,0.6778021605],"iteration":123,"passed_time":0.7741768755,"remaining_time":5.469185024,"test":[0.5222273674,0.6941038908]},
{"learn":[0.5732594864,0.6777441308],"iteration":124,"passed_time":0.7811099433,"remaining_time":5.467769603,"test":[0.5222273674,0.6941318899]},
{"learn":[0.5737172152,0.6776640278],"iteration":125,"passed_time":0.7923285151,"remaining_time":5.495993034,"test":[0.5245427182,0.6941318581]},
{"learn":[0.5741062846,0.6775768826],"iteration":126,"passed_time":0.7972769206,"remaining_time":5.480494108,"test":[0.5240796481,0.6939774214]},
{"learn":[0.5738545338,0.6775322572],"iteration":127,"passed_time":0.8045955871,"remaining_time":5.481307437,"test":[0.5246584858,0.6939790446]},
{"learn":[0.5738545338,0.6774381457],"iteration":128,"passed_time":0.8096396213,"remaining_time":5.466636513,"test":[0.5244269507,0.6939875313]},
{"learn":[0.5739460796,0.6773933791],"iteration":129,"passed_time":0.8141404672,"remaining_time":5.448478511,"test":[0.5239638805,0.6940065251]},
{"learn":[0.5738545338,0.6773073305],"iteration":130,"passed_time":0.8197628995,"remaining_time":5.437969158,"test":[0.5237323455,0.6940062666]},
{"learn":[0.5746555591,0.6772430348],"iteration":131,"passed_time":0.8257601383,"remaining_time":5.429998485,"test":[0.5231535078,0.6940655128]},
{"learn":[0.5745640134,0.6771630132],"iteration":132,"passed_time":0.8309698042,"remaining_time":5.41692346,"test":[0.5226904376,0.6940488902]},
{"learn":[0.5747242184,0.6770868641],"iteration":133,"passed_time":0.8383795993,"remaining_time":5.418184574,"test":[0.5221115999,0.6940625077]},
{"learn":[0.5749988557,0.6770144627],"iteration":134,"passed_time":0.8433877987,"remaining_time":5.403929229,"test":[0.5225746701,0.6940868198]},
{"learn":[0.5747699913,0.6769488704],"iteration":135,"passed_time":0.848858975,"remaining_time":5.392751135,"test":[0.5229219727,0.6940640084]},
{"learn":[0.5751590607,0.6768712881],"iteration":136,"passed_time":0.8548430883,"remaining_time":5.384887483,"test":[0.5232692753,0.6941097371]},
{"learn":[0.5755939031,0.6767999464],"iteration":137,"passed_time":0.860643611,"remaining_time":5.375904295,"test":[0.5244269507,0.6940823091]},
{"learn":[0.5755710166,0.6767315844],"iteration":138,"passed_time":0.8670014892,"remaining_time":5.370419297,"test":[0.5237323455,0.6941720366]},
{"learn":[0.5758914267,0.6766580612],"iteration":139,"passed_time":0.8721688617,"remaining_time":5.357608722,"test":[0.5235008104,0.6942806828]}
]}
Binary file not shown.
+141
View File
@@ -0,0 +1,141 @@
iter Accuracy Logloss
0 0.5470774019 0.6925856518
1 0.5485192475 0.6920296194
2 0.5508078912 0.6915437145
3 0.5511969607 0.6911042525
4 0.5528218977 0.6906429178
5 0.553096535 0.6902328044
6 0.5537144688 0.6899070645
7 0.552730352 0.6895966245
8 0.5539204467 0.6892625204
9 0.554561267 0.6889386437
10 0.554561267 0.6886345552
11 0.5548587907 0.6883728473
12 0.5552020872 0.6881185803
13 0.5564150684 0.6879043975
14 0.5559802261 0.6877314929
15 0.5564150684 0.6875118582
16 0.5569414565 0.6872755986
17 0.5561862041 0.6870435386
18 0.5561633176 0.6868442431
19 0.5575593903 0.6866694237
20 0.5575593903 0.686503203
21 0.557124548 0.6863452751
22 0.5569185701 0.6862172686
23 0.5573762988 0.6860730724
24 0.5571703209 0.6859302623
25 0.5570101158 0.6858087212
26 0.5571474344 0.6857036471
27 0.5571932073 0.6855803136
28 0.5572160937 0.6854590307
29 0.5568041379 0.6853245403
30 0.5570558887 0.6852010211
31 0.5570330022 0.6850648161
32 0.5578340276 0.6849486439
33 0.5576738225 0.6848167763
34 0.5577424818 0.6847064338
35 0.5582917563 0.6846073419
36 0.5587037122 0.6845016816
37 0.5587494851 0.6844106082
38 0.5591156681 0.6843432648
39 0.5589325766 0.6842678359
40 0.5590012359 0.6841515192
41 0.5590698952 0.6840610381
42 0.5593445324 0.6839565281
43 0.5592529867 0.6838539206
44 0.559321646 0.6837417759
45 0.559687829 0.6836669298
46 0.5603744221 0.6835634424
47 0.5599853527 0.6834698484
48 0.5601913306 0.6833860083
49 0.5602599899 0.6833086288
50 0.5606490594 0.683239475
51 0.5609694695 0.6831625258
52 0.5611983339 0.683056699
53 0.5617018355 0.6829611591
54 0.5619993592 0.6828690012
55 0.5620451321 0.6827776665
56 0.5624342015 0.6826927644
57 0.5626401794 0.6825957686
58 0.5626630659 0.6825449467
59 0.5627088387 0.6824559998
60 0.562983476 0.6823684725
61 0.5632352268 0.6822923813
62 0.5630521353 0.6822333406
63 0.5634412047 0.6821367311
64 0.5636242962 0.6820867227
65 0.563715842 0.6820256999
66 0.5638531606 0.6819329129
67 0.5638989335 0.6818706983
68 0.5639447064 0.6818045601
69 0.564082025 0.68171304
70 0.5640362521 0.6816251881
71 0.5645168673 0.6815203712
72 0.564448208 0.6814559429
73 0.5646541859 0.6813334168
74 0.5649059367 0.6812138418
75 0.5649059367 0.6811428702
76 0.565180574 0.6810679335
77 0.5653407791 0.6810189357
78 0.5655696434 0.6809239905
79 0.5651119147 0.6808452293
80 0.5653407791 0.6807843648
81 0.565180574 0.6807084812
82 0.5654780977 0.6806237044
83 0.5656840756 0.680541791
84 0.5653636655 0.6804402561
85 0.565546757 0.6803841011
86 0.5657298485 0.6803302076
87 0.5663248959 0.6802294187
88 0.5660273722 0.6801427052
89 0.5664393281 0.680092343
90 0.5666224196 0.6800188515
91 0.567377672 0.6799633931
92 0.5676523092 0.6799142593
93 0.5677667414 0.6798218244
94 0.5679727194 0.6797624264
95 0.5682702431 0.6796825487
96 0.5683160159 0.6796119175
97 0.5681786973 0.6795251979
98 0.5684533346 0.6794647206
99 0.5690026091 0.6793963783
100 0.5689797226 0.6793126045
101 0.569208587 0.6792105367
102 0.5693687921 0.6791494493
103 0.5695518836 0.6790732211
104 0.5698265208 0.6789905315
105 0.5698951801 0.67895488
106 0.5700324987 0.6788948137
107 0.5704902275 0.6788457101
108 0.5704902275 0.6787737664
109 0.5708106376 0.6786897689
110 0.5709021834 0.6786357569
111 0.5711768206 0.6785404316
112 0.5714972307 0.6784635222
113 0.5716345494 0.6783830081
114 0.5716803222 0.6783261377
115 0.5717947544 0.6782551829
116 0.5717260951 0.6782039946
117 0.5719549595 0.6781381441
118 0.5720007324 0.6780959678
119 0.5721609374 0.6780580972
120 0.5722982561 0.6779909469
121 0.5722753696 0.6779155506
122 0.5726186662 0.67787157
123 0.5729161899 0.6778021605
124 0.5732594864 0.6777441308
125 0.5737172152 0.6776640278
126 0.5741062846 0.6775768826
127 0.5738545338 0.6775322572
128 0.5738545338 0.6774381457
129 0.5739460796 0.6773933791
130 0.5738545338 0.6773073305
131 0.5746555591 0.6772430348
132 0.5745640134 0.6771630132
133 0.5747242184 0.6770868641
134 0.5749988557 0.6770144627
135 0.5747699913 0.6769488704
136 0.5751590607 0.6768712881
137 0.5755939031 0.6767999464
138 0.5755710166 0.6767315844
139 0.5758914267 0.6766580612
1 iter Accuracy Logloss
2 0 0.5470774019 0.6925856518
3 1 0.5485192475 0.6920296194
4 2 0.5508078912 0.6915437145
5 3 0.5511969607 0.6911042525
6 4 0.5528218977 0.6906429178
7 5 0.553096535 0.6902328044
8 6 0.5537144688 0.6899070645
9 7 0.552730352 0.6895966245
10 8 0.5539204467 0.6892625204
11 9 0.554561267 0.6889386437
12 10 0.554561267 0.6886345552
13 11 0.5548587907 0.6883728473
14 12 0.5552020872 0.6881185803
15 13 0.5564150684 0.6879043975
16 14 0.5559802261 0.6877314929
17 15 0.5564150684 0.6875118582
18 16 0.5569414565 0.6872755986
19 17 0.5561862041 0.6870435386
20 18 0.5561633176 0.6868442431
21 19 0.5575593903 0.6866694237
22 20 0.5575593903 0.686503203
23 21 0.557124548 0.6863452751
24 22 0.5569185701 0.6862172686
25 23 0.5573762988 0.6860730724
26 24 0.5571703209 0.6859302623
27 25 0.5570101158 0.6858087212
28 26 0.5571474344 0.6857036471
29 27 0.5571932073 0.6855803136
30 28 0.5572160937 0.6854590307
31 29 0.5568041379 0.6853245403
32 30 0.5570558887 0.6852010211
33 31 0.5570330022 0.6850648161
34 32 0.5578340276 0.6849486439
35 33 0.5576738225 0.6848167763
36 34 0.5577424818 0.6847064338
37 35 0.5582917563 0.6846073419
38 36 0.5587037122 0.6845016816
39 37 0.5587494851 0.6844106082
40 38 0.5591156681 0.6843432648
41 39 0.5589325766 0.6842678359
42 40 0.5590012359 0.6841515192
43 41 0.5590698952 0.6840610381
44 42 0.5593445324 0.6839565281
45 43 0.5592529867 0.6838539206
46 44 0.559321646 0.6837417759
47 45 0.559687829 0.6836669298
48 46 0.5603744221 0.6835634424
49 47 0.5599853527 0.6834698484
50 48 0.5601913306 0.6833860083
51 49 0.5602599899 0.6833086288
52 50 0.5606490594 0.683239475
53 51 0.5609694695 0.6831625258
54 52 0.5611983339 0.683056699
55 53 0.5617018355 0.6829611591
56 54 0.5619993592 0.6828690012
57 55 0.5620451321 0.6827776665
58 56 0.5624342015 0.6826927644
59 57 0.5626401794 0.6825957686
60 58 0.5626630659 0.6825449467
61 59 0.5627088387 0.6824559998
62 60 0.562983476 0.6823684725
63 61 0.5632352268 0.6822923813
64 62 0.5630521353 0.6822333406
65 63 0.5634412047 0.6821367311
66 64 0.5636242962 0.6820867227
67 65 0.563715842 0.6820256999
68 66 0.5638531606 0.6819329129
69 67 0.5638989335 0.6818706983
70 68 0.5639447064 0.6818045601
71 69 0.564082025 0.68171304
72 70 0.5640362521 0.6816251881
73 71 0.5645168673 0.6815203712
74 72 0.564448208 0.6814559429
75 73 0.5646541859 0.6813334168
76 74 0.5649059367 0.6812138418
77 75 0.5649059367 0.6811428702
78 76 0.565180574 0.6810679335
79 77 0.5653407791 0.6810189357
80 78 0.5655696434 0.6809239905
81 79 0.5651119147 0.6808452293
82 80 0.5653407791 0.6807843648
83 81 0.565180574 0.6807084812
84 82 0.5654780977 0.6806237044
85 83 0.5656840756 0.680541791
86 84 0.5653636655 0.6804402561
87 85 0.565546757 0.6803841011
88 86 0.5657298485 0.6803302076
89 87 0.5663248959 0.6802294187
90 88 0.5660273722 0.6801427052
91 89 0.5664393281 0.680092343
92 90 0.5666224196 0.6800188515
93 91 0.567377672 0.6799633931
94 92 0.5676523092 0.6799142593
95 93 0.5677667414 0.6798218244
96 94 0.5679727194 0.6797624264
97 95 0.5682702431 0.6796825487
98 96 0.5683160159 0.6796119175
99 97 0.5681786973 0.6795251979
100 98 0.5684533346 0.6794647206
101 99 0.5690026091 0.6793963783
102 100 0.5689797226 0.6793126045
103 101 0.569208587 0.6792105367
104 102 0.5693687921 0.6791494493
105 103 0.5695518836 0.6790732211
106 104 0.5698265208 0.6789905315
107 105 0.5698951801 0.67895488
108 106 0.5700324987 0.6788948137
109 107 0.5704902275 0.6788457101
110 108 0.5704902275 0.6787737664
111 109 0.5708106376 0.6786897689
112 110 0.5709021834 0.6786357569
113 111 0.5711768206 0.6785404316
114 112 0.5714972307 0.6784635222
115 113 0.5716345494 0.6783830081
116 114 0.5716803222 0.6783261377
117 115 0.5717947544 0.6782551829
118 116 0.5717260951 0.6782039946
119 117 0.5719549595 0.6781381441
120 118 0.5720007324 0.6780959678
121 119 0.5721609374 0.6780580972
122 120 0.5722982561 0.6779909469
123 121 0.5722753696 0.6779155506
124 122 0.5726186662 0.67787157
125 123 0.5729161899 0.6778021605
126 124 0.5732594864 0.6777441308
127 125 0.5737172152 0.6776640278
128 126 0.5741062846 0.6775768826
129 127 0.5738545338 0.6775322572
130 128 0.5738545338 0.6774381457
131 129 0.5739460796 0.6773933791
132 130 0.5738545338 0.6773073305
133 131 0.5746555591 0.6772430348
134 132 0.5745640134 0.6771630132
135 133 0.5747242184 0.6770868641
136 134 0.5749988557 0.6770144627
137 135 0.5747699913 0.6769488704
138 136 0.5751590607 0.6768712881
139 137 0.5755939031 0.6767999464
140 138 0.5755710166 0.6767315844
141 139 0.5758914267 0.6766580612
Binary file not shown.
+141
View File
@@ -0,0 +1,141 @@
iter Accuracy Logloss
0 0.5215327622 0.6929069842
1 0.5201435517 0.6926980686
2 0.5235008104 0.6925995058
3 0.5243111831 0.6923741119
4 0.5240796481 0.6922359346
5 0.5217642973 0.6921532273
6 0.5241954156 0.6920533177
7 0.520838157 0.6919667553
8 0.520838157 0.6919667553
9 0.520838157 0.6918984047
10 0.5213012271 0.6918473786
11 0.5217642973 0.691760433
12 0.5231535078 0.6917269838
13 0.5210696921 0.6916992448
14 0.522343135 0.6916911046
15 0.5213012271 0.6916992139
16 0.5204908544 0.6917199845
17 0.5226904376 0.6917129587
18 0.5225746701 0.6916992772
19 0.5207223894 0.6917050845
20 0.5247742533 0.6916027729
21 0.5239638805 0.6916046624
22 0.5241954156 0.6916203654
23 0.5243111831 0.6916491893
24 0.5244269507 0.6916666458
25 0.5235008104 0.691666967
26 0.5229219727 0.6916827648
27 0.5245427182 0.6917018334
28 0.5222273674 0.6916998315
29 0.5224589025 0.6917500551
30 0.5222273674 0.691867972
31 0.5215327622 0.6918745283
32 0.5214169947 0.691873578
33 0.5215327622 0.6918621914
34 0.5222273674 0.691870106
35 0.5219958324 0.6919348629
36 0.5245427182 0.6920133967
37 0.5246584858 0.6920145372
38 0.5247742533 0.6920231409
39 0.5248900208 0.6920429941
40 0.5248900208 0.6920318228
41 0.5252373235 0.6920202842
42 0.525353091 0.6920430337
43 0.5247742533 0.6920265347
44 0.525353091 0.6920697067
45 0.5263949988 0.692116771
46 0.5259319287 0.692098178
47 0.5260476962 0.692152499
48 0.5262792313 0.6922137468
49 0.5270896041 0.6922167687
50 0.5272053716 0.6922875182
51 0.526858069 0.6923662256
52 0.526858069 0.6923707756
53 0.5270896041 0.6923776257
54 0.5259319287 0.692400255
55 0.5266265339 0.692269877
56 0.5260476962 0.6922932744
57 0.5266265339 0.6923264029
58 0.5263949988 0.692314059
59 0.5263949988 0.6923015601
60 0.5263949988 0.6923592968
61 0.5267423015 0.6923476579
62 0.5265107664 0.692348824
63 0.5259319287 0.6923600086
64 0.5254688585 0.692372751
65 0.5260476962 0.6924455742
66 0.5255846261 0.6924748157
67 0.5273211392 0.6924789867
68 0.5267423015 0.6924876711
69 0.5266265339 0.692489792
70 0.5266265339 0.6924869911
71 0.5267423015 0.6925206162
72 0.5262792313 0.6925313913
73 0.5258161611 0.6925367884
74 0.5265107664 0.6925626459
75 0.5273211392 0.6925711713
76 0.526858069 0.6926776812
77 0.5274369067 0.6926884897
78 0.5266265339 0.6926819413
79 0.5272053716 0.692682082
80 0.5270896041 0.6927326486
81 0.5266265339 0.69272742
82 0.5272053716 0.6927260891
83 0.5267423015 0.6927850957
84 0.5266265339 0.6928797523
85 0.526858069 0.6928753106
86 0.525353091 0.6929332764
87 0.5265107664 0.6929225447
88 0.5262792313 0.6929127979
89 0.5278999768 0.6929168776
90 0.5261634638 0.6930123071
91 0.5260476962 0.6930271402
92 0.525353091 0.693074147
93 0.5257003936 0.693098238
94 0.5252373235 0.6931168231
95 0.5252373235 0.6930889734
96 0.5265107664 0.6931814079
97 0.5265107664 0.6935736538
98 0.5257003936 0.6935709125
99 0.5262792313 0.6935839531
100 0.5250057884 0.6936165151
101 0.5250057884 0.6936424871
102 0.5246584858 0.6936432847
103 0.5245427182 0.6936493519
104 0.5248900208 0.6936865636
105 0.5252373235 0.693675143
106 0.5255846261 0.6936874053
107 0.5254688585 0.693689784
108 0.5251215559 0.6936707742
109 0.5241954156 0.6937073345
110 0.5243111831 0.6937614267
111 0.5219958324 0.6937989599
112 0.5218800648 0.6938027615
113 0.5232692753 0.6939119665
114 0.5235008104 0.6939374584
115 0.5229219727 0.6939266092
116 0.5222273674 0.6939398216
117 0.5221115999 0.6939581303
118 0.522343135 0.6940189884
119 0.5224589025 0.6940203223
120 0.5228062051 0.6940373975
121 0.5216485298 0.6940836851
122 0.5217642973 0.6940971538
123 0.5222273674 0.6941038908
124 0.5222273674 0.6941318899
125 0.5245427182 0.6941318581
126 0.5240796481 0.6939774214
127 0.5246584858 0.6939790446
128 0.5244269507 0.6939875313
129 0.5239638805 0.6940065251
130 0.5237323455 0.6940062666
131 0.5231535078 0.6940655128
132 0.5226904376 0.6940488902
133 0.5221115999 0.6940625077
134 0.5225746701 0.6940868198
135 0.5229219727 0.6940640084
136 0.5232692753 0.6941097371
137 0.5244269507 0.6940823091
138 0.5237323455 0.6941720366
139 0.5235008104 0.6942806828
1 iter Accuracy Logloss
2 0 0.5215327622 0.6929069842
3 1 0.5201435517 0.6926980686
4 2 0.5235008104 0.6925995058
5 3 0.5243111831 0.6923741119
6 4 0.5240796481 0.6922359346
7 5 0.5217642973 0.6921532273
8 6 0.5241954156 0.6920533177
9 7 0.520838157 0.6919667553
10 8 0.520838157 0.6919667553
11 9 0.520838157 0.6918984047
12 10 0.5213012271 0.6918473786
13 11 0.5217642973 0.691760433
14 12 0.5231535078 0.6917269838
15 13 0.5210696921 0.6916992448
16 14 0.522343135 0.6916911046
17 15 0.5213012271 0.6916992139
18 16 0.5204908544 0.6917199845
19 17 0.5226904376 0.6917129587
20 18 0.5225746701 0.6916992772
21 19 0.5207223894 0.6917050845
22 20 0.5247742533 0.6916027729
23 21 0.5239638805 0.6916046624
24 22 0.5241954156 0.6916203654
25 23 0.5243111831 0.6916491893
26 24 0.5244269507 0.6916666458
27 25 0.5235008104 0.691666967
28 26 0.5229219727 0.6916827648
29 27 0.5245427182 0.6917018334
30 28 0.5222273674 0.6916998315
31 29 0.5224589025 0.6917500551
32 30 0.5222273674 0.691867972
33 31 0.5215327622 0.6918745283
34 32 0.5214169947 0.691873578
35 33 0.5215327622 0.6918621914
36 34 0.5222273674 0.691870106
37 35 0.5219958324 0.6919348629
38 36 0.5245427182 0.6920133967
39 37 0.5246584858 0.6920145372
40 38 0.5247742533 0.6920231409
41 39 0.5248900208 0.6920429941
42 40 0.5248900208 0.6920318228
43 41 0.5252373235 0.6920202842
44 42 0.525353091 0.6920430337
45 43 0.5247742533 0.6920265347
46 44 0.525353091 0.6920697067
47 45 0.5263949988 0.692116771
48 46 0.5259319287 0.692098178
49 47 0.5260476962 0.692152499
50 48 0.5262792313 0.6922137468
51 49 0.5270896041 0.6922167687
52 50 0.5272053716 0.6922875182
53 51 0.526858069 0.6923662256
54 52 0.526858069 0.6923707756
55 53 0.5270896041 0.6923776257
56 54 0.5259319287 0.692400255
57 55 0.5266265339 0.692269877
58 56 0.5260476962 0.6922932744
59 57 0.5266265339 0.6923264029
60 58 0.5263949988 0.692314059
61 59 0.5263949988 0.6923015601
62 60 0.5263949988 0.6923592968
63 61 0.5267423015 0.6923476579
64 62 0.5265107664 0.692348824
65 63 0.5259319287 0.6923600086
66 64 0.5254688585 0.692372751
67 65 0.5260476962 0.6924455742
68 66 0.5255846261 0.6924748157
69 67 0.5273211392 0.6924789867
70 68 0.5267423015 0.6924876711
71 69 0.5266265339 0.692489792
72 70 0.5266265339 0.6924869911
73 71 0.5267423015 0.6925206162
74 72 0.5262792313 0.6925313913
75 73 0.5258161611 0.6925367884
76 74 0.5265107664 0.6925626459
77 75 0.5273211392 0.6925711713
78 76 0.526858069 0.6926776812
79 77 0.5274369067 0.6926884897
80 78 0.5266265339 0.6926819413
81 79 0.5272053716 0.692682082
82 80 0.5270896041 0.6927326486
83 81 0.5266265339 0.69272742
84 82 0.5272053716 0.6927260891
85 83 0.5267423015 0.6927850957
86 84 0.5266265339 0.6928797523
87 85 0.526858069 0.6928753106
88 86 0.525353091 0.6929332764
89 87 0.5265107664 0.6929225447
90 88 0.5262792313 0.6929127979
91 89 0.5278999768 0.6929168776
92 90 0.5261634638 0.6930123071
93 91 0.5260476962 0.6930271402
94 92 0.525353091 0.693074147
95 93 0.5257003936 0.693098238
96 94 0.5252373235 0.6931168231
97 95 0.5252373235 0.6930889734
98 96 0.5265107664 0.6931814079
99 97 0.5265107664 0.6935736538
100 98 0.5257003936 0.6935709125
101 99 0.5262792313 0.6935839531
102 100 0.5250057884 0.6936165151
103 101 0.5250057884 0.6936424871
104 102 0.5246584858 0.6936432847
105 103 0.5245427182 0.6936493519
106 104 0.5248900208 0.6936865636
107 105 0.5252373235 0.693675143
108 106 0.5255846261 0.6936874053
109 107 0.5254688585 0.693689784
110 108 0.5251215559 0.6936707742
111 109 0.5241954156 0.6937073345
112 110 0.5243111831 0.6937614267
113 111 0.5219958324 0.6937989599
114 112 0.5218800648 0.6938027615
115 113 0.5232692753 0.6939119665
116 114 0.5235008104 0.6939374584
117 115 0.5229219727 0.6939266092
118 116 0.5222273674 0.6939398216
119 117 0.5221115999 0.6939581303
120 118 0.522343135 0.6940189884
121 119 0.5224589025 0.6940203223
122 120 0.5228062051 0.6940373975
123 121 0.5216485298 0.6940836851
124 122 0.5217642973 0.6940971538
125 123 0.5222273674 0.6941038908
126 124 0.5222273674 0.6941318899
127 125 0.5245427182 0.6941318581
128 126 0.5240796481 0.6939774214
129 127 0.5246584858 0.6939790446
130 128 0.5244269507 0.6939875313
131 129 0.5239638805 0.6940065251
132 130 0.5237323455 0.6940062666
133 131 0.5231535078 0.6940655128
134 132 0.5226904376 0.6940488902
135 133 0.5221115999 0.6940625077
136 134 0.5225746701 0.6940868198
137 135 0.5229219727 0.6940640084
138 136 0.5232692753 0.6941097371
139 137 0.5244269507 0.6940823091
140 138 0.5237323455 0.6941720366
141 139 0.5235008104 0.6942806828
+141
View File
@@ -0,0 +1,141 @@
iter Passed Remaining
0 61 61139
1 66 33196
2 71 23708
3 76 18990
4 81 16262
5 86 14390
6 91 13010
7 97 12050
8 101 11219
9 107 10684
10 113 10199
11 118 9733
12 123 9371
13 129 9088
14 134 8843
15 139 8565
16 144 8375
17 149 8180
18 155 8031
19 160 7880
20 166 7774
21 171 7632
22 176 7499
23 181 7396
24 186 7277
25 191 7171
26 196 7090
27 202 7024
28 207 6943
29 217 7041
30 225 7061
31 234 7092
32 241 7067
33 247 7032
34 255 7032
35 261 7005
36 269 7006
37 275 6984
38 283 6981
39 289 6954
40 300 7027
41 308 7033
42 316 7052
43 324 7043
44 331 7029
45 336 6977
46 341 6926
47 349 6929
48 356 6923
49 364 6922
50 369 6878
51 375 6844
52 380 6806
53 386 6773
54 391 6730
55 396 6685
56 401 6646
57 406 6600
58 411 6566
59 416 6531
60 421 6490
61 427 6462
62 432 6425
63 436 6384
64 441 6349
65 446 6316
66 452 6296
67 457 6267
68 462 6243
69 467 6211
70 472 6182
71 477 6156
72 482 6128
73 487 6104
74 493 6088
75 499 6068
76 504 6043
77 509 6020
78 515 6005
79 519 5979
80 525 5959
81 530 5935
82 534 5908
83 539 5887
84 545 5876
85 555 5906
86 562 5903
87 568 5889
88 575 5890
89 580 5874
90 585 5852
91 592 5844
92 597 5830
93 602 5806
94 607 5791
95 613 5778
96 618 5756
97 623 5743
98 629 5729
99 634 5713
100 640 5701
101 645 5684
102 650 5664
103 655 5647
104 661 5641
105 667 5626
106 672 5610
107 677 5595
108 682 5580
109 687 5566
110 693 5552
111 699 5543
112 704 5529
113 709 5513
114 714 5498
115 719 5481
116 725 5477
117 731 5468
118 737 5460
119 744 5458
120 751 5456
121 758 5455
122 763 5446
123 774 5469
124 781 5467
125 792 5495
126 797 5480
127 804 5481
128 809 5466
129 814 5448
130 819 5437
131 825 5429
132 830 5416
133 838 5418
134 843 5403
135 848 5392
136 854 5384
137 860 5375
138 867 5370
139 872 5357
1 iter Passed Remaining
2 0 61 61139
3 1 66 33196
4 2 71 23708
5 3 76 18990
6 4 81 16262
7 5 86 14390
8 6 91 13010
9 7 97 12050
10 8 101 11219
11 9 107 10684
12 10 113 10199
13 11 118 9733
14 12 123 9371
15 13 129 9088
16 14 134 8843
17 15 139 8565
18 16 144 8375
19 17 149 8180
20 18 155 8031
21 19 160 7880
22 20 166 7774
23 21 171 7632
24 22 176 7499
25 23 181 7396
26 24 186 7277
27 25 191 7171
28 26 196 7090
29 27 202 7024
30 28 207 6943
31 29 217 7041
32 30 225 7061
33 31 234 7092
34 32 241 7067
35 33 247 7032
36 34 255 7032
37 35 261 7005
38 36 269 7006
39 37 275 6984
40 38 283 6981
41 39 289 6954
42 40 300 7027
43 41 308 7033
44 42 316 7052
45 43 324 7043
46 44 331 7029
47 45 336 6977
48 46 341 6926
49 47 349 6929
50 48 356 6923
51 49 364 6922
52 50 369 6878
53 51 375 6844
54 52 380 6806
55 53 386 6773
56 54 391 6730
57 55 396 6685
58 56 401 6646
59 57 406 6600
60 58 411 6566
61 59 416 6531
62 60 421 6490
63 61 427 6462
64 62 432 6425
65 63 436 6384
66 64 441 6349
67 65 446 6316
68 66 452 6296
69 67 457 6267
70 68 462 6243
71 69 467 6211
72 70 472 6182
73 71 477 6156
74 72 482 6128
75 73 487 6104
76 74 493 6088
77 75 499 6068
78 76 504 6043
79 77 509 6020
80 78 515 6005
81 79 519 5979
82 80 525 5959
83 81 530 5935
84 82 534 5908
85 83 539 5887
86 84 545 5876
87 85 555 5906
88 86 562 5903
89 87 568 5889
90 88 575 5890
91 89 580 5874
92 90 585 5852
93 91 592 5844
94 92 597 5830
95 93 602 5806
96 94 607 5791
97 95 613 5778
98 96 618 5756
99 97 623 5743
100 98 629 5729
101 99 634 5713
102 100 640 5701
103 101 645 5684
104 102 650 5664
105 103 655 5647
106 104 661 5641
107 105 667 5626
108 106 672 5610
109 107 677 5595
110 108 682 5580
111 109 687 5566
112 110 693 5552
113 111 699 5543
114 112 704 5529
115 113 709 5513
116 114 714 5498
117 115 719 5481
118 116 725 5477
119 117 731 5468
120 118 737 5460
121 119 744 5458
122 120 751 5456
123 121 758 5455
124 122 763 5446
125 123 774 5469
126 124 781 5467
127 125 792 5495
128 126 797 5480
129 127 804 5481
130 128 809 5466
131 129 814 5448
132 130 819 5437
133 131 825 5429
134 132 830 5416
135 133 838 5418
136 134 843 5403
137 135 848 5392
138 136 854 5384
139 137 860 5375
140 138 867 5370
141 139 872 5357
+460
View File
@@ -0,0 +1,460 @@
"""按天统计双模型和三模型的准确率"""
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 data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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("按天统计:双模型交集 vs 三模型投票 每日准确率分析")
print("="*80)
# ========== 加载模型 ==========
print("\n[1/4] 加载模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear
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)
# 加载N-BEATS
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)
# 加载TTM-R2
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(f"✓ 三个模型已加载")
# ========== 获取数据 ==========
print("\n[2/4] 获取2026年测试数据...")
fetcher = BinanceDataFetcher()
try:
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2025-12-26',
end_date='2026-02-02', 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("\n[3/4] 预处理数据...")
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]
y_nbeats = y_nbeats[:min_samples]
# ========== 预测 ==========
print("\n[4/4] 执行预测...")
# 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))
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten()
# 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)
print(f"✓ 预测完成")
# ========== 构建结果DataFrame ==========
# 获取对应的时间戳(第i个样本对应df_2026的第168+i根K线)
timestamps = df_2026['timestamp'].iloc[168:168+min_samples].values
# 转换为美国东部时间 (ET)
timestamps_utc = pd.to_datetime(timestamps)
timestamps_et = timestamps_utc.tz_localize('UTC').tz_convert('America/New_York')
dates = timestamps_et.date
# 双模型交集
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
# 创建DataFrame
results_df = pd.DataFrame({
'date': dates,
'timestamp': timestamps,
'timestamp_et': timestamps_et,
'true_label': true_labels,
'dlinear_pred': dlinear_preds,
'nbeats_pred': nbeats_preds,
'ttm_pred': ttm_preds,
'two_model_pred': two_model_preds,
'three_model_pred': three_model_preds,
'two_model_correct': (two_model_preds == true_labels) & (two_model_preds != -1),
'three_model_correct': (three_model_preds == true_labels)
})
# 添加周几信息(美国东部时间)
results_df['day_of_week'] = pd.to_datetime(results_df['timestamp_et']).dt.day_name()
results_df['is_weekend'] = pd.to_datetime(results_df['timestamp_et']).dt.dayofweek >= 5 # 5=Saturday, 6=Sunday
# 2026年1月美国重要经济数据发布日期(美国东部时间)
# 数据来源:美联储、劳工部等官方日历
us_economic_events = {
'2026-01-08': ['初请失业金人数'],
'2026-01-09': ['非农就业报告 (NFP)', '失业率'],
'2026-01-13': ['CPI通胀数据'],
'2026-01-14': ['PPI生产者物价指数'],
'2026-01-15': ['零售销售数据'],
'2026-01-16': ['密歇根大学消费者信心指数'],
'2026-01-22': ['初请失业金人数'],
'2026-01-27': ['耐用品订单'],
'2026-01-28': ['FOMC利率决议', 'GDP初值'],
'2026-01-29': ['PCE物价指数', '个人收入与支出'],
}
# ========== 按天统计 ==========
print("\n" + "="*80)
print("📊 每日准确率统计 (美国东部时间)")
print("="*80)
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', # 三模型正确数
'is_weekend': 'first', # 是否周末
'day_of_week': 'first' # 星期几
})
daily_stats.columns = ['total_samples', 'two_model_valid', 'two_model_correct',
'three_model_correct', 'is_weekend', 'day_of_week']
# 计算准确率
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
# 填充NaN(当天没有有效预测)
daily_stats['two_model_accuracy'] = daily_stats['two_model_accuracy'].fillna(0)
# 添加美国经济数据发布标记
daily_stats['us_events'] = daily_stats.index.map(
lambda x: us_economic_events.get(str(x), [])
)
daily_stats['has_us_data'] = daily_stats['us_events'].apply(lambda x: len(x) > 0)
print(f"\n每日详细统计:")
print(f"{'日期':<12} {'星期':<10} {'周末':<4} {'美国数据':<4} {'样本':>4} "
f"{'双模型%':>8} {'覆盖%':>6} {'三模型%':>8} {'差异':>7}")
print("-" * 95)
for date, row in daily_stats.iterrows():
diff = row['two_model_accuracy'] - row['three_model_accuracy']
diff_str = f"{diff:+.1f}" if row['two_model_valid'] > 0 else "N/A"
weekend_mark = "✓" if row['is_weekend'] else ""
us_data_mark = "📊" if row['has_us_data'] else ""
# 根据准确率设置颜色标记
two_acc = row['two_model_accuracy']
three_acc = row['three_model_accuracy']
two_mark = "🔥" if two_acc >= 70 else ("✅" if two_acc >= 60 else ("⚠️" if two_acc < 40 else ""))
three_mark = "🔥" if three_acc >= 70 else ("✅" if three_acc >= 60 else ("⚠️" if three_acc < 40 else ""))
print(f"{date} {row['day_of_week'][:3]:>8} {weekend_mark:>4} {us_data_mark:>4} "
f"{row['total_samples']:>4.0f} "
f"{row['two_model_accuracy']:>7.1f}{two_mark} "
f"{row['two_model_coverage']:>5.1f} "
f"{row['three_model_accuracy']:>7.1f}{three_mark} "
f"{diff_str:>7}")
# 如果有美国经济数据发布,显示详情
if row['has_us_data']:
events_str = ", ".join(row['us_events'])
print(f" └─ 📊 {events_str}")
# ========== 周末 vs 工作日分析 ==========
print("\n" + "="*80)
print("📅 周末 vs 工作日准确率对比")
print("="*80)
weekend_stats = daily_stats[daily_stats['is_weekend']]
weekday_stats = daily_stats[~daily_stats['is_weekend']]
if len(weekend_stats) > 0:
print(f"\n🌴 周末 ({len(weekend_stats)}天):")
print(f" 双模型平均准确率: {weekend_stats['two_model_accuracy'].mean():.2f}%")
print(f" 三模型平均准确率: {weekend_stats['three_model_accuracy'].mean():.2f}%")
print(f" 平均覆盖率: {weekend_stats['two_model_coverage'].mean():.2f}%")
if len(weekday_stats) > 0:
print(f"\n💼 工作日 ({len(weekday_stats)}天):")
print(f" 双模型平均准确率: {weekday_stats['two_model_accuracy'].mean():.2f}%")
print(f" 三模型平均准确率: {weekday_stats['three_model_accuracy'].mean():.2f}%")
print(f" 平均覆盖率: {weekday_stats['two_model_coverage'].mean():.2f}%")
# ========== 美国数据发布日 vs 普通日分析 ==========
print("\n" + "="*80)
print("📊 美国经济数据发布日 vs 普通日准确率对比")
print("="*80)
us_data_days = daily_stats[daily_stats['has_us_data']]
normal_days = daily_stats[~daily_stats['has_us_data']]
if len(us_data_days) > 0:
print(f"\n📊 美国数据发布日 ({len(us_data_days)}天):")
print(f" 双模型平均准确率: {us_data_days['two_model_accuracy'].mean():.2f}%")
print(f" 三模型平均准确率: {us_data_days['three_model_accuracy'].mean():.2f}%")
print(f" 平均覆盖率: {us_data_days['two_model_coverage'].mean():.2f}%")
print(f"\n 具体日期:")
for date in us_data_days.index:
events = ", ".join(us_data_days.loc[date, 'us_events'])
two_acc = us_data_days.loc[date, 'two_model_accuracy']
three_acc = us_data_days.loc[date, 'three_model_accuracy']
print(f" {date}: 双{two_acc:.1f}% / 三{three_acc:.1f}% - {events}")
if len(normal_days) > 0:
print(f"\n📅 普通日 ({len(normal_days)}天):")
print(f" 双模型平均准确率: {normal_days['two_model_accuracy'].mean():.2f}%")
print(f" 三模型平均准确率: {normal_days['three_model_accuracy'].mean():.2f}%")
print(f" 平均覆盖率: {normal_days['two_model_coverage'].mean():.2f}%")
# ========== 分类统计 ==========
print("\n" + "="*80)
print("📈 准确率分类统计")
print("="*80)
# 双模型交集分类
two_model_high = daily_stats[daily_stats['two_model_accuracy'] >= 60]
two_model_medium = daily_stats[(daily_stats['two_model_accuracy'] >= 45) &
(daily_stats['two_model_accuracy'] < 60)]
two_model_low = daily_stats[(daily_stats['two_model_accuracy'] > 0) &
(daily_stats['two_model_accuracy'] < 45)]
# 三模型投票分类
three_model_high = daily_stats[daily_stats['three_model_accuracy'] >= 60]
three_model_medium = daily_stats[(daily_stats['three_model_accuracy'] >= 45) &
(daily_stats['three_model_accuracy'] < 60)]
three_model_low = daily_stats[daily_stats['three_model_accuracy'] < 45]
print(f"\n🔵 双模型交集:")
print(f" 高准确率 (>=60%): {len(two_model_high)}天")
if len(two_model_high) > 0:
print(f" 日期: {', '.join([str(d) for d in two_model_high.index])}")
print(f" 平均准确率: {two_model_high['two_model_accuracy'].mean():.1f}%")
print(f"\n 中等准确率 (45-60%): {len(two_model_medium)}天")
if len(two_model_medium) > 0:
print(f" 日期: {', '.join([str(d) for d in two_model_medium.index])}")
print(f" 平均准确率: {two_model_medium['two_model_accuracy'].mean():.1f}%")
print(f"\n 低准确率 (<45%): {len(two_model_low)}天")
if len(two_model_low) > 0:
print(f" 日期: {', '.join([str(d) for d in two_model_low.index])}")
print(f" 平均准确率: {two_model_low['two_model_accuracy'].mean():.1f}%")
print(f"\n🟢 三模型投票:")
print(f" 高准确率 (>=60%): {len(three_model_high)}天")
if len(three_model_high) > 0:
print(f" 日期: {', '.join([str(d) for d in three_model_high.index])}")
print(f" 平均准确率: {three_model_high['three_model_accuracy'].mean():.1f}%")
print(f"\n 中等准确率 (45-60%): {len(three_model_medium)}天")
if len(three_model_medium) > 0:
print(f" 日期: {', '.join([str(d) for d in three_model_medium.index])}")
print(f" 平均准确率: {three_model_medium['three_model_accuracy'].mean():.1f}%")
print(f"\n 低准确率 (<45%): {len(three_model_low)}天")
if len(three_model_low) > 0:
print(f" 日期: {', '.join([str(d) for d in three_model_low.index])}")
print(f" 平均准确率: {three_model_low['three_model_accuracy'].mean():.1f}%")
# ========== 总结 ==========
print("\n" + "="*80)
print("📊 总体统计")
print("="*80)
print(f"\n双模型交集:")
print(f" 总体准确率: {(daily_stats['two_model_correct'].sum() / daily_stats['two_model_valid'].sum() * 100):.2f}%")
print(f" 总体覆盖率: {(daily_stats['two_model_valid'].sum() / daily_stats['total_samples'].sum() * 100):.2f}%")
print(f" 最高日准确率: {daily_stats['two_model_accuracy'].max():.1f}% ({daily_stats['two_model_accuracy'].idxmax()})")
print(f" 最低日准确率: {daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].min():.1f}% ({daily_stats[daily_stats['two_model_accuracy'] > 0]['two_model_accuracy'].idxmin()})")
print(f"\n三模型投票:")
print(f" 总体准确率: {(daily_stats['three_model_correct'].sum() / daily_stats['total_samples'].sum() * 100):.2f}%")
print(f" 覆盖率: 100%")
print(f" 最高日准确率: {daily_stats['three_model_accuracy'].max():.1f}% ({daily_stats['three_model_accuracy'].idxmax()})")
print(f" 最低日准确率: {daily_stats['three_model_accuracy'].min():.1f}% ({daily_stats['three_model_accuracy'].idxmin()})")
print("\n" + "="*80)
print("✓ 每日准确率统计完成!")
print("="*80)
if __name__ == "__main__":
main()
+406
View File
@@ -0,0 +1,406 @@
"""分析DLinear输出概率值的偏向性及其对集成策略的影响"""
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 data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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("DLinear输出概率值分布分析 - 以0.1阶梯统计")
print("="*80)
# ========== 加载模型 ==========
print("\n[1/4] 加载模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear
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)
# 加载N-BEATS
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)
# 加载TTM-R2
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(f"✓ 三个模型已加载")
# ========== 获取数据 ==========
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("\n[3/4] 预处理数据...")
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]
y_nbeats = y_nbeats[:min_samples]
# ========== 预测并获取DLinear概率值 ==========
print("\n[4/4] 执行预测并分析DLinear输出概率...")
# 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())) # sigmoid概率
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_valid = two_model_agree_up | two_model_agree_down
# 三模型多数投票
vote_sum = dlinear_preds + nbeats_preds + ttm_preds
three_model_preds = np.where(vote_sum >= 2, 1, 0)
# 真实标签
true_labels = y_dlinear
print(f"✓ 预测完成,开始分析DLinear概率分布...")
# ========== 按0.1阶梯分析 ==========
print("\n" + "="*80)
print("📊 DLinear输出概率分布分析(0.1阶梯)")
print("="*80)
# 定义概率区间
prob_bins = np.arange(0, 1.1, 0.1)
prob_labels = [f"{i/10:.1f}-{(i+1)/10:.1f}" for i in range(10)]
# 为每个样本分配到概率区间
prob_bin_indices = np.digitize(dlinear_probs, prob_bins) - 1
prob_bin_indices = np.clip(prob_bin_indices, 0, 9) # 确保在0-9范围内
# 初始化统计
stats = []
for i, bin_label in enumerate(prob_labels):
mask = prob_bin_indices == i
count = mask.sum()
if count == 0:
continue
# 该区间的真实标签
bin_true = true_labels[mask]
# 该区间DLinear的预测
bin_dlinear_pred = dlinear_preds[mask]
# 该区间在双模型交集中的表现
bin_two_model_valid = two_model_valid[mask]
bin_two_model_pred = np.full(count, -1)
bin_two_model_pred[two_model_agree_up[mask]] = 1
bin_two_model_pred[two_model_agree_down[mask]] = 0
# 该区间在三模型投票中的表现
bin_three_model_pred = three_model_preds[mask]
# 计算统计
dlinear_acc = (bin_dlinear_pred == bin_true).mean() * 100 if count > 0 else 0
two_model_valid_count = bin_two_model_valid.sum()
two_model_acc = ((bin_two_model_pred == bin_true) & (bin_two_model_pred != -1)).sum() / two_model_valid_count * 100 if two_model_valid_count > 0 else 0
two_model_coverage = two_model_valid_count / count * 100
three_model_acc = (bin_three_model_pred == bin_true).mean() * 100 if count > 0 else 0
# 平均概率值
avg_prob = dlinear_probs[mask].mean()
# 预测倾向
pred_up = bin_dlinear_pred.mean() * 100
stats.append({
'bin': bin_label,
'count': count,
'avg_prob': avg_prob,
'pred_up_pct': pred_up,
'dlinear_acc': dlinear_acc,
'two_model_acc': two_model_acc,
'two_model_cov': two_model_coverage,
'three_model_acc': three_model_acc
})
# 转换为DataFrame
stats_df = pd.DataFrame(stats)
# 显示统计表
print("\n概率区间分析:")
print(f"{'区间':<10} {'样本数':>6} {'平均概率':>8} {'预测↑%':>7} "
f"{'DL准确率':>9} {'双模准确率':>10} {'双模覆盖':>8} {'三模准确率':>10}")
print("-" * 95)
for _, row in stats_df.iterrows():
# 根据准确率添加标记
dl_mark = "🔥" if row['dlinear_acc'] >= 70 else ("✅" if row['dlinear_acc'] >= 60 else ("⚠️" if row['dlinear_acc'] < 45 else ""))
two_mark = "🔥" if row['two_model_acc'] >= 70 else ("✅" if row['two_model_acc'] >= 60 else ("⚠️" if row['two_model_acc'] < 45 else ""))
three_mark = "🔥" if row['three_model_acc'] >= 70 else ("✅" if row['three_model_acc'] >= 60 else ("⚠️" if row['three_model_acc'] < 45 else ""))
print(f"{row['bin']:<10} {row['count']:>6} {row['avg_prob']:>8.3f} {row['pred_up_pct']:>6.1f}% "
f"{row['dlinear_acc']:>8.1f}{dl_mark} "
f"{row['two_model_acc']:>9.1f}{two_mark} "
f"{row['two_model_cov']:>7.1f}% "
f"{row['three_model_acc']:>9.1f}{three_mark}")
# ========== 关键发现 ==========
print("\n" + "="*80)
print("🔍 关键发现")
print("="*80)
# 极端概率区间(高置信度)
high_conf_up = stats_df[stats_df['avg_prob'] >= 0.65]
high_conf_down = stats_df[stats_df['avg_prob'] <= 0.35]
print(f"\n📈 高置信度看涨区间 (概率≥0.65):")
if len(high_conf_up) > 0:
total_samples = high_conf_up['count'].sum()
avg_dl_acc = (high_conf_up['dlinear_acc'] * high_conf_up['count']).sum() / total_samples
avg_two_acc = (high_conf_up['two_model_acc'] * high_conf_up['count']).sum() / total_samples
avg_three_acc = (high_conf_up['three_model_acc'] * high_conf_up['count']).sum() / total_samples
print(f" 样本数: {total_samples} ({total_samples/len(dlinear_probs)*100:.1f}%)")
print(f" DLinear平均准确率: {avg_dl_acc:.2f}%")
print(f" 双模型平均准确率: {avg_two_acc:.2f}%")
print(f" 三模型平均准确率: {avg_three_acc:.2f}%")
else:
print(" 无数据")
print(f"\n📉 高置信度看跌区间 (概率≤0.35):")
if len(high_conf_down) > 0:
total_samples = high_conf_down['count'].sum()
avg_dl_acc = (high_conf_down['dlinear_acc'] * high_conf_down['count']).sum() / total_samples
avg_two_acc = (high_conf_down['two_model_acc'] * high_conf_down['count']).sum() / total_samples
avg_three_acc = (high_conf_down['three_model_acc'] * high_conf_down['count']).sum() / total_samples
print(f" 样本数: {total_samples} ({total_samples/len(dlinear_probs)*100:.1f}%)")
print(f" DLinear平均准确率: {avg_dl_acc:.2f}%")
print(f" 双模型平均准确率: {avg_two_acc:.2f}%")
print(f" 三模型平均准确率: {avg_three_acc:.2f}%")
else:
print(" 无数据")
# 中等置信度区间
mid_conf = stats_df[(stats_df['avg_prob'] > 0.45) & (stats_df['avg_prob'] < 0.55)]
print(f"\n⚖️ 中等置信度区间 (0.45-0.55):")
if len(mid_conf) > 0:
total_samples = mid_conf['count'].sum()
avg_dl_acc = (mid_conf['dlinear_acc'] * mid_conf['count']).sum() / total_samples
avg_two_acc = (mid_conf['two_model_acc'] * mid_conf['count']).sum() / total_samples if total_samples > 0 else 0
avg_three_acc = (mid_conf['three_model_acc'] * mid_conf['count']).sum() / total_samples
print(f" 样本数: {total_samples} ({total_samples/len(dlinear_probs)*100:.1f}%)")
print(f" DLinear平均准确率: {avg_dl_acc:.2f}%")
print(f" 双模型平均准确率: {avg_two_acc:.2f}%")
print(f" 三模型平均准确率: {avg_three_acc:.2f}%")
print(f" ⚠️ 建议: 低置信度区间,考虑弃权或减仓")
else:
print(" 无数据")
# 找出表现最好的概率区间
best_bin = stats_df.loc[stats_df['dlinear_acc'].idxmax()]
worst_bin = stats_df.loc[stats_df['dlinear_acc'].idxmin()]
print(f"\n🏆 DLinear表现最佳区间:")
print(f" 区间: {best_bin['bin']}")
print(f" 准确率: {best_bin['dlinear_acc']:.1f}%")
print(f" 样本数: {best_bin['count']}")
print(f"\n💀 DLinear表现最差区间:")
print(f" 区间: {worst_bin['bin']}")
print(f" 准确率: {worst_bin['dlinear_acc']:.1f}%")
print(f" 样本数: {worst_bin['count']}")
# ========== 概率分布可视化 ==========
print("\n" + "="*80)
print("📊 DLinear概率分布直方图")
print("="*80)
print("\n样本数分布:")
max_count = stats_df['count'].max()
for _, row in stats_df.iterrows():
bar_length = int(row['count'] / max_count * 40)
bar = "█" * bar_length
print(f"{row['bin']:<10} {bar} {row['count']}")
print("\n" + "="*80)
print("✓ DLinear概率分布分析完成!")
print("="*80)
if __name__ == "__main__":
main()
+395
View File
@@ -0,0 +1,395 @@
"""四模型集成每日准确率分析 - 多时间段测试(2019年1月、2020年1月、2026年1月)"""
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 data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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 predict_period(period_name, start_date, end_date, dlinear_model, nbeats_model, ttm_model, timesfm_model,
dlinear_preprocessor, nbeats_preprocessor, device):
"""预测单个时间段的数据"""
print(f"\n{'='*80}")
print(f"📅 {period_name} ({start_date} 至 {end_date})")
print(f"{'='*80}")
# 获取数据
fetcher = BinanceDataFetcher()
df = fetcher.fetch_ohlcv('BTC', '1h', start_date=start_date, end_date=end_date, use_cache=False)
print(f"✓ 数据: {len(df)} 行")
# 预处理
X_dlinear, y_dlinear, df_dlinear = dlinear_preprocessor.preprocess(df, fit=False)
X_nbeats, y_nbeats, df_nbeats = nbeats_preprocessor.preprocess(df, 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]
print(f"✓ 样本数: {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))
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten()
# 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['close'].values
all_open_prices = df['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)
# TimesFM预测
timesfm_preds_list = []
for i in range(min_samples):
start_idx = i
end_idx = start_idx + 512
if end_idx > len(all_prices):
context_prices = all_prices[-512:]
current_open_price = all_open_prices[min(168 + i, len(all_open_prices) - 1)]
else:
context_prices = all_prices[start_idx:end_idx]
current_open_price = all_open_prices[end_idx] if end_idx < len(all_open_prices) else all_open_prices[-1]
try:
point_forecast, _ = timesfm_model.forecast(horizon=1, inputs=[context_prices])
pred_price = point_forecast[0, 0]
timesfm_preds_list.append(1 if pred_price > current_open_price else 0)
except:
timesfm_preds_list.append(np.random.randint(0, 2))
timesfm_preds = np.array(timesfm_preds_list)
# 集成策略
true_labels = y_dlinear
# 双模型交集
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_3 = dlinear_preds + nbeats_preds + ttm_preds
three_model_preds = np.where(vote_sum_3 >= 2, 1, 0)
# 四模型投票
vote_sum_4 = dlinear_preds + nbeats_preds + ttm_preds + timesfm_preds
four_model_preds = np.where(vote_sum_4 >= 3, 1, 0)
# 构建每日结果DataFrame
timestamps = df['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
results_df = pd.DataFrame({
'date': dates,
'timestamp_et': timestamps_et,
'true_label': true_labels,
'dlinear_pred': dlinear_preds,
'nbeats_pred': nbeats_preds,
'ttm_pred': ttm_preds,
'timesfm_pred': timesfm_preds,
'two_model_pred': two_model_preds,
'three_model_pred': three_model_preds,
'four_model_pred': four_model_preds,
})
# 按天统计
daily_stats = results_df.groupby('date').agg({
'true_label': 'count',
'dlinear_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
'nbeats_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
'ttm_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
'timesfm_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
'two_model_pred': lambda x: ((x == results_df.loc[x.index, 'true_label']) & (x != -1)).sum(),
'three_model_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
'four_model_pred': lambda x: (x == results_df.loc[x.index, 'true_label']).sum(),
})
daily_stats.columns = ['total', 'dlinear_correct', 'nbeats_correct', 'ttm_correct',
'timesfm_correct', 'two_model_correct', 'three_model_correct', 'four_model_correct']
# 计算每天的有效预测数(双模型)
two_model_valid = results_df.groupby('date')['two_model_pred'].apply(lambda x: (x != -1).sum())
daily_stats['two_model_valid'] = two_model_valid
# 计算准确率
daily_stats['dlinear_acc'] = daily_stats['dlinear_correct'] / daily_stats['total'] * 100
daily_stats['nbeats_acc'] = daily_stats['nbeats_correct'] / daily_stats['total'] * 100
daily_stats['ttm_acc'] = daily_stats['ttm_correct'] / daily_stats['total'] * 100
daily_stats['timesfm_acc'] = daily_stats['timesfm_correct'] / daily_stats['total'] * 100
daily_stats['two_model_acc'] = daily_stats['two_model_correct'] / daily_stats['two_model_valid'] * 100
daily_stats['three_model_acc'] = daily_stats['three_model_correct'] / daily_stats['total'] * 100
daily_stats['four_model_acc'] = daily_stats['four_model_correct'] / daily_stats['total'] * 100
daily_stats['two_model_acc'] = daily_stats['two_model_acc'].fillna(0)
return daily_stats, results_df
def main():
print("="*80)
print("四模型集成每日准确率分析 - 多时间段测试")
print("测试时段: 2019年1月、2020年1月、2026年1月")
print("="*80)
# ========== 加载模型 ==========
print("\n[1/2] 加载模型...")
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, weights_only=False
)
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, weights_only=False
)
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()
import timesfm
timesfm_model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
"google/timesfm-2.5-200m-pytorch",
torch_compile=False
)
timesfm_model.compile(
timesfm.ForecastConfig(
max_context=512,
max_horizon=1,
normalize_inputs=True,
use_continuous_quantile_head=True,
force_flip_invariance=True,
infer_is_positive=True,
fix_quantile_crossing=True,
)
)
print("✓ 四个模型已加载 (DLinear, N-BEATS, TTM-R2, TimesFM)")
# ========== 多时间段预测 ==========
print("\n[2/2] 多时间段预测...")
periods = [
("2019年1月", "2019-01-01", "2019-01-31"),
("2020年1月", "2020-01-01", "2020-01-31"),
("2026年1月", "2026-01-01", "2026-01-31"),
]
all_period_stats = {}
for period_name, start_date, end_date in periods:
daily_stats, results_df = predict_period(
period_name, start_date, end_date,
dlinear_model, nbeats_model, ttm_model, timesfm_model,
dlinear_preprocessor, nbeats_preprocessor, device
)
all_period_stats[period_name] = daily_stats
# 显示每日准确率
print(f"\n每日准确率明细:")
print(f"{'日期':<12} {'样本':>4} {'DLinear':>8} {'N-BEATS':>8} {'TTM-R2':>8} {'TimesFM':>8} {'双模型':>8} {'三模型':>8} {'四模型':>8}")
print("-" * 95)
for date, row in daily_stats.iterrows():
print(f"{date} {row['total']:>4.0f} "
f"{row['dlinear_acc']:>7.1f}% "
f"{row['nbeats_acc']:>7.1f}% "
f"{row['ttm_acc']:>7.1f}% "
f"{row['timesfm_acc']:>7.1f}% "
f"{row['two_model_acc']:>7.1f}% "
f"{row['three_model_acc']:>7.1f}% "
f"{row['four_model_acc']:>7.1f}%")
# 统计总体
print(f"\n{period_name} 总体统计:")
print(f" DLinear: {daily_stats['dlinear_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
print(f" N-BEATS: {daily_stats['nbeats_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
print(f" TTM-R2: {daily_stats['ttm_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
print(f" TimesFM: {daily_stats['timesfm_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
print(f" 双模型: {daily_stats['two_model_correct'].sum() / daily_stats['two_model_valid'].sum() * 100:.2f}% (覆盖率: {daily_stats['two_model_valid'].sum() / daily_stats['total'].sum() * 100:.1f}%)")
print(f" 三模型: {daily_stats['three_model_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
print(f" 四模型: {daily_stats['four_model_correct'].sum() / daily_stats['total'].sum() * 100:.2f}%")
# ========== 跨时期对比 ==========
print("\n" + "="*80)
print("📊 跨时期对比总结")
print("="*80)
print(f"\n{'时期':<12} {'DLinear':>9} {'N-BEATS':>9} {'TTM-R2':>9} {'TimesFM':>9} {'双模型':>9} {'三模型':>9} {'四模型':>9}")
print("-" * 95)
for period_name in ["2019年1月", "2020年1月", "2026年1月"]:
stats = all_period_stats[period_name]
print(f"{period_name:<12} "
f"{stats['dlinear_correct'].sum() / stats['total'].sum() * 100:>8.2f}% "
f"{stats['nbeats_correct'].sum() / stats['total'].sum() * 100:>8.2f}% "
f"{stats['ttm_correct'].sum() / stats['total'].sum() * 100:>8.2f}% "
f"{stats['timesfm_correct'].sum() / stats['total'].sum() * 100:>8.2f}% "
f"{stats['two_model_correct'].sum() / stats['two_model_valid'].sum() * 100:>8.2f}% "
f"{stats['three_model_correct'].sum() / stats['total'].sum() * 100:>8.2f}% "
f"{stats['four_model_correct'].sum() / stats['total'].sum() * 100:>8.2f}%")
print("\n" + "="*80)
print("✓ 多时间段每日准确率分析完成!")
print("="*80)
if __name__ == "__main__":
main()
+454
View File
@@ -0,0 +1,454 @@
"""五模型集成预测:DLinear + N-BEATS + TTM-R2 + TimesFM 2.5 + CatBoost"""
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 catboost import CatBoostClassifier
from data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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 add_catboost_features(df):
"""为CatBoost添加丰富的技术指标特征"""
df = df.copy()
# 基础价格特征
df['price_change'] = df['close'] - df['open']
df['price_change_pct'] = (df['close'] - df['open']) / df['open']
df['high_low_range'] = df['high'] - df['low']
df['high_low_range_pct'] = (df['high'] - df['low']) / df['low']
# 移动平均线
df['sma_7'] = ta.sma(df['close'], length=7)
df['sma_25'] = ta.sma(df['close'], length=25)
df['sma_99'] = ta.sma(df['close'], length=99)
df['ema_12'] = ta.ema(df['close'], length=12)
df['ema_26'] = ta.ema(df['close'], length=26)
# RSI
df['rsi_14'] = ta.rsi(df['close'], length=14)
df['rsi_7'] = ta.rsi(df['close'], length=7)
# MACD
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]
# 布林带
bbands = ta.bbands(df['close'], length=20)
if bbands is not None and not bbands.empty:
df['bb_upper'] = bbands.iloc[:, 0]
df['bb_mid'] = bbands.iloc[:, 1]
df['bb_lower'] = bbands.iloc[:, 2]
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_mid']
df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
# ATR
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
# 成交量特征
df['volume_sma_7'] = ta.sma(df['volume'], length=7)
df['volume_ratio'] = df['volume'] / df['volume_sma_7']
# OBV
df['obv'] = ta.obv(df['close'], df['volume'])
# Stochastic
stoch = ta.stoch(df['high'], df['low'], df['close'])
if stoch is not None and not stoch.empty:
df['stoch_k'] = stoch.iloc[:, 0]
df['stoch_d'] = stoch.iloc[:, 1]
# ADX
adx = ta.adx(df['high'], df['low'], df['close'])
if adx is not None and not adx.empty:
df['adx'] = adx.iloc[:, 0]
# 价格动量
df['momentum_1'] = df['close'].pct_change(1)
df['momentum_3'] = df['close'].pct_change(3)
df['momentum_7'] = df['close'].pct_change(7)
return df
def main():
print("="*80)
print("五模型集成预测:DLinear + N-BEATS + TTM-R2 + TimesFM + CatBoost")
print("="*80)
# ========== 步骤1: 加载PyTorch模型 ==========
print("\n[1/8] 加载DLinear和N-BEATS模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear
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)
print(f"✓ DLinear已加载")
# 加载N-BEATS
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)
print(f"✓ N-BEATS已加载")
# ========== 步骤2: 加载TTM-R2 ==========
print("\n[2/8] 加载TTM-R2零样本模型...")
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(f"✓ TTM-R2已加载")
# ========== 步骤3: 加载TimesFM 2.5 ==========
print("\n[3/8] 加载TimesFM 2.5模型...")
import timesfm
timesfm_model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
"google/timesfm-2.5-200m-pytorch",
torch_compile=False
)
timesfm_model.compile(
timesfm.ForecastConfig(
max_context=512,
max_horizon=1,
normalize_inputs=True,
use_continuous_quantile_head=True,
force_flip_invariance=True,
infer_is_positive=True,
fix_quantile_crossing=True,
)
)
print(f"✓ TimesFM 2.5已加载")
# ========== 步骤4: 加载CatBoost ==========
print("\n[4/8] 加载CatBoost模型...")
catboost_model = CatBoostClassifier()
catboost_model.load_model('./models/checkpoints/catboost_classification_2026/catboost_model.cbm')
with open('./models/checkpoints/catboost_classification_2026/metadata.pkl', 'rb') as f:
catboost_metadata = pickle.load(f)
catboost_feature_cols = catboost_metadata['feature_cols']
print(f"✓ CatBoost已加载 (特征数: {len(catboost_feature_cols)})")
# ========== 步骤5: 获取测试数据 ==========
print("\n[5/8] 获取2026年1月测试数据...")
fetcher = BinanceDataFetcher()
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2026-01-01',
end_date='2026-01-31', use_cache=False)
print(f"✓ 数据获取完成: {len(df_2026)} 行")
# ========== 步骤6: 预处理 ==========
print("\n[6/8] 预处理数据...")
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)
# CatBoost特征工程
df_catboost = add_catboost_features(df_2026)
df_catboost = df_catboost.dropna().reset_index(drop=True)
X_catboost = df_catboost[catboost_feature_cols]
# 确保样本数量一致
min_samples = min(len(X_dlinear), len(X_nbeats), len(X_catboost))
X_dlinear = X_dlinear[:min_samples]
y_dlinear = y_dlinear[:min_samples]
X_nbeats = X_nbeats[:min_samples]
X_catboost = X_catboost.iloc[:min_samples]
print(f"✓ 对齐样本数: {min_samples}")
# ========== 步骤7: 五个模型分别预测 ==========
print("\n[7/8] 五个模型分别预测...")
# 7.1 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))
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten()
print(f"✓ DLinear: 涨{(dlinear_preds==1).sum()} ({(dlinear_preds==1).sum()/len(dlinear_preds)*100:.1f}%)")
# 7.2 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)
print(f"✓ N-BEATS: 涨{(nbeats_preds==1).sum()} ({(nbeats_preds==1).sum()/len(nbeats_preds)*100:.1f}%)")
# 7.3 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)
print(f"✓ TTM-R2: 涨{(ttm_preds==1).sum()} ({(ttm_preds==1).sum()/len(ttm_preds)*100:.1f}%)")
# 7.4 TimesFM 2.5预测
timesfm_preds_list = []
for i in range(min_samples):
start_idx = i
end_idx = start_idx + 512
if end_idx > len(all_prices):
context_prices = all_prices[-512:]
current_open_price = all_open_prices[min(168 + i, len(all_open_prices) - 1)]
else:
context_prices = all_prices[start_idx:end_idx]
current_open_price = all_open_prices[end_idx] if end_idx < len(all_open_prices) else all_open_prices[-1]
try:
point_forecast, _ = timesfm_model.forecast(
horizon=1,
inputs=[context_prices],
)
pred_price = point_forecast[0, 0]
timesfm_preds_list.append(1 if pred_price > current_open_price else 0)
except:
timesfm_preds_list.append(np.random.randint(0, 2))
timesfm_preds = np.array(timesfm_preds_list)
print(f"✓ TimesFM: 涨{(timesfm_preds==1).sum()} ({(timesfm_preds==1).sum()/len(timesfm_preds)*100:.1f}%)")
# 7.5 CatBoost预测
catboost_preds = catboost_model.predict(X_catboost).astype(int)
print(f"✓ CatBoost: 涨{(catboost_preds==1).sum()} ({(catboost_preds==1).sum()/len(catboost_preds)*100:.1f}%)")
# ========== 步骤8: 集成策略评估 ==========
print("\n[8/8] 集成策略评估...")
true_labels = y_dlinear
# 策略1: 双模型交集(DLinear + N-BEATS)
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
two_model_valid = two_model_preds != -1
two_model_correct = (two_model_preds == true_labels) & two_model_valid
two_model_acc = two_model_correct.sum() / two_model_valid.sum() * 100 if two_model_valid.sum() > 0 else 0
two_model_coverage = two_model_valid.sum() / len(true_labels) * 100
# 策略2: 三模型投票(DLinear + N-BEATS + TTM-R2)
vote_sum_3 = dlinear_preds + nbeats_preds + ttm_preds
three_model_preds = np.where(vote_sum_3 >= 2, 1, 0)
three_model_acc = (three_model_preds == true_labels).mean() * 100
# 策略3: 四模型投票
vote_sum_4 = dlinear_preds + nbeats_preds + ttm_preds + timesfm_preds
four_model_preds = np.where(vote_sum_4 >= 3, 1, 0)
four_model_acc = (four_model_preds == true_labels).mean() * 100
# 策略4: 五模型强投票(≥3票)
vote_sum_5 = dlinear_preds + nbeats_preds + ttm_preds + timesfm_preds + catboost_preds
five_model_preds_strong = np.where(vote_sum_5 >= 3, 1, 0)
five_model_acc_strong = (five_model_preds_strong == true_labels).mean() * 100
# 策略5: 五模型弱投票(≥2票)
five_model_preds_weak = np.where(vote_sum_5 >= 2, 1, 0)
five_model_acc_weak = (five_model_preds_weak == true_labels).mean() * 100
# ========== 结果展示 ==========
print("\n" + "="*80)
print("📊 五模型集成策略对比结果")
print("="*80)
print(f"\n单模型性能:")
print(f" DLinear: {(dlinear_preds == true_labels).mean() * 100:.2f}%")
print(f" CatBoost: {(catboost_preds == true_labels).mean() * 100:.2f}%")
print(f" TimesFM 2.5: {(timesfm_preds == true_labels).mean() * 100:.2f}%")
print(f" TTM-R2: {(ttm_preds == true_labels).mean() * 100:.2f}%")
print(f" N-BEATS: {(nbeats_preds == true_labels).mean() * 100:.2f}%")
print(f"\n集成策略性能:")
print(f" 双模型交集 (DL+NB): {two_model_acc:.2f}% (覆盖率: {two_model_coverage:.2f}%)")
print(f" 三模型投票 (DL+NB+TTM, ≥2票): {three_model_acc:.2f}% (覆盖率: 100%)")
print(f" 四模型投票 (前4, ≥3票): {four_model_acc:.2f}% (覆盖率: 100%)")
print(f" 五模型强投票 (所有, ≥3票): {five_model_acc_strong:.2f}% (覆盖率: 100%)")
print(f" 五模型弱投票 (所有, ≥2票): {five_model_acc_weak:.2f}% (覆盖率: 100%)")
# 投票分布分析
print(f"\n五模型投票分布:")
for votes in range(6):
count = (vote_sum_5 == votes).sum()
pct = count / len(vote_sum_5) * 100
bars = "█" * int(pct / 2)
print(f" {votes}票同意: {count:3d}次 ({pct:5.1f}%) {bars}")
# 模型一致性分析
print(f"\n模型一致性分析:")
all_agree_up = (dlinear_preds == 1) & (nbeats_preds == 1) & (ttm_preds == 1) & (timesfm_preds == 1) & (catboost_preds == 1)
all_agree_down = (dlinear_preds == 0) & (nbeats_preds == 0) & (ttm_preds == 0) & (timesfm_preds == 0) & (catboost_preds == 0)
all_agree = all_agree_up | all_agree_down
print(f" 五模型完全一致: {all_agree.sum()}次 ({all_agree.sum()/len(all_agree)*100:.1f}%)")
if all_agree.sum() > 0:
all_agree_acc = ((five_model_preds_strong[all_agree] == true_labels[all_agree]).mean() * 100)
print(f" 一致时准确率: {all_agree_acc:.2f}%")
print("\n" + "="*80)
print("✓ 五模型集成预测完成!")
print("="*80)
if __name__ == "__main__":
main()
+385
View File
@@ -0,0 +1,385 @@
"""四模型集成预测:DLinear + N-BEATS + TTM-R2 + TimesFM 2.5"""
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 data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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("四模型集成预测:DLinear + N-BEATS + TTM-R2 + TimesFM 2.5")
print("="*80)
# ========== 步骤1: 加载DLinear和N-BEATS模型 ==========
print("\n[1/7] 加载DLinear和N-BEATS模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear
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)
print(f"✓ DLinear已加载")
# 加载N-BEATS
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)
print(f"✓ N-BEATS已加载")
# ========== 步骤2: 加载TTM-R2 ==========
print("\n[2/7] 加载TTM-R2零样本模型...")
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(f"✓ TTM-R2已加载 (参数量: {sum(p.numel() for p in ttm_model.parameters())})")
# ========== 步骤3: 加载TimesFM 2.5 ==========
print("\n[3/7] 加载TimesFM 2.5模型...")
import timesfm
timesfm_model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
"google/timesfm-2.5-200m-pytorch",
torch_compile=False # 推理时不使用torch.compile
)
# 配置TimesFM用于BTC 1小时预测
timesfm_model.compile(
timesfm.ForecastConfig(
max_context=512, # 使用512小时历史数据
max_horizon=1, # 预测未来1小时
normalize_inputs=True,
use_continuous_quantile_head=True,
force_flip_invariance=True,
infer_is_positive=True,
fix_quantile_crossing=True,
)
)
print(f"✓ TimesFM 2.5已加载并配置")
print(f" 上下文长度: 512小时")
print(f" 预测长度: 1小时")
# ========== 步骤4: 获取测试数据 ==========
print("\n[4/7] 获取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)} 行")
print(f"✓ 时间范围: {df_2026['timestamp'].min()} 至 {df_2026['timestamp'].max()}")
# ========== 步骤5: 预处理 ==========
print("\n[5/7] 预处理数据...")
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]
y_nbeats = y_nbeats[:min_samples]
print(f"✓ 对齐样本数: {min_samples}")
# ========== 步骤6: 四个模型分别预测 ==========
print("\n[6/7] 四个模型分别预测...")
# 6.1 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)) # sigmoid
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten() # 1=涨, 0=跌
print(f"✓ DLinear预测完成")
print(f" 预测上涨: {(dlinear_preds==1).sum()} ({(dlinear_preds==1).sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 预测下跌: {(dlinear_preds==0).sum()} ({(dlinear_preds==0).sum()/len(dlinear_preds)*100:.1f}%)")
# 6.2 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()
# 还原真实价格并使用当前K线open价格判断
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) # 1=涨, 0=跌
print(f"✓ N-BEATS预测完成 (使用当前K线open价格判断)")
print(f" 预测上涨: {(nbeats_preds==1).sum()} ({(nbeats_preds==1).sum()/len(nbeats_preds)*100:.1f}%)")
print(f" 预测下跌: {(nbeats_preds==0).sum()} ({(nbeats_preds==0).sum()/len(nbeats_preds)*100:.1f}%)")
# 6.3 TTM-R2预测
print(f"✓ 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)
print(f"✓ TTM-R2预测完成")
print(f" 预测上涨: {(ttm_preds==1).sum()} ({(ttm_preds==1).sum()/len(ttm_preds)*100:.1f}%)")
print(f" 预测下跌: {(ttm_preds==0).sum()} ({(ttm_preds==0).sum()/len(ttm_preds)*100:.1f}%)")
# 6.4 TimesFM 2.5预测
print(f"✓ TimesFM 2.5预测中...")
timesfm_preds_list = []
for i in range(min_samples):
# TimesFM使用512小时上下文
start_idx = i
end_idx = start_idx + 512
if end_idx > len(all_prices):
# 如果数据不足,使用最后512个数据点
context_prices = all_prices[-512:]
current_open_price = all_open_prices[min(168 + i, len(all_open_prices) - 1)]
else:
context_prices = all_prices[start_idx:end_idx]
current_open_price = all_open_prices[end_idx] if end_idx < len(all_open_prices) else all_open_prices[-1]
# TimesFM的forecast方法接受list of arrays作为inputs
try:
point_forecast, _ = timesfm_model.forecast(
horizon=1,
inputs=[context_prices], # list包含单个序列
)
# point_forecast形状: (1, 1) - (num_series, horizon)
pred_price = point_forecast[0, 0]
# 与当前K线open价格比较
timesfm_preds_list.append(1 if pred_price > current_open_price else 0)
except Exception as e:
print(f" ⚠️ TimesFM预测第{i}个样本失败: {e}")
# 使用随机预测作为fallback
timesfm_preds_list.append(np.random.randint(0, 2))
timesfm_preds = np.array(timesfm_preds_list)
print(f"✓ TimesFM 2.5预测完成")
print(f" 预测上涨: {(timesfm_preds==1).sum()} ({(timesfm_preds==1).sum()/len(timesfm_preds)*100:.1f}%)")
print(f" 预测下跌: {(timesfm_preds==0).sum()} ({(timesfm_preds==0).sum()/len(timesfm_preds)*100:.1f}%)")
# ========== 步骤7: 集成策略评估 ==========
print("\n[7/7] 集成策略评估...")
true_labels = y_dlinear
# 策略1: 双模型交集(DLinear + N-BEATS)
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
two_model_valid = two_model_preds != -1
two_model_correct = (two_model_preds == true_labels) & two_model_valid
two_model_acc = two_model_correct.sum() / two_model_valid.sum() * 100 if two_model_valid.sum() > 0 else 0
two_model_coverage = two_model_valid.sum() / len(true_labels) * 100
# 策略2: 三模型多数投票(DLinear + N-BEATS + TTM-R2)
vote_sum_3 = dlinear_preds + nbeats_preds + ttm_preds
three_model_preds = np.where(vote_sum_3 >= 2, 1, 0)
three_model_acc = (three_model_preds == true_labels).mean() * 100
# 策略3: 四模型多数投票(至少3个模型同意)
vote_sum_4 = dlinear_preds + nbeats_preds + ttm_preds + timesfm_preds
four_model_preds = np.where(vote_sum_4 >= 3, 1, 0)
four_model_acc = (four_model_preds == true_labels).mean() * 100
# 策略4: 四模型半数以上投票(至少2个模型同意)
four_model_preds_half = np.where(vote_sum_4 >= 2, 1, 0)
four_model_acc_half = (four_model_preds_half == true_labels).mean() * 100
# ========== 结果展示 ==========
print("\n" + "="*80)
print("📊 集成策略对比结果")
print("="*80)
print(f"\n单模型性能:")
print(f" DLinear: {(dlinear_preds == true_labels).mean() * 100:.2f}%")
print(f" N-BEATS: {(nbeats_preds == true_labels).mean() * 100:.2f}%")
print(f" TTM-R2: {(ttm_preds == true_labels).mean() * 100:.2f}%")
print(f" TimesFM 2.5: {(timesfm_preds == true_labels).mean() * 100:.2f}%")
print(f"\n集成策略性能:")
print(f" 双模型交集 (DL+NB): {two_model_acc:.2f}% (覆盖率: {two_model_coverage:.2f}%)")
print(f" 三模型投票 (DL+NB+TTM, ≥2票): {three_model_acc:.2f}% (覆盖率: 100%)")
print(f" 四模型强投票 (所有, ≥3票): {four_model_acc:.2f}% (覆盖率: 100%)")
print(f" 四模型弱投票 (所有, ≥2票): {four_model_acc_half:.2f}% (覆盖率: 100%)")
# 投票分布分析
print(f"\n四模型投票分布:")
for votes in range(5):
count = (vote_sum_4 == votes).sum()
pct = count / len(vote_sum_4) * 100
bars = "█" * int(pct / 2)
print(f" {votes}票同意: {count:3d}次 ({pct:5.1f}%) {bars}")
# 模型一致性分析
print(f"\n模型一致性分析:")
all_agree_up = (dlinear_preds == 1) & (nbeats_preds == 1) & (ttm_preds == 1) & (timesfm_preds == 1)
all_agree_down = (dlinear_preds == 0) & (nbeats_preds == 0) & (ttm_preds == 0) & (timesfm_preds == 0)
all_agree = all_agree_up | all_agree_down
print(f" 四模型完全一致: {all_agree.sum()}次 ({all_agree.sum()/len(all_agree)*100:.1f}%)")
if all_agree.sum() > 0:
all_agree_acc = ((four_model_preds[all_agree] == true_labels[all_agree]).mean() * 100)
print(f" 一致时准确率: {all_agree_acc:.2f}%")
print("\n" + "="*80)
print("✓ 四模型集成预测完成!")
print("="*80)
if __name__ == "__main__":
main()
+302
View File
@@ -0,0 +1,302 @@
"""N-BEATS + DLinear 模型集成预测 - 交集策略"""
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
# ==================== 预处理器定义 ====================
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("模型集成预测:N-BEATS + DLinear 交集策略")
print("="*80)
# ========== 步骤1: 加载两个模型 ==========
print("\n[1/5] 加载两个模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear分类模型
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)
print(f"✓ DLinear分类模型已加载 (准确率: 53.10%)")
# 加载N-BEATS回归模型
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)
print(f"✓ N-BEATS回归模型已加载 (准确率: 50.83%)")
# ========== 步骤2: 获取2026年测试数据 ==========
print("\n[2/5] 获取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()}")
# ========== 步骤3: 预处理数据 ==========
print("\n[3/5] 预处理数据...")
# DLinear预处理
X_dlinear, y_dlinear, df_dlinear = dlinear_preprocessor.preprocess(df_2026, fit=False)
# N-BEATS预处理
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]
y_nbeats = y_nbeats[:min_samples]
print(f"✓ 对齐样本数: {min_samples}")
# ========== 步骤4: 两个模型分别预测 ==========
print("\n[4/5] 两个模型分别预测...")
# 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)) # sigmoid
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten() # 1=涨, 0=跌
print(f"✓ DLinear预测完成")
print(f" 预测上涨: {(dlinear_preds==1).sum()} ({(dlinear_preds==1).sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 预测下跌: {(dlinear_preds==0).sum()} ({(dlinear_preds==0).sum()/len(dlinear_preds)*100:.1f}%)")
# 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_prices = df_nbeats['close'].iloc[168:168+len(nbeats_pred_prices)].values
nbeats_preds = (nbeats_pred_prices > current_prices).astype(int) # 1=涨, 0=跌
print(f"✓ N-BEATS预测完成")
print(f" 预测上涨: {(nbeats_preds==1).sum()} ({(nbeats_preds==1).sum()/len(nbeats_preds)*100:.1f}%)")
print(f" 预测下跌: {(nbeats_preds==0).sum()} ({(nbeats_preds==0).sum()/len(nbeats_preds)*100:.1f}%)")
# ========== 步骤5: 交集策略 ==========
print("\n[5/5] 应用交集策略...")
# 真实标签
true_labels = y_dlinear
# 策略1: 两个模型都预测上涨 → 上涨
# 策略2: 两个模型都预测下跌 → 下跌
# 策略3: 两个模型预测不一致 → 不做决策(弃权)
both_agree_up = (dlinear_preds == 1) & (nbeats_preds == 1)
both_agree_down = (dlinear_preds == 0) & (nbeats_preds == 0)
disagree = (dlinear_preds != nbeats_preds)
n_agree_up = both_agree_up.sum()
n_agree_down = both_agree_down.sum()
n_disagree = disagree.sum()
print(f"\n📊 预测一致性分析:")
print(f" 双方都预测上涨: {n_agree_up} ({n_agree_up/len(dlinear_preds)*100:.1f}%)")
print(f" 双方都预测下跌: {n_agree_down} ({n_agree_down/len(dlinear_preds)*100:.1f}%)")
print(f" 双方预测不一致: {n_disagree} ({n_disagree/len(dlinear_preds)*100:.1f}%)")
# 只在两个模型一致时做决策
agree_mask = both_agree_up | both_agree_down
ensemble_preds = np.full(len(dlinear_preds), -1) # -1表示弃权
ensemble_preds[both_agree_up] = 1
ensemble_preds[both_agree_down] = 0
# 有效预测(非弃权)
valid_mask = ensemble_preds != -1
valid_preds = ensemble_preds[valid_mask]
valid_labels = true_labels[valid_mask]
print(f"\n📊 集成策略结果:")
print(f" 有效预测数: {valid_mask.sum()} ({valid_mask.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 弃权数: {n_disagree} ({n_disagree/len(dlinear_preds)*100:.1f}%)")
if len(valid_preds) > 0:
ensemble_accuracy = (valid_preds == valid_labels).mean()
print(f"\n✨ 交集策略准确率: {ensemble_accuracy*100:.2f}%")
# 混淆矩阵
cm = confusion_matrix(valid_labels, valid_preds)
tn, fp, fn, tp = cm.ravel()
print(f"\n详细分析(仅有效预测):")
print(f" ✓ 预测涨且真涨: {tp} ({tp/len(valid_preds)*100:.1f}%)")
print(f" ✓ 预测跌且真跌: {tn} ({tn/len(valid_preds)*100:.1f}%)")
print(f" ✗ 预测涨但真跌: {fp} ({fp/len(valid_preds)*100:.1f}%)")
print(f" ✗ 预测跌但真涨: {fn} ({fn/len(valid_preds)*100:.1f}%)")
# ========== 对比分析 ==========
print("\n" + "="*80)
print("📊 性能对比")
print("="*80)
dlinear_acc = (dlinear_preds == true_labels).mean()
nbeats_acc = (nbeats_preds == true_labels).mean()
print(f"\n单模型性能:")
print(f" DLinear分类: {dlinear_acc*100:.2f}%")
print(f" N-BEATS回归: {nbeats_acc*100:.2f}%")
if len(valid_preds) > 0:
print(f"\n集成策略性能:")
print(f" 交集准确率: {ensemble_accuracy*100:.2f}%")
print(f" 覆盖率: {valid_mask.sum()/len(dlinear_preds)*100:.1f}%")
print(f" 弃权率: {n_disagree/len(dlinear_preds)*100:.1f}%")
# 性能评估
print("\n" + "="*80)
print("🎯 策略评估")
print("="*80)
if ensemble_accuracy > max(dlinear_acc, nbeats_acc):
improvement = ensemble_accuracy - max(dlinear_acc, nbeats_acc)
print(f"\n ✅ 交集策略优于单模型!")
print(f" 准确率提升: {improvement*100:.2f}个百分点")
print(f" 代价: 弃权{n_disagree}次预测 ({n_disagree/len(dlinear_preds)*100:.1f}%)")
else:
print(f"\n ⚠️ 交集策略未能提升准确率")
print(f" 可能原因: 两个模型一致时也可能都错")
# 计算如果弃权时不交易的盈利
print(f"\n💰 实际收益分析:")
print(f" 如果只在两个模型都同意时交易:")
print(f" - 交易次数: {valid_mask.sum()} ({valid_mask.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" - 准确率: {ensemble_accuracy*100:.2f}%")
print(f" - 避免交易: {n_disagree}次 (规避不确定性)")
print("\n" + "="*80)
print("✓ 集成预测分析完成!")
print("="*80)
if __name__ == "__main__":
main()
+400
View File
@@ -0,0 +1,400 @@
"""三模型集成预测 - DLinear + N-BEATS + TTM-R2 交集策略"""
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
# ==================== 预处理器定义 ====================
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("三模型集成预测:DLinear + N-BEATS + TTM-R2 交集策略")
print("="*80)
# ========== 步骤1: 加载三个模型 ==========
print("\n[1/6] 加载三个模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 1. 加载DLinear分类模型
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)
print(f"✓ DLinear分类模型已加载 (准确率: 53.10%)")
# 2. 加载N-BEATS回归模型
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)
print(f"✓ N-BEATS回归模型已加载 (准确率: 50.83%)")
# 3. 加载TTM-R2模型
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(f"✓ TTM-R2模型已加载 (准确率: 37.50%)")
# ========== 步骤2: 获取2026年测试数据 ==========
print("\n[2/6] 获取2026年测试数据...")
fetcher = BinanceDataFetcher()
try:
df_2026 = fetcher.fetch_ohlcv('BTC', '1h', start_date='2026-01-01',
end_date='2026-02-02', 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()}")
# ========== 步骤3: 预处理数据 ==========
print("\n[3/6] 预处理数据...")
# DLinear预处理
X_dlinear, y_dlinear, df_dlinear = dlinear_preprocessor.preprocess(df_2026, fit=False)
# N-BEATS预处理
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]
y_nbeats = y_nbeats[:min_samples]
print(f"✓ 对齐样本数: {min_samples}")
# ========== 步骤4: 三个模型分别预测 ==========
print("\n[4/6] 三个模型分别预测...")
# 4.1 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)) # sigmoid
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten() # 1=涨, 0=跌
print(f"✓ DLinear预测完成")
print(f" 预测上涨: {(dlinear_preds==1).sum()} ({(dlinear_preds==1).sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 预测下跌: {(dlinear_preds==0).sum()} ({(dlinear_preds==0).sum()/len(dlinear_preds)*100:.1f}%)")
# 4.2 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)
# 【修改】获取当前K线的open价格(未来可交易价格)用于判断方向
# 第i个样本对应的是第168+i根K线,其open价格是真正可以交易的价格
current_open_prices = df_nbeats['open'].iloc[168:168+len(nbeats_pred_prices)].values
nbeats_preds = (nbeats_pred_prices > current_open_prices).astype(int) # 1=涨, 0=跌
print(f"✓ N-BEATS预测完成 (使用当前K线open价格判断)")
print(f" 预测上涨: {(nbeats_preds==1).sum()} ({(nbeats_preds==1).sum()/len(nbeats_preds)*100:.1f}%)")
print(f" 预测下跌: {(nbeats_preds==0).sum()} ({(nbeats_preds==0).sum()/len(nbeats_preds)*100:.1f}%)")
# 4.3 TTM-R2预测
print(f"✓ TTM-R2预测中...")
# TTM-R2需要特定格式: (batch_size, context_length, num_features)
context_length = 512
ttm_preds_list = []
# 从df_2026获取价格数据
all_prices = df_2026['close'].values
all_open_prices = df_2026['open'].values
# 为每个样本创建预测
for i in range(min_samples):
# 计算当前样本在原始数据中的位置
# DLinear的lookback是168,所以第i个样本对应df_2026的第168+i根K线
start_idx = i
end_idx = start_idx + context_length
if end_idx > len(all_prices):
# 如果超出范围,使用最后512个价格
context_data = all_prices[-context_length:].reshape(1, -1, 1)
# 【修改】使用对应K线的open价格作为当前可交易价格
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)
# 【修改】使用第end_idx根K线的open价格(即当前K线的开盘价)
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]
# 【修改】判断方向: 预测价格 vs 当前K线open价格
ttm_preds_list.append(1 if pred_price > current_open_price else 0)
ttm_preds = np.array(ttm_preds_list)
print(f"✓ TTM-R2预测完成 (使用当前K线open价格判断)")
print(f" 预测上涨: {(ttm_preds==1).sum()} ({(ttm_preds==1).sum()/len(ttm_preds)*100:.1f}%)")
print(f" 预测下跌: {(ttm_preds==0).sum()} ({(ttm_preds==0).sum()/len(ttm_preds)*100:.1f}%)")
# ========== 步骤5: 三模型多数投票策略(两方相同) ==========
print("\n[5/6] 应用三模型多数投票策略(两方相同)...")
# 真实标签
true_labels = y_dlinear
# 多数投票:至少两个模型预测上涨 → 上涨,至少两个预测下跌 → 下跌
vote_sum = dlinear_preds + nbeats_preds + ttm_preds
# vote_sum >= 2 表示至少两个模型预测上涨(1)
# vote_sum <= 1 表示至少两个模型预测下跌(0)
majority_up = vote_sum >= 2
majority_down = vote_sum <= 1
# 统计各种情况
all_agree_up = (dlinear_preds == 1) & (nbeats_preds == 1) & (ttm_preds == 1)
all_agree_down = (dlinear_preds == 0) & (nbeats_preds == 0) & (ttm_preds == 0)
# 恰好两方同意(不包括三方都同意)
exactly_two_up = majority_up & ~all_agree_up
exactly_two_down = majority_down & ~all_agree_down
n_all_agree_up = all_agree_up.sum()
n_all_agree_down = all_agree_down.sum()
n_exactly_two_up = exactly_two_up.sum()
n_exactly_two_down = exactly_two_down.sum()
print(f"\n📊 预测投票分析:")
print(f" 三方都预测上涨: {n_all_agree_up} ({n_all_agree_up/len(dlinear_preds)*100:.1f}%)")
print(f" 三方都预测下跌: {n_all_agree_down} ({n_all_agree_down/len(dlinear_preds)*100:.1f}%)")
print(f" 恰好两方预测上涨: {n_exactly_two_up} ({n_exactly_two_up/len(dlinear_preds)*100:.1f}%)")
print(f" 恰好两方预测下跌: {n_exactly_two_down} ({n_exactly_two_down/len(dlinear_preds)*100:.1f}%)")
# 详细显示是哪两方同意
dl_nb_up = (dlinear_preds == 1) & (nbeats_preds == 1) & (ttm_preds == 0)
dl_ttm_up = (dlinear_preds == 1) & (nbeats_preds == 0) & (ttm_preds == 1)
nb_ttm_up = (dlinear_preds == 0) & (nbeats_preds == 1) & (ttm_preds == 1)
dl_nb_down = (dlinear_preds == 0) & (nbeats_preds == 0) & (ttm_preds == 1)
dl_ttm_down = (dlinear_preds == 0) & (nbeats_preds == 1) & (ttm_preds == 0)
nb_ttm_down = (dlinear_preds == 1) & (nbeats_preds == 0) & (ttm_preds == 0)
print(f"\n 详细组合:")
print(f" DLinear+NBEATS同意看涨: {dl_nb_up.sum()} ({dl_nb_up.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" DLinear+TTM同意看涨: {dl_ttm_up.sum()} ({dl_ttm_up.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" NBEATS+TTM同意看涨: {nb_ttm_up.sum()} ({nb_ttm_up.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" DLinear+NBEATS同意看跌: {dl_nb_down.sum()} ({dl_nb_down.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" DLinear+TTM同意看跌: {dl_ttm_down.sum()} ({dl_ttm_down.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" NBEATS+TTM同意看跌: {nb_ttm_down.sum()} ({nb_ttm_down.sum()/len(dlinear_preds)*100:.1f}%)")
# 多数投票结果
ensemble_preds = np.where(majority_up, 1, 0)
print(f"\n📊 三模型多数投票策略结果:")
print(f" 最终预测上涨: {(ensemble_preds==1).sum()} ({(ensemble_preds==1).sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 最终预测下跌: {(ensemble_preds==0).sum()} ({(ensemble_preds==0).sum()/len(dlinear_preds)*100:.1f}%)")
print(f" 覆盖率: 100% (多数投票无弃权)")
# ========== 步骤6: 性能对比 ==========
print("\n[6/6] 性能对比分析...")
# 计算多数投票准确率
ensemble_accuracy = (ensemble_preds == true_labels).mean()
print(f"\n✨ 三模型多数投票准确率: {ensemble_accuracy*100:.2f}%")
# 混淆矩阵
cm = confusion_matrix(true_labels, ensemble_preds)
tn, fp, fn, tp = cm.ravel()
print(f"\n详细分析:")
print(f" ✓ 预测涨且真涨: {tp} ({tp/len(ensemble_preds)*100:.1f}%)")
print(f" ✓ 预测跌且真跌: {tn} ({tn/len(ensemble_preds)*100:.1f}%)")
print(f" ✗ 预测涨但真跌: {fp} ({fp/len(ensemble_preds)*100:.1f}%)")
print(f" ✗ 预测跌但真涨: {fn} ({fn/len(ensemble_preds)*100:.1f}%)")
# 对比分析
print("\n" + "="*80)
print("📊 性能对比")
print("="*80)
dlinear_acc = (dlinear_preds == true_labels).mean()
nbeats_acc = (nbeats_preds == true_labels).mean()
ttm_acc = (ttm_preds == true_labels).mean()
print(f"\n单模型性能:")
print(f" DLinear分类: {dlinear_acc*100:.2f}%")
print(f" N-BEATS回归: {nbeats_acc*100:.2f}%")
print(f" TTM-R2零样本: {ttm_acc*100:.2f}%")
# 两模型交集(DLinear + N-BEATS)
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
two_model_valid = two_model_preds != -1
two_model_acc = (two_model_preds[two_model_valid] == true_labels[two_model_valid]).mean()
print(f"\n集成策略性能:")
print(f" 双模型交集 (DLinear+NBEATS): {two_model_acc*100:.2f}% (覆盖率: {two_model_valid.sum()/len(dlinear_preds)*100:.1f}%)")
if len(valid_preds) > 0:
print(f" 三模型交集 (ALL): {ensemble_accuracy*100:.2f}% (覆盖率: {valid_mask.sum()/len(dlinear_preds)*100:.1f}%)")
# 性能评估
print("\n" + "="*80)
print("🎯 策略评估")
print("="*80)
best_single = max(dlinear_acc, nbeats_acc, ttm_acc)
if ensemble_accuracy > best_single:
improvement = ensemble_accuracy - best_single
print(f"\n ✅ 三模型交集优于所有单模型!")
print(f" 准确率提升: {improvement*100:.2f}个百分点")
print(f" 代价: 弃权{(~valid_mask).sum()}次预测 ({(~valid_mask).sum()/len(dlinear_preds)*100:.1f}%)")
elif ensemble_accuracy > two_model_acc:
improvement = ensemble_accuracy - two_model_acc
print(f"\n ✅ 三模型交集优于双模型交集!")
print(f" 准确率提升: {improvement*100:.2f}个百分点")
print(f" 但覆盖率下降: {(two_model_valid.sum() - valid_mask.sum())/len(dlinear_preds)*100:.1f}%")
else:
print(f"\n ⚠️ 三模型交集未能提升准确率")
print(f" 可能原因: TTM-R2表现太差({ttm_acc*100:.2f}%),拖累了整体性能")
print(f" 建议: 使用双模型交集(DLinear+NBEATS)")
# 实际收益分析
print(f"\n💰 实际收益分析:")
print(f" 如果只在三个模型都同意时交易:")
print(f" - 交易次数: {valid_mask.sum()} ({valid_mask.sum()/len(dlinear_preds)*100:.1f}%)")
print(f" - 准确率: {ensemble_accuracy*100:.2f}%")
print(f" - 避免交易: {(~valid_mask).sum()}次 (规避不确定性)")
print("\n" + "="*80)
print("✓ 三模型集成预测分析完成!")
print("="*80)
if __name__ == "__main__":
main()
+295
View File
@@ -0,0 +1,295 @@
"""将Google TimesFM 2.5模型导出为ONNX格式 - 使用正确的API"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import torch
import numpy as np
def export_timesfm_to_onnx():
print("="*80)
print("导出TimesFM 2.5 (200M) 模型为ONNX格式")
print("="*80)
# ========== 步骤1: 加载TimesFM模型 ==========
print("\n[1/6] 加载TimesFM 2.5模型...")
try:
import timesfm
# 使用正确的from_pretrained方法加载模型
print(" 从HuggingFace加载预训练模型...")
print(" 模型ID: google/timesfm-2.5-200m-pytorch")
model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
"google/timesfm-2.5-200m-pytorch",
torch_compile=False # 导出ONNX时不使用torch.compile
)
print("✓ TimesFM 2.5模型已加载")
except ImportError as e:
print(f"✗ TimesFM未安装或导入失败: {e}")
print(" 请运行: pip install git+https://github.com/google-research/timesfm.git")
return
except Exception as e:
print(f"✗ 加载模型失败: {e}")
import traceback
traceback.print_exc()
return
# ========== 步骤2: 编译模型配置 ==========
print("\n[2/6] 配置模型...")
try:
# 配置模型
model.compile(
timesfm.ForecastConfig(
max_context=512, # BTC历史数据512小时
max_horizon=24, # 预测未来24小时
normalize_inputs=True,
use_continuous_quantile_head=True,
force_flip_invariance=True,
infer_is_positive=True,
fix_quantile_crossing=True,
)
)
print("✓ 模型配置完成")
print(f" 最大上下文长度: 512")
print(f" 最大预测长度: 24")
print(f" 归一化输入: True")
print(f" 使用分位数预测: True")
except Exception as e:
print(f"✗ 配置失败: {e}")
import traceback
traceback.print_exc()
return
# ========== 步骤3: 测试模型 ==========
print("\n[3/6] 测试模型推理...")
try:
# 创建测试数据
test_input = [
np.linspace(85000, 90000, 512), # 模拟BTC价格从85k到90k
np.sin(np.linspace(0, 20, 512)) * 1000 + 87000, # 模拟震荡价格
]
# 测试推理
point_forecast, quantile_forecast = model.forecast(
horizon=24,
inputs=test_input,
)
print("✓ 模型推理成功")
print(f" 点预测形状: {point_forecast.shape}") # (2, 24)
print(f" 分位数预测形状: {quantile_forecast.shape}") # (2, 24, 11)
except Exception as e:
print(f"⚠️ 模型测试失败: {e}")
import traceback
traceback.print_exc()
print(" 继续尝试导出...")
# ========== 步骤4: 提取核心模型并导出ONNX ==========
print("\n[4/6] 提取核心模型...")
try:
# TimesFM的核心模型在model.model中
core_model = model.model
core_model.eval()
print("✓ 核心模型已提取")
print(f" 模型类型: {type(core_model)}")
# 创建ONNX导出包装器
class TimesFMONNXWrapper(torch.nn.Module):
"""TimesFM ONNX导出包装器"""
def __init__(self, timesfm_core_model):
super().__init__()
self.model = timesfm_core_model
def forward(self, inputs, masks=None):
"""
Args:
inputs: (batch_size, context_length) - 输入时间序列
masks: (batch_size, context_length) - 可选mask,1=有效,0=无效
Returns:
point_forecast: (batch_size, horizon_length) - 点预测
"""
# 如果没有提供masks,创建全1的mask
if masks is None:
masks = torch.ones_like(inputs)
# 调用TimesFM核心模型
# 注意:这里需要根据实际的forward签名调整
outputs, _ = self.model(inputs, masks, decode_caches=None)
# outputs包含: (input_embeddings, output_embeddings, output_ts, output_quantile_spread)
# output_ts是点预测
point_forecast = outputs[2]
return point_forecast
wrapped_model = TimesFMONNXWrapper(core_model)
wrapped_model.eval()
print("✓ ONNX包装器已创建")
except Exception as e:
print(f"✗ 提取核心模型失败: {e}")
import traceback
traceback.print_exc()
return
# ========== 步骤5: 导出为ONNX ==========
print("\n[5/6] 导出模型为ONNX格式...")
onnx_path = "./models/timesfm_2p5_200m.onnx"
os.makedirs("./models", exist_ok=True)
try:
# 准备示例输入
batch_size = 1
context_len = 512
dummy_input = torch.randn(batch_size, context_len, dtype=torch.float32)
dummy_mask = torch.ones(batch_size, context_len, dtype=torch.float32)
print(f" 示例输入形状: inputs={dummy_input.shape}, masks={dummy_mask.shape}")
print(" 开始导出ONNX...")
# 导出为ONNX
torch.onnx.export(
wrapped_model,
(dummy_input, dummy_mask),
onnx_path,
export_params=True,
opset_version=14,
do_constant_folding=True,
input_names=['inputs', 'masks'],
output_names=['point_forecast'],
dynamic_axes={
'inputs': {0: 'batch_size', 1: 'context_length'},
'masks': {0: 'batch_size', 1: 'context_length'},
'point_forecast': {0: 'batch_size', 1: 'horizon_length'}
},
verbose=False
)
print(f"✓ ONNX模型已导出")
print(f" 保存路径: {onnx_path}")
print(f" 文件大小: {os.path.getsize(onnx_path) / (1024**2):.2f} MB")
except Exception as e:
print(f"✗ ONNX导出失败: {e}")
import traceback
traceback.print_exc()
# 尝试简化版本
print("\n尝试简化导出方案...")
try:
# 只导出单输入版本
torch.onnx.export(
wrapped_model,
dummy_input,
onnx_path,
export_params=True,
opset_version=14,
input_names=['inputs'],
output_names=['predictions'],
)
print(f"✓ 简化版ONNX模型已导出: {onnx_path}")
except Exception as e2:
print(f"✗ 简化导出也失败: {e2}")
return
# ========== 步骤6: 验证ONNX模型 ==========
print("\n[6/6] 验证ONNX模型...")
try:
import onnx
import onnxruntime as ort
# 加载并检查ONNX模型
onnx_model = onnx.load(onnx_path)
onnx.checker.check_model(onnx_model)
print("✓ ONNX模型结构验证通过")
# 显示模型信息
print(f"\n模型信息:")
print(f" 输入:")
for input_tensor in onnx_model.graph.input:
dims = [d.dim_value if d.dim_value > 0 else 'dynamic' for d in input_tensor.type.tensor_type.shape.dim]
print(f" - {input_tensor.name}: {dims}")
print(f" 输出:")
for output_tensor in onnx_model.graph.output:
dims = [d.dim_value if d.dim_value > 0 else 'dynamic' for d in output_tensor.type.tensor_type.shape.dim]
print(f" - {output_tensor.name}: {dims}")
# 创建ONNX Runtime推理会话
print("\n 创建ONNX Runtime推理会话...")
ort_session = ort.InferenceSession(onnx_path)
# 准备测试输入
test_input = np.random.randn(1, 512).astype(np.float32)
test_mask = np.ones((1, 512), dtype=np.float32)
# 运行推理
print(" 运行推理测试...")
outputs = ort_session.run(
None,
{'inputs': test_input, 'masks': test_mask}
)
print(f"✓ ONNX推理成功")
print(f" 输出数量: {len(outputs)}")
for i, output in enumerate(outputs):
print(f" 输出{i}形状: {output.shape}")
except ImportError:
print("⚠️ 未安装onnx或onnxruntime,跳过验证")
print(" 安装命令: pip install onnx onnxruntime")
except Exception as e:
print(f"⚠️ ONNX验证失败: {e}")
import traceback
traceback.print_exc()
print(" 但模型文件已保存,可以尝试手动验证")
# ========== 总结 ==========
print("\n" + "="*80)
print("✓ TimesFM 2.5模型导出完成!")
print("="*80)
if os.path.exists(onnx_path):
print(f"\n保存位置: {onnx_path}")
print(f"文件大小: {os.path.getsize(onnx_path) / (1024**2):.2f} MB")
print(f"\n使用方法:")
print(f"```python")
print(f"import onnxruntime as ort")
print(f"import numpy as np")
print(f"")
print(f"# 加载模型")
print(f"session = ort.InferenceSession('{onnx_path}')")
print(f"")
print(f"# 准备输入(512小时BTC历史价格)")
print(f"btc_prices = np.array([85000, 85100, ...]) # 512个价格点")
print(f"inputs = btc_prices.reshape(1, -1).astype(np.float32)")
print(f"masks = np.ones((1, 512), dtype=np.float32)")
print(f"")
print(f"# 推理")
print(f"outputs = session.run(None, {{'inputs': inputs, 'masks': masks}})")
print(f"predictions = outputs[0] # 未来24小时价格预测")
print(f"```")
print("\n" + "="*80)
if __name__ == "__main__":
export_timesfm_to_onnx()
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# 安装三个时间序列基础模型
echo "=================================="
echo "安装时间序列基础模型"
echo "=================================="
# 激活conda环境
source /Users/xxz/miniforge3/bin/activate
conda activate crypto_pred_312
echo ""
echo "[1/3] 安装 Google TimesFM 2.5..."
pip install git+https://github.com/google-research/timesfm.git
echo ""
echo "[2/3] 安装 IBM TTM-R2 (Granite TSFM)..."
pip install tsfm_public
echo ""
echo "[3/3] 安装 Lag-Llama 依赖..."
pip install gluonts[torch] torch
echo ""
echo "=================================="
echo "✓ 所有模型安装完成!"
echo "=================================="
echo ""
echo "模型位置:"
echo " - TimesFM 2.5: HuggingFace自动下载"
echo " - TTM-R2: ibm-granite/granite-timeseries-ttm-r2"
echo " - Lag-Llama: time-series-foundation-models/lag-llama"
Binary file not shown.
@@ -0,0 +1,7 @@
{
"price_mae": 434.07877457530066,
"price_rmse": 633.7116744507372,
"price_mape": 0.4112793819634811,
"direction_accuracy": 0.5083031112807788,
"best_val_loss": 0.00040675073481596463
}
Binary file not shown.
View File
Whitespace-only changes.
+265
View File
@@ -0,0 +1,265 @@
"""N-BEATS模型实现:预测未来1小时BTC价格"""
import torch
import torch.nn as nn
import numpy as np
class NBeatsBlock(nn.Module):
"""N-BEATS基础块"""
def __init__(self, input_size, theta_size, basis_function, layers, layer_size):
super().__init__()
self.input_size = input_size
self.theta_size = theta_size
self.basis_function = basis_function
# 全连接层栈
self.fc_stack = nn.ModuleList([
nn.Linear(input_size if i == 0 else layer_size, layer_size)
for i in range(layers)
])
# Theta层(用于生成基函数参数)
self.theta_b_fc = nn.Linear(layer_size, theta_size, bias=False) # backcast
self.theta_f_fc = nn.Linear(layer_size, theta_size, bias=False) # forecast
self.relu = nn.ReLU()
def forward(self, x):
"""
Args:
x: (batch_size, input_size)
Returns:
backcast: (batch_size, input_size)
forecast: (batch_size, forecast_size)
"""
# 通过FC层栈
for fc in self.fc_stack:
x = self.relu(fc(x))
# 生成theta参数
theta_b = self.theta_b_fc(x) # backcast coefficients
theta_f = self.theta_f_fc(x) # forecast coefficients
# 应用基函数
backcast = self.basis_function(theta_b, is_forecast=False)
forecast = self.basis_function(theta_f, is_forecast=True)
return backcast, forecast
class GenericBasis(nn.Module):
"""通用基函数"""
def __init__(self, backcast_size, forecast_size):
super().__init__()
self.backcast_size = backcast_size
self.forecast_size = forecast_size
def forward(self, theta, is_forecast):
if is_forecast:
return theta[:, :self.forecast_size]
else:
return theta[:, :self.backcast_size]
class TrendBasis(nn.Module):
"""趋势基函数(多项式)"""
def __init__(self, degree, backcast_size, forecast_size):
super().__init__()
self.backcast_size = backcast_size
self.forecast_size = forecast_size
# 多项式时间点
backcast_time = torch.arange(backcast_size, dtype=torch.float32) / backcast_size
forecast_time = torch.arange(forecast_size, dtype=torch.float32) / forecast_size
# 构建多项式基
self.backcast_basis = torch.stack([backcast_time ** i for i in range(degree)], dim=1)
self.forecast_basis = torch.stack([forecast_time ** i for i in range(degree)], dim=1)
def forward(self, theta, is_forecast):
basis = self.forecast_basis if is_forecast else self.backcast_basis
basis = basis.to(theta.device)
return torch.matmul(theta, basis.T)
class SeasonalityBasis(nn.Module):
"""季节性基函数(傅里叶级数)"""
def __init__(self, harmonics, backcast_size, forecast_size):
super().__init__()
self.backcast_size = backcast_size
self.forecast_size = forecast_size
backcast_time = 2 * np.pi * torch.arange(backcast_size, dtype=torch.float32) / backcast_size
forecast_time = 2 * np.pi * torch.arange(forecast_size, dtype=torch.float32) / forecast_size
# 构建傅里叶基(sin和cos)
backcast_basis = []
forecast_basis = []
for i in range(1, harmonics + 1):
backcast_basis.extend([torch.cos(i * backcast_time), torch.sin(i * backcast_time)])
forecast_basis.extend([torch.cos(i * forecast_time), torch.sin(i * forecast_time)])
self.backcast_basis = torch.stack(backcast_basis, dim=1)
self.forecast_basis = torch.stack(forecast_basis, dim=1)
def forward(self, theta, is_forecast):
basis = self.forecast_basis if is_forecast else self.backcast_basis
basis = basis.to(theta.device)
return torch.matmul(theta, basis.T)
class NBeatsStack(nn.Module):
"""N-BEATS栈(多个块的组合)"""
def __init__(self, input_size, forecast_size, num_blocks, block_layers,
layer_size, theta_size, basis_function):
super().__init__()
self.blocks = nn.ModuleList([
NBeatsBlock(
input_size=input_size,
theta_size=theta_size,
basis_function=basis_function,
layers=block_layers,
layer_size=layer_size
)
for _ in range(num_blocks)
])
self.forecast_size = forecast_size
def forward(self, x):
"""
Args:
x: (batch_size, input_size)
Returns:
forecast: (batch_size, forecast_size)
"""
residual = x
forecast = torch.zeros(x.size(0), self.forecast_size, device=x.device)
for block in self.blocks:
backcast, block_forecast = block(residual)
residual = residual - backcast
forecast = forecast + block_forecast
return forecast
class NBeatsConfig:
"""N-BEATS模型配置"""
def __init__(
self,
input_size=168, # 输入序列长度
forecast_size=1, # 预测长度
stacks=['trend', 'seasonality', 'generic'], # 使用的栈类型
num_blocks_per_stack=3, # 每个栈的块数
block_layers=4, # 每个块的FC层数
layer_size=256, # FC层大小
trend_degree=2, # 趋势多项式阶数
seasonality_harmonics=4, # 季节性谐波数
):
self.input_size = input_size
self.forecast_size = forecast_size
self.stacks = stacks
self.num_blocks_per_stack = num_blocks_per_stack
self.block_layers = block_layers
self.layer_size = layer_size
self.trend_degree = trend_degree
self.seasonality_harmonics = seasonality_harmonics
class NBeats(nn.Module):
"""N-BEATS模型"""
def __init__(self, config):
super().__init__()
self.config = config
# 创建多个栈
self.stacks = nn.ModuleList()
for stack_type in config.stacks:
if stack_type == 'trend':
basis_function = TrendBasis(
degree=config.trend_degree,
backcast_size=config.input_size,
forecast_size=config.forecast_size
)
theta_size = config.trend_degree
elif stack_type == 'seasonality':
basis_function = SeasonalityBasis(
harmonics=config.seasonality_harmonics,
backcast_size=config.input_size,
forecast_size=config.forecast_size
)
theta_size = 2 * config.seasonality_harmonics
else: # generic
basis_function = GenericBasis(
backcast_size=config.input_size,
forecast_size=config.forecast_size
)
theta_size = max(config.input_size, config.forecast_size)
stack = NBeatsStack(
input_size=config.input_size,
forecast_size=config.forecast_size,
num_blocks=config.num_blocks_per_stack,
block_layers=config.block_layers,
layer_size=config.layer_size,
theta_size=theta_size,
basis_function=basis_function
)
self.stacks.append(stack)
def forward(self, x):
"""
Args:
x: (batch_size, lookback, features) 或 (batch_size, lookback)
Returns:
forecast: (batch_size, forecast_size)
"""
# 如果输入是多特征,取最后一个特征(close price)
if len(x.shape) == 3:
x = x[:, :, -1] # (batch_size, lookback)
# 确保是2D
if len(x.shape) == 1:
x = x.unsqueeze(0)
# 通过所有栈
forecast = torch.zeros(x.size(0), self.config.forecast_size, device=x.device)
residual = x
for stack in self.stacks:
stack_forecast = stack(residual)
forecast = forecast + stack_forecast
# 更新残差(用于下一个栈)
# 注意:这里简化了,标准N-BEATS会从每个栈获取backcast
return forecast.squeeze(-1) # (batch_size,)
def count_parameters(self):
"""统计模型参数数量"""
return sum(p.numel() for p in self.parameters() if p.requires_grad)
def save(self, path):
"""保存模型"""
torch.save({
'model_state_dict': self.state_dict(),
'config': self.config
}, path)
@classmethod
def load(cls, path, device='cpu'):
"""加载模型"""
checkpoint = torch.load(path, map_location=device)
model = cls(checkpoint['config'])
model.load_state_dict(checkpoint['model_state_dict'])
return model
+83
View File
@@ -0,0 +1,83 @@
# 加密货币价格预测模型测试报告
## 测试概况
- **测试期间**: 2026年1月1日 - 2026年1月30日
- **总天数**: 22天
- **总样本数**: 495个小时
- **生成时间**: 2026-01-31 16:47:08
---
## 单模型性能
### DLinear分类模型
- **准确率**: 53.33%
- **预测上涨比例**: 47.1%
- **预测下跌比例**: 52.9%
### N-BEATS回归模型
- **准确率**: 49.90%
- **预测上涨比例**: 73.5%
- **预测下跌比例**: 26.5%
### TTM-R2零样本模型
- **准确率**: 51.31%
- **预测上涨比例**: 23.6%
- **预测下跌比例**: 76.4%
---
## 集成策略性能
### 双模型交集(DLinear + N-BEATS)
- **准确率**: 53.39%
- **覆盖率**: 47.68%
- **有效预测数**: 236次
### 三模型多数投票(DLinear + N-BEATS + TTM-R2)
- **准确率**: 52.93%
- **覆盖率**: 100.00%
- **有效预测数**: 495次
---
## DLinear概率分布分析
| 概率区间 | 样本数 | 占比 |
|---------|--------|------|
| 0.4-0.5 | 262 | 52.9% |
| 0.5-0.6 | 229 | 46.3% |
| 0.6-0.7 | 4 | 0.8% |
**平均概率**: 0.501
---
## 最佳/最差交易日
### 双模型交集
- **最佳日**: 2026-01-11 (83.3%)
- **最差日**: 2026-01-23 (28.6%)
### 三模型投票
- **最佳日**: 2026-01-28 (80.0%)
- **最差日**: 2026-01-23 (29.2%)
---
## 结论
1. **最佳单模型**: DLinear (53.33%)
2. **最佳集成策略**: 双模型交集 (53.39%)
3. **DLinear置信度**: 99.2%的预测集中在0.45-0.55区间,缺乏高置信度预测
## 数据文件
- 逐小时预测结果: `hourly_predictions_2026_01.csv`
- 每日统计数据: `daily_statistics_2026_01.csv`
- 总结报告: `summary_report_2026_01.json`
---
生成时间: 2026-01-31 16:47:08
+23
View File
@@ -0,0 +1,23 @@
date,total_samples,two_model_valid,two_model_correct,three_model_correct,dlinear_correct,dlinear_accuracy,two_model_accuracy,three_model_accuracy,two_model_coverage
2026-01-07,5,3,2,3,4,80.0,66.66666666666666,60.0,60.0
2026-01-08,24,13,6,12,11,45.83333333333333,46.15384615384615,50.0,54.166666666666664
2026-01-09,24,12,6,15,13,54.166666666666664,50.0,62.5,50.0
2026-01-10,24,12,6,12,13,54.166666666666664,50.0,50.0,50.0
2026-01-11,24,6,5,12,11,45.83333333333333,83.33333333333334,50.0,25.0
2026-01-12,24,6,3,8,13,54.166666666666664,50.0,33.33333333333333,25.0
2026-01-13,24,12,6,12,12,50.0,50.0,50.0,50.0
2026-01-14,24,12,6,13,14,58.333333333333336,50.0,54.166666666666664,50.0
2026-01-15,24,14,8,12,14,58.333333333333336,57.14285714285714,50.0,58.333333333333336
2026-01-16,24,13,6,13,10,41.66666666666667,46.15384615384615,54.166666666666664,54.166666666666664
2026-01-17,24,8,5,13,11,45.83333333333333,62.5,54.166666666666664,33.33333333333333
2026-01-18,24,17,9,15,12,50.0,52.94117647058824,62.5,70.83333333333334
2026-01-19,24,17,12,17,16,66.66666666666666,70.58823529411765,70.83333333333334,70.83333333333334
2026-01-20,24,6,2,9,11,45.83333333333333,33.33333333333333,37.5,25.0
2026-01-21,24,9,6,15,15,62.5,66.66666666666666,62.5,37.5
2026-01-22,24,10,6,13,14,58.333333333333336,60.0,54.166666666666664,41.66666666666667
2026-01-23,24,14,4,7,7,29.166666666666668,28.57142857142857,29.166666666666668,58.333333333333336
2026-01-24,24,14,7,13,14,58.333333333333336,50.0,54.166666666666664,58.333333333333336
2026-01-25,24,4,0,11,10,41.66666666666667,0.0,45.83333333333333,16.666666666666664
2026-01-26,24,13,8,13,15,62.5,61.53846153846154,54.166666666666664,54.166666666666664
2026-01-27,24,13,7,16,16,66.66666666666666,53.84615384615385,66.66666666666666,54.166666666666664
2026-01-28,10,8,6,8,8,80.0,75.0,80.0,80.0
1 date total_samples two_model_valid two_model_correct three_model_correct dlinear_correct dlinear_accuracy two_model_accuracy three_model_accuracy two_model_coverage
2 2026-01-07 5 3 2 3 4 80.0 66.66666666666666 60.0 60.0
3 2026-01-08 24 13 6 12 11 45.83333333333333 46.15384615384615 50.0 54.166666666666664
4 2026-01-09 24 12 6 15 13 54.166666666666664 50.0 62.5 50.0
5 2026-01-10 24 12 6 12 13 54.166666666666664 50.0 50.0 50.0
6 2026-01-11 24 6 5 12 11 45.83333333333333 83.33333333333334 50.0 25.0
7 2026-01-12 24 6 3 8 13 54.166666666666664 50.0 33.33333333333333 25.0
8 2026-01-13 24 12 6 12 12 50.0 50.0 50.0 50.0
9 2026-01-14 24 12 6 13 14 58.333333333333336 50.0 54.166666666666664 50.0
10 2026-01-15 24 14 8 12 14 58.333333333333336 57.14285714285714 50.0 58.333333333333336
11 2026-01-16 24 13 6 13 10 41.66666666666667 46.15384615384615 54.166666666666664 54.166666666666664
12 2026-01-17 24 8 5 13 11 45.83333333333333 62.5 54.166666666666664 33.33333333333333
13 2026-01-18 24 17 9 15 12 50.0 52.94117647058824 62.5 70.83333333333334
14 2026-01-19 24 17 12 17 16 66.66666666666666 70.58823529411765 70.83333333333334 70.83333333333334
15 2026-01-20 24 6 2 9 11 45.83333333333333 33.33333333333333 37.5 25.0
16 2026-01-21 24 9 6 15 15 62.5 66.66666666666666 62.5 37.5
17 2026-01-22 24 10 6 13 14 58.333333333333336 60.0 54.166666666666664 41.66666666666667
18 2026-01-23 24 14 4 7 7 29.166666666666668 28.57142857142857 29.166666666666668 58.333333333333336
19 2026-01-24 24 14 7 13 14 58.333333333333336 50.0 54.166666666666664 58.333333333333336
20 2026-01-25 24 4 0 11 10 41.66666666666667 0.0 45.83333333333333 16.666666666666664
21 2026-01-26 24 13 8 13 15 62.5 61.53846153846154 54.166666666666664 54.166666666666664
22 2026-01-27 24 13 7 16 16 66.66666666666666 53.84615384615385 66.66666666666666 54.166666666666664
23 2026-01-28 10 8 6 8 8 80.0 75.0 80.0 80.0
+496
View File
@@ -0,0 +1,496 @@
timestamp_utc,timestamp_et,date,true_label,dlinear_prob,dlinear_pred,nbeats_pred,ttm_pred,two_model_pred,three_model_pred,dlinear_correct,two_model_correct,three_model_correct
2026-01-08 00:00:00,2026-01-07 19:00:00-05:00,2026-01-07,1,0.57705873,1,0,1,-1,1,True,False,True
2026-01-08 01:00:00,2026-01-07 20:00:00-05:00,2026-01-07,0,0.49304762,0,0,1,0,0,True,True,True
2026-01-08 02:00:00,2026-01-07 21:00:00-05:00,2026-01-07,0,0.47215617,0,0,0,0,0,True,True,True
2026-01-08 03:00:00,2026-01-07 22:00:00-05:00,2026-01-07,1,0.46118113,0,0,0,0,0,False,False,False
2026-01-08 04:00:00,2026-01-07 23:00:00-05:00,2026-01-07,0,0.45929378,0,1,1,-1,1,True,False,False
2026-01-08 05:00:00,2026-01-08 00:00:00-05:00,2026-01-08,1,0.47178885,0,0,0,0,0,False,False,False
2026-01-08 06:00:00,2026-01-08 01:00:00-05:00,2026-01-08,1,0.5306643,1,1,1,1,1,True,True,True
2026-01-08 07:00:00,2026-01-08 02:00:00-05:00,2026-01-08,0,0.4891925,0,1,1,-1,1,True,False,False
2026-01-08 08:00:00,2026-01-08 03:00:00-05:00,2026-01-08,0,0.5191637,1,0,1,-1,1,False,False,False
2026-01-08 09:00:00,2026-01-08 04:00:00-05:00,2026-01-08,0,0.47131988,0,1,1,-1,1,True,False,False
2026-01-08 10:00:00,2026-01-08 05:00:00-05:00,2026-01-08,0,0.47046715,0,0,0,0,0,True,True,True
2026-01-08 11:00:00,2026-01-08 06:00:00-05:00,2026-01-08,1,0.5125343,1,0,1,-1,1,True,False,True
2026-01-08 12:00:00,2026-01-08 07:00:00-05:00,2026-01-08,1,0.5334059,1,1,1,1,1,True,True,True
2026-01-08 13:00:00,2026-01-08 08:00:00-05:00,2026-01-08,1,0.49018916,0,1,1,-1,1,False,False,True
2026-01-08 14:00:00,2026-01-08 09:00:00-05:00,2026-01-08,0,0.50079376,1,1,1,1,1,False,False,False
2026-01-08 15:00:00,2026-01-08 10:00:00-05:00,2026-01-08,1,0.46582156,0,1,1,-1,1,False,False,True
2026-01-08 16:00:00,2026-01-08 11:00:00-05:00,2026-01-08,1,0.46240616,0,1,1,-1,1,False,False,True
2026-01-08 17:00:00,2026-01-08 12:00:00-05:00,2026-01-08,0,0.48750654,0,1,1,-1,1,True,False,False
2026-01-08 18:00:00,2026-01-08 13:00:00-05:00,2026-01-08,0,0.47014055,0,1,1,-1,1,True,False,False
2026-01-08 19:00:00,2026-01-08 14:00:00-05:00,2026-01-08,1,0.49040872,0,1,1,-1,1,False,False,True
2026-01-08 20:00:00,2026-01-08 15:00:00-05:00,2026-01-08,0,0.5273022,1,1,1,1,1,False,False,False
2026-01-08 21:00:00,2026-01-08 16:00:00-05:00,2026-01-08,1,0.5106931,1,1,1,1,1,True,True,True
2026-01-08 22:00:00,2026-01-08 17:00:00-05:00,2026-01-08,0,0.5778609,1,1,1,1,1,False,False,False
2026-01-08 23:00:00,2026-01-08 18:00:00-05:00,2026-01-08,1,0.54889923,1,1,1,1,1,True,True,True
2026-01-09 00:00:00,2026-01-08 19:00:00-05:00,2026-01-08,1,0.51507455,1,1,1,1,1,True,True,True
2026-01-09 01:00:00,2026-01-08 20:00:00-05:00,2026-01-08,0,0.5170575,1,1,1,1,1,False,False,False
2026-01-09 02:00:00,2026-01-08 21:00:00-05:00,2026-01-08,1,0.4717993,0,1,1,-1,1,False,False,True
2026-01-09 03:00:00,2026-01-08 22:00:00-05:00,2026-01-08,0,0.5043716,1,1,1,1,1,False,False,False
2026-01-09 04:00:00,2026-01-08 23:00:00-05:00,2026-01-08,0,0.51360196,1,1,1,1,1,False,False,False
2026-01-09 05:00:00,2026-01-09 00:00:00-05:00,2026-01-09,1,0.469029,0,1,1,-1,1,False,False,True
2026-01-09 06:00:00,2026-01-09 01:00:00-05:00,2026-01-09,1,0.50669587,1,1,1,1,1,True,True,True
2026-01-09 07:00:00,2026-01-09 02:00:00-05:00,2026-01-09,0,0.5564795,1,1,1,1,1,False,False,False
2026-01-09 08:00:00,2026-01-09 03:00:00-05:00,2026-01-09,1,0.5820567,1,1,1,1,1,True,True,True
2026-01-09 09:00:00,2026-01-09 04:00:00-05:00,2026-01-09,1,0.5504754,1,1,1,1,1,True,True,True
2026-01-09 10:00:00,2026-01-09 05:00:00-05:00,2026-01-09,0,0.53064233,1,1,1,1,1,False,False,False
2026-01-09 11:00:00,2026-01-09 06:00:00-05:00,2026-01-09,0,0.50407845,1,1,1,1,1,False,False,False
2026-01-09 12:00:00,2026-01-09 07:00:00-05:00,2026-01-09,0,0.4576875,0,1,1,-1,1,True,False,False
2026-01-09 13:00:00,2026-01-09 08:00:00-05:00,2026-01-09,1,0.5189501,1,0,1,-1,1,True,False,True
2026-01-09 14:00:00,2026-01-09 09:00:00-05:00,2026-01-09,1,0.50425255,1,1,1,1,1,True,True,True
2026-01-09 15:00:00,2026-01-09 10:00:00-05:00,2026-01-09,0,0.45628017,0,1,1,-1,1,True,False,False
2026-01-09 16:00:00,2026-01-09 11:00:00-05:00,2026-01-09,1,0.44883052,0,0,1,0,0,False,False,False
2026-01-09 17:00:00,2026-01-09 12:00:00-05:00,2026-01-09,1,0.46907404,0,1,1,-1,1,False,False,True
2026-01-09 18:00:00,2026-01-09 13:00:00-05:00,2026-01-09,1,0.48777947,0,1,1,-1,1,False,False,True
2026-01-09 19:00:00,2026-01-09 14:00:00-05:00,2026-01-09,0,0.49411413,0,1,0,-1,0,True,False,True
2026-01-09 20:00:00,2026-01-09 15:00:00-05:00,2026-01-09,0,0.47928023,0,1,0,-1,0,True,False,True
2026-01-09 21:00:00,2026-01-09 16:00:00-05:00,2026-01-09,0,0.49988702,0,1,0,-1,0,True,False,True
2026-01-09 22:00:00,2026-01-09 17:00:00-05:00,2026-01-09,1,0.4896841,0,1,0,-1,0,False,False,False
2026-01-09 23:00:00,2026-01-09 18:00:00-05:00,2026-01-09,0,0.49358237,0,1,0,-1,0,True,False,True
2026-01-10 00:00:00,2026-01-09 19:00:00-05:00,2026-01-09,0,0.50367767,1,1,0,1,1,False,False,False
2026-01-10 01:00:00,2026-01-09 20:00:00-05:00,2026-01-09,1,0.52877015,1,1,0,1,1,True,True,True
2026-01-10 02:00:00,2026-01-09 21:00:00-05:00,2026-01-09,1,0.51958245,1,1,0,1,1,True,True,True
2026-01-10 03:00:00,2026-01-09 22:00:00-05:00,2026-01-09,0,0.53057724,1,1,0,1,1,False,False,False
2026-01-10 04:00:00,2026-01-09 23:00:00-05:00,2026-01-09,1,0.45753565,0,1,1,-1,1,False,False,True
2026-01-10 05:00:00,2026-01-10 00:00:00-05:00,2026-01-10,0,0.447833,0,1,0,-1,0,True,False,True
2026-01-10 06:00:00,2026-01-10 01:00:00-05:00,2026-01-10,1,0.4577814,0,1,1,-1,1,False,False,True
2026-01-10 07:00:00,2026-01-10 02:00:00-05:00,2026-01-10,0,0.47578683,0,1,1,-1,1,True,False,False
2026-01-10 08:00:00,2026-01-10 03:00:00-05:00,2026-01-10,0,0.4506602,0,1,1,-1,1,True,False,False
2026-01-10 09:00:00,2026-01-10 04:00:00-05:00,2026-01-10,0,0.5478148,1,1,0,1,1,False,False,False
2026-01-10 10:00:00,2026-01-10 05:00:00-05:00,2026-01-10,1,0.5990233,1,1,0,1,1,True,True,True
2026-01-10 11:00:00,2026-01-10 06:00:00-05:00,2026-01-10,1,0.5706127,1,1,0,1,1,True,True,True
2026-01-10 12:00:00,2026-01-10 07:00:00-05:00,2026-01-10,0,0.5276616,1,1,0,1,1,False,False,False
2026-01-10 13:00:00,2026-01-10 08:00:00-05:00,2026-01-10,1,0.51404405,1,1,0,1,1,True,True,True
2026-01-10 14:00:00,2026-01-10 09:00:00-05:00,2026-01-10,1,0.50078404,1,1,0,1,1,True,True,True
2026-01-10 15:00:00,2026-01-10 10:00:00-05:00,2026-01-10,1,0.45550296,0,1,0,-1,0,False,False,False
2026-01-10 16:00:00,2026-01-10 11:00:00-05:00,2026-01-10,1,0.44787562,0,1,0,-1,0,False,False,False
2026-01-10 17:00:00,2026-01-10 12:00:00-05:00,2026-01-10,0,0.44727677,0,1,0,-1,0,True,False,True
2026-01-10 18:00:00,2026-01-10 13:00:00-05:00,2026-01-10,1,0.44855183,0,1,0,-1,0,False,False,False
2026-01-10 19:00:00,2026-01-10 14:00:00-05:00,2026-01-10,0,0.45485023,0,1,0,-1,0,True,False,True
2026-01-10 20:00:00,2026-01-10 15:00:00-05:00,2026-01-10,0,0.47032186,0,1,0,-1,0,True,False,True
2026-01-10 21:00:00,2026-01-10 16:00:00-05:00,2026-01-10,0,0.48025206,0,1,0,-1,0,True,False,True
2026-01-10 22:00:00,2026-01-10 17:00:00-05:00,2026-01-10,0,0.5247036,1,1,0,1,1,False,False,False
2026-01-10 23:00:00,2026-01-10 18:00:00-05:00,2026-01-10,0,0.58018094,1,1,0,1,1,False,False,False
2026-01-11 00:00:00,2026-01-10 19:00:00-05:00,2026-01-10,0,0.59668726,1,1,0,1,1,False,False,False
2026-01-11 01:00:00,2026-01-10 20:00:00-05:00,2026-01-10,1,0.5909982,1,1,0,1,1,True,True,True
2026-01-11 02:00:00,2026-01-10 21:00:00-05:00,2026-01-10,1,0.5297047,1,1,0,1,1,True,True,True
2026-01-11 03:00:00,2026-01-10 22:00:00-05:00,2026-01-10,0,0.5227216,1,1,0,1,1,False,False,False
2026-01-11 04:00:00,2026-01-10 23:00:00-05:00,2026-01-10,1,0.4885675,0,1,0,-1,0,False,False,False
2026-01-11 05:00:00,2026-01-11 00:00:00-05:00,2026-01-11,1,0.47107595,0,1,0,-1,0,False,False,False
2026-01-11 06:00:00,2026-01-11 01:00:00-05:00,2026-01-11,1,0.4610141,0,1,0,-1,0,False,False,False
2026-01-11 07:00:00,2026-01-11 02:00:00-05:00,2026-01-11,0,0.4598382,0,1,1,-1,1,True,False,False
2026-01-11 08:00:00,2026-01-11 03:00:00-05:00,2026-01-11,1,0.48207,0,1,1,-1,1,False,False,True
2026-01-11 09:00:00,2026-01-11 04:00:00-05:00,2026-01-11,1,0.513094,1,1,1,1,1,True,True,True
2026-01-11 10:00:00,2026-01-11 05:00:00-05:00,2026-01-11,0,0.47068593,0,1,1,-1,1,True,False,False
2026-01-11 11:00:00,2026-01-11 06:00:00-05:00,2026-01-11,0,0.4895874,0,1,1,-1,1,True,False,False
2026-01-11 12:00:00,2026-01-11 07:00:00-05:00,2026-01-11,1,0.46443766,0,1,1,-1,1,False,False,True
2026-01-11 13:00:00,2026-01-11 08:00:00-05:00,2026-01-11,1,0.4845122,0,1,1,-1,1,False,False,True
2026-01-11 14:00:00,2026-01-11 09:00:00-05:00,2026-01-11,1,0.50560534,1,1,1,1,1,True,True,True
2026-01-11 15:00:00,2026-01-11 10:00:00-05:00,2026-01-11,0,0.47123846,0,0,1,0,0,True,True,True
2026-01-11 16:00:00,2026-01-11 11:00:00-05:00,2026-01-11,0,0.45919827,0,1,1,-1,1,True,False,False
2026-01-11 17:00:00,2026-01-11 12:00:00-05:00,2026-01-11,1,0.52895916,1,0,1,-1,1,True,False,True
2026-01-11 18:00:00,2026-01-11 13:00:00-05:00,2026-01-11,1,0.5049388,1,1,1,1,1,True,True,True
2026-01-11 19:00:00,2026-01-11 14:00:00-05:00,2026-01-11,1,0.48020154,0,1,1,-1,1,False,False,True
2026-01-11 20:00:00,2026-01-11 15:00:00-05:00,2026-01-11,1,0.49915588,0,1,1,-1,1,False,False,True
2026-01-11 21:00:00,2026-01-11 16:00:00-05:00,2026-01-11,0,0.5144025,1,1,0,1,1,False,False,False
2026-01-11 22:00:00,2026-01-11 17:00:00-05:00,2026-01-11,1,0.49443942,0,1,0,-1,0,False,False,False
2026-01-11 23:00:00,2026-01-11 18:00:00-05:00,2026-01-11,1,0.4918472,0,1,0,-1,0,False,False,False
2026-01-12 00:00:00,2026-01-11 19:00:00-05:00,2026-01-11,0,0.4723591,0,0,1,0,0,True,True,True
2026-01-12 01:00:00,2026-01-11 20:00:00-05:00,2026-01-11,0,0.45115232,0,1,1,-1,1,True,False,False
2026-01-12 02:00:00,2026-01-11 21:00:00-05:00,2026-01-11,1,0.47599584,0,1,1,-1,1,False,False,True
2026-01-12 03:00:00,2026-01-11 22:00:00-05:00,2026-01-11,0,0.51635396,1,0,1,-1,1,False,False,False
2026-01-12 04:00:00,2026-01-11 23:00:00-05:00,2026-01-11,1,0.47296882,0,1,0,-1,0,False,False,False
2026-01-12 05:00:00,2026-01-12 00:00:00-05:00,2026-01-12,1,0.48586744,0,1,0,-1,0,False,False,False
2026-01-12 06:00:00,2026-01-12 01:00:00-05:00,2026-01-12,0,0.47855884,0,1,0,-1,0,True,False,True
2026-01-12 07:00:00,2026-01-12 02:00:00-05:00,2026-01-12,0,0.4667405,0,1,0,-1,0,True,False,True
2026-01-12 08:00:00,2026-01-12 03:00:00-05:00,2026-01-12,1,0.47844118,0,1,0,-1,0,False,False,False
2026-01-12 09:00:00,2026-01-12 04:00:00-05:00,2026-01-12,1,0.49896824,0,1,0,-1,0,False,False,False
2026-01-12 10:00:00,2026-01-12 05:00:00-05:00,2026-01-12,1,0.4889443,0,1,0,-1,0,False,False,False
2026-01-12 11:00:00,2026-01-12 06:00:00-05:00,2026-01-12,0,0.45522135,0,1,0,-1,0,True,False,True
2026-01-12 12:00:00,2026-01-12 07:00:00-05:00,2026-01-12,1,0.45169547,0,0,0,0,0,False,False,False
2026-01-12 13:00:00,2026-01-12 08:00:00-05:00,2026-01-12,0,0.48637372,0,0,0,0,0,True,True,True
2026-01-12 14:00:00,2026-01-12 09:00:00-05:00,2026-01-12,0,0.45615003,0,1,0,-1,0,True,False,True
2026-01-12 15:00:00,2026-01-12 10:00:00-05:00,2026-01-12,1,0.45153883,0,1,0,-1,0,False,False,False
2026-01-12 16:00:00,2026-01-12 11:00:00-05:00,2026-01-12,1,0.47393757,0,0,0,0,0,False,False,False
2026-01-12 17:00:00,2026-01-12 12:00:00-05:00,2026-01-12,1,0.47199413,0,1,0,-1,0,False,False,False
2026-01-12 18:00:00,2026-01-12 13:00:00-05:00,2026-01-12,0,0.43410957,0,1,1,-1,1,True,False,False
2026-01-12 19:00:00,2026-01-12 14:00:00-05:00,2026-01-12,0,0.44708502,0,1,1,-1,1,True,False,False
2026-01-12 20:00:00,2026-01-12 15:00:00-05:00,2026-01-12,1,0.58962935,1,1,1,1,1,True,True,True
2026-01-12 21:00:00,2026-01-12 16:00:00-05:00,2026-01-12,1,0.5799872,1,1,1,1,1,True,True,True
2026-01-12 22:00:00,2026-01-12 17:00:00-05:00,2026-01-12,0,0.5379354,1,1,0,1,1,False,False,False
2026-01-12 23:00:00,2026-01-12 18:00:00-05:00,2026-01-12,1,0.52044976,1,0,0,-1,0,True,False,False
2026-01-13 00:00:00,2026-01-12 19:00:00-05:00,2026-01-12,1,0.546228,1,0,0,-1,0,True,False,False
2026-01-13 01:00:00,2026-01-12 20:00:00-05:00,2026-01-12,0,0.50185496,1,0,0,-1,0,False,False,True
2026-01-13 02:00:00,2026-01-12 21:00:00-05:00,2026-01-12,1,0.5131573,1,0,0,-1,0,True,False,False
2026-01-13 03:00:00,2026-01-12 22:00:00-05:00,2026-01-12,0,0.49922633,0,1,1,-1,1,True,False,False
2026-01-13 04:00:00,2026-01-12 23:00:00-05:00,2026-01-12,1,0.47469753,0,1,0,-1,0,False,False,False
2026-01-13 05:00:00,2026-01-13 00:00:00-05:00,2026-01-13,1,0.45794666,0,1,1,-1,1,False,False,True
2026-01-13 06:00:00,2026-01-13 01:00:00-05:00,2026-01-13,1,0.45379728,0,1,1,-1,1,False,False,True
2026-01-13 07:00:00,2026-01-13 02:00:00-05:00,2026-01-13,0,0.45295683,0,1,1,-1,1,True,False,False
2026-01-13 08:00:00,2026-01-13 03:00:00-05:00,2026-01-13,0,0.44573298,0,1,0,-1,0,True,False,True
2026-01-13 09:00:00,2026-01-13 04:00:00-05:00,2026-01-13,1,0.4709209,0,1,0,-1,0,False,False,False
2026-01-13 10:00:00,2026-01-13 05:00:00-05:00,2026-01-13,1,0.54900587,1,1,1,1,1,True,True,True
2026-01-13 11:00:00,2026-01-13 06:00:00-05:00,2026-01-13,0,0.54181457,1,1,1,1,1,False,False,False
2026-01-13 12:00:00,2026-01-13 07:00:00-05:00,2026-01-13,0,0.47640413,0,1,1,-1,1,True,False,False
2026-01-13 13:00:00,2026-01-13 08:00:00-05:00,2026-01-13,1,0.5774929,1,0,1,-1,1,True,False,True
2026-01-13 14:00:00,2026-01-13 09:00:00-05:00,2026-01-13,0,0.55843365,1,1,1,1,1,False,False,False
2026-01-13 15:00:00,2026-01-13 10:00:00-05:00,2026-01-13,0,0.53291476,1,1,1,1,1,False,False,False
2026-01-13 16:00:00,2026-01-13 11:00:00-05:00,2026-01-13,0,0.5417355,1,0,1,-1,1,False,False,False
2026-01-13 17:00:00,2026-01-13 12:00:00-05:00,2026-01-13,0,0.526461,1,1,1,1,1,False,False,False
2026-01-13 18:00:00,2026-01-13 13:00:00-05:00,2026-01-13,1,0.5094685,1,1,1,1,1,True,True,True
2026-01-13 19:00:00,2026-01-13 14:00:00-05:00,2026-01-13,1,0.50502,1,0,1,-1,1,True,False,True
2026-01-13 20:00:00,2026-01-13 15:00:00-05:00,2026-01-13,0,0.5074931,1,1,1,1,1,False,False,False
2026-01-13 21:00:00,2026-01-13 16:00:00-05:00,2026-01-13,1,0.50496596,1,1,1,1,1,True,True,True
2026-01-13 22:00:00,2026-01-13 17:00:00-05:00,2026-01-13,1,0.5311341,1,1,1,1,1,True,True,True
2026-01-13 23:00:00,2026-01-13 18:00:00-05:00,2026-01-13,1,0.5194955,1,0,1,-1,1,True,False,True
2026-01-14 00:00:00,2026-01-13 19:00:00-05:00,2026-01-13,0,0.51150465,1,0,1,-1,1,False,False,False
2026-01-14 01:00:00,2026-01-13 20:00:00-05:00,2026-01-13,0,0.53702235,1,0,1,-1,1,False,False,False
2026-01-14 02:00:00,2026-01-13 21:00:00-05:00,2026-01-13,1,0.4907134,0,0,0,0,0,False,False,False
2026-01-14 03:00:00,2026-01-13 22:00:00-05:00,2026-01-13,0,0.4840499,0,0,0,0,0,True,True,True
2026-01-14 04:00:00,2026-01-13 23:00:00-05:00,2026-01-13,0,0.4611074,0,0,0,0,0,True,True,True
2026-01-14 05:00:00,2026-01-14 00:00:00-05:00,2026-01-14,1,0.45327646,0,0,0,0,0,False,False,False
2026-01-14 06:00:00,2026-01-14 01:00:00-05:00,2026-01-14,1,0.49665686,0,0,0,0,0,False,False,False
2026-01-14 07:00:00,2026-01-14 02:00:00-05:00,2026-01-14,0,0.4882262,0,0,1,0,0,True,True,True
2026-01-14 08:00:00,2026-01-14 03:00:00-05:00,2026-01-14,0,0.46823135,0,1,1,-1,1,True,False,False
2026-01-14 09:00:00,2026-01-14 04:00:00-05:00,2026-01-14,0,0.47908562,0,1,0,-1,0,True,False,True
2026-01-14 10:00:00,2026-01-14 05:00:00-05:00,2026-01-14,0,0.55422133,1,1,0,1,1,False,False,False
2026-01-14 11:00:00,2026-01-14 06:00:00-05:00,2026-01-14,1,0.610168,1,1,1,1,1,True,True,True
2026-01-14 12:00:00,2026-01-14 07:00:00-05:00,2026-01-14,0,0.5614474,1,1,1,1,1,False,False,False
2026-01-14 13:00:00,2026-01-14 08:00:00-05:00,2026-01-14,1,0.56697947,1,0,1,-1,1,True,False,True
2026-01-14 14:00:00,2026-01-14 09:00:00-05:00,2026-01-14,1,0.52636105,1,0,1,-1,1,True,False,True
2026-01-14 15:00:00,2026-01-14 10:00:00-05:00,2026-01-14,1,0.50316924,1,0,1,-1,1,True,False,True
2026-01-14 16:00:00,2026-01-14 11:00:00-05:00,2026-01-14,0,0.5363478,1,0,1,-1,1,False,False,False
2026-01-14 17:00:00,2026-01-14 12:00:00-05:00,2026-01-14,0,0.47355476,0,0,1,0,0,True,True,True
2026-01-14 18:00:00,2026-01-14 13:00:00-05:00,2026-01-14,0,0.48269007,0,0,1,0,0,True,True,True
2026-01-14 19:00:00,2026-01-14 14:00:00-05:00,2026-01-14,1,0.50739765,1,0,1,-1,1,True,False,True
2026-01-14 20:00:00,2026-01-14 15:00:00-05:00,2026-01-14,1,0.501837,1,0,1,-1,1,True,False,True
2026-01-14 21:00:00,2026-01-14 16:00:00-05:00,2026-01-14,0,0.4608701,0,0,1,0,0,True,True,True
2026-01-14 22:00:00,2026-01-14 17:00:00-05:00,2026-01-14,1,0.4631222,0,0,1,0,0,False,False,False
2026-01-14 23:00:00,2026-01-14 18:00:00-05:00,2026-01-14,0,0.5486243,1,0,1,-1,1,False,False,False
2026-01-15 00:00:00,2026-01-14 19:00:00-05:00,2026-01-14,0,0.55234075,1,0,1,-1,1,False,False,False
2026-01-15 01:00:00,2026-01-14 20:00:00-05:00,2026-01-14,1,0.4942968,0,1,1,-1,1,False,False,True
2026-01-15 02:00:00,2026-01-14 21:00:00-05:00,2026-01-14,1,0.5293442,1,1,1,1,1,True,True,True
2026-01-15 03:00:00,2026-01-14 22:00:00-05:00,2026-01-14,0,0.5131931,1,1,1,1,1,False,False,False
2026-01-15 04:00:00,2026-01-14 23:00:00-05:00,2026-01-14,0,0.48758185,0,1,1,-1,1,True,False,False
2026-01-15 05:00:00,2026-01-15 00:00:00-05:00,2026-01-15,0,0.4538339,0,0,1,0,0,True,True,True
2026-01-15 06:00:00,2026-01-15 01:00:00-05:00,2026-01-15,1,0.48841763,0,1,1,-1,1,False,False,True
2026-01-15 07:00:00,2026-01-15 02:00:00-05:00,2026-01-15,1,0.5323837,1,1,1,1,1,True,True,True
2026-01-15 08:00:00,2026-01-15 03:00:00-05:00,2026-01-15,0,0.5621298,1,1,1,1,1,False,False,False
2026-01-15 09:00:00,2026-01-15 04:00:00-05:00,2026-01-15,1,0.46453536,0,1,1,-1,1,False,False,True
2026-01-15 10:00:00,2026-01-15 05:00:00-05:00,2026-01-15,1,0.5253816,1,1,1,1,1,True,True,True
2026-01-15 11:00:00,2026-01-15 06:00:00-05:00,2026-01-15,0,0.5148865,1,1,1,1,1,False,False,False
2026-01-15 12:00:00,2026-01-15 07:00:00-05:00,2026-01-15,0,0.4899683,0,1,1,-1,1,True,False,False
2026-01-15 13:00:00,2026-01-15 08:00:00-05:00,2026-01-15,1,0.4946566,0,0,1,0,0,False,False,False
2026-01-15 14:00:00,2026-01-15 09:00:00-05:00,2026-01-15,0,0.4987126,0,0,1,0,0,True,True,True
2026-01-15 15:00:00,2026-01-15 10:00:00-05:00,2026-01-15,0,0.48068026,0,1,1,-1,1,True,False,False
2026-01-15 16:00:00,2026-01-15 11:00:00-05:00,2026-01-15,0,0.48770115,0,0,1,0,0,True,True,True
2026-01-15 17:00:00,2026-01-15 12:00:00-05:00,2026-01-15,1,0.4844149,0,0,0,0,0,False,False,False
2026-01-15 18:00:00,2026-01-15 13:00:00-05:00,2026-01-15,0,0.4631365,0,0,0,0,0,True,True,True
2026-01-15 19:00:00,2026-01-15 14:00:00-05:00,2026-01-15,1,0.45610657,0,0,0,0,0,False,False,False
2026-01-15 20:00:00,2026-01-15 15:00:00-05:00,2026-01-15,0,0.4881056,0,0,0,0,0,True,True,True
2026-01-15 21:00:00,2026-01-15 16:00:00-05:00,2026-01-15,0,0.46482596,0,0,0,0,0,True,True,True
2026-01-15 22:00:00,2026-01-15 17:00:00-05:00,2026-01-15,1,0.51136947,1,0,0,-1,0,True,False,False
2026-01-15 23:00:00,2026-01-15 18:00:00-05:00,2026-01-15,1,0.55429316,1,0,0,-1,0,True,False,False
2026-01-16 00:00:00,2026-01-15 19:00:00-05:00,2026-01-15,0,0.5584868,1,0,0,-1,0,False,False,True
2026-01-16 01:00:00,2026-01-15 20:00:00-05:00,2026-01-15,1,0.51578057,1,0,0,-1,0,True,False,False
2026-01-16 02:00:00,2026-01-15 21:00:00-05:00,2026-01-15,0,0.5562782,1,0,0,-1,0,False,False,True
2026-01-16 03:00:00,2026-01-15 22:00:00-05:00,2026-01-15,1,0.5080268,1,0,0,-1,0,True,False,False
2026-01-16 04:00:00,2026-01-15 23:00:00-05:00,2026-01-15,1,0.49140474,0,0,0,0,0,False,False,False
2026-01-16 05:00:00,2026-01-16 00:00:00-05:00,2026-01-16,1,0.46221796,0,0,0,0,0,False,False,False
2026-01-16 06:00:00,2026-01-16 01:00:00-05:00,2026-01-16,0,0.4448804,0,0,0,0,0,True,True,True
2026-01-16 07:00:00,2026-01-16 02:00:00-05:00,2026-01-16,1,0.4457024,0,0,0,0,0,False,False,False
2026-01-16 08:00:00,2026-01-16 03:00:00-05:00,2026-01-16,1,0.49323055,0,0,0,0,0,False,False,False
2026-01-16 09:00:00,2026-01-16 04:00:00-05:00,2026-01-16,0,0.49757826,0,0,0,0,0,True,True,True
2026-01-16 10:00:00,2026-01-16 05:00:00-05:00,2026-01-16,1,0.5067139,1,0,0,-1,0,True,False,False
2026-01-16 11:00:00,2026-01-16 06:00:00-05:00,2026-01-16,0,0.5102587,1,0,0,-1,0,False,False,True
2026-01-16 12:00:00,2026-01-16 07:00:00-05:00,2026-01-16,0,0.5843353,1,0,0,-1,0,False,False,True
2026-01-16 13:00:00,2026-01-16 08:00:00-05:00,2026-01-16,0,0.5425463,1,0,0,-1,0,False,False,True
2026-01-16 14:00:00,2026-01-16 09:00:00-05:00,2026-01-16,0,0.5690049,1,0,0,-1,0,False,False,True
2026-01-16 15:00:00,2026-01-16 10:00:00-05:00,2026-01-16,0,0.54829246,1,0,0,-1,0,False,False,True
2026-01-16 16:00:00,2026-01-16 11:00:00-05:00,2026-01-16,1,0.5046336,1,0,0,-1,0,True,False,False
2026-01-16 17:00:00,2026-01-16 12:00:00-05:00,2026-01-16,1,0.5086898,1,0,0,-1,0,True,False,False
2026-01-16 18:00:00,2026-01-16 13:00:00-05:00,2026-01-16,0,0.4823461,0,0,0,0,0,True,True,True
2026-01-16 19:00:00,2026-01-16 14:00:00-05:00,2026-01-16,1,0.4856351,0,0,0,0,0,False,False,False
2026-01-16 20:00:00,2026-01-16 15:00:00-05:00,2026-01-16,0,0.4558552,0,0,0,0,0,True,True,True
2026-01-16 21:00:00,2026-01-16 16:00:00-05:00,2026-01-16,0,0.4780688,0,0,0,0,0,True,True,True
2026-01-16 22:00:00,2026-01-16 17:00:00-05:00,2026-01-16,1,0.48367676,0,0,0,0,0,False,False,False
2026-01-16 23:00:00,2026-01-16 18:00:00-05:00,2026-01-16,1,0.5118516,1,0,0,-1,0,True,False,False
2026-01-17 00:00:00,2026-01-16 19:00:00-05:00,2026-01-16,0,0.5440252,1,0,0,-1,0,False,False,True
2026-01-17 01:00:00,2026-01-16 20:00:00-05:00,2026-01-16,0,0.52165735,1,1,0,1,1,False,False,False
2026-01-17 02:00:00,2026-01-16 21:00:00-05:00,2026-01-16,0,0.57716566,1,1,0,1,1,False,False,False
2026-01-17 03:00:00,2026-01-16 22:00:00-05:00,2026-01-16,1,0.53289396,1,1,0,1,1,True,True,True
2026-01-17 04:00:00,2026-01-16 23:00:00-05:00,2026-01-16,0,0.5077436,1,0,0,-1,0,False,False,True
2026-01-17 05:00:00,2026-01-17 00:00:00-05:00,2026-01-17,1,0.49432057,0,1,0,-1,0,False,False,False
2026-01-17 06:00:00,2026-01-17 01:00:00-05:00,2026-01-17,1,0.50985175,1,1,0,1,1,True,True,True
2026-01-17 07:00:00,2026-01-17 02:00:00-05:00,2026-01-17,1,0.4898242,0,1,0,-1,0,False,False,False
2026-01-17 08:00:00,2026-01-17 03:00:00-05:00,2026-01-17,1,0.45491692,0,1,0,-1,0,False,False,False
2026-01-17 09:00:00,2026-01-17 04:00:00-05:00,2026-01-17,0,0.45771605,0,1,0,-1,0,True,False,True
2026-01-17 10:00:00,2026-01-17 05:00:00-05:00,2026-01-17,0,0.45357528,0,1,0,-1,0,True,False,True
2026-01-17 11:00:00,2026-01-17 06:00:00-05:00,2026-01-17,1,0.4550014,0,1,0,-1,0,False,False,False
2026-01-17 12:00:00,2026-01-17 07:00:00-05:00,2026-01-17,1,0.46508005,0,1,0,-1,0,False,False,False
2026-01-17 13:00:00,2026-01-17 08:00:00-05:00,2026-01-17,0,0.4753723,0,1,0,-1,0,True,False,True
2026-01-17 14:00:00,2026-01-17 09:00:00-05:00,2026-01-17,0,0.49003577,0,0,0,0,0,True,True,True
2026-01-17 15:00:00,2026-01-17 10:00:00-05:00,2026-01-17,0,0.60734934,1,0,0,-1,0,False,False,True
2026-01-17 16:00:00,2026-01-17 11:00:00-05:00,2026-01-17,0,0.5786392,1,0,0,-1,0,False,False,True
2026-01-17 17:00:00,2026-01-17 12:00:00-05:00,2026-01-17,1,0.5754632,1,1,0,1,1,True,True,True
2026-01-17 18:00:00,2026-01-17 13:00:00-05:00,2026-01-17,0,0.59382933,1,1,0,1,1,False,False,False
2026-01-17 19:00:00,2026-01-17 14:00:00-05:00,2026-01-17,0,0.5593247,1,1,0,1,1,False,False,False
2026-01-17 20:00:00,2026-01-17 15:00:00-05:00,2026-01-17,1,0.5365127,1,1,0,1,1,True,True,True
2026-01-17 21:00:00,2026-01-17 16:00:00-05:00,2026-01-17,1,0.50084764,1,1,0,1,1,True,True,True
2026-01-17 22:00:00,2026-01-17 17:00:00-05:00,2026-01-17,1,0.46421826,0,1,0,-1,0,False,False,False
2026-01-17 23:00:00,2026-01-17 18:00:00-05:00,2026-01-17,0,0.48463318,0,1,0,-1,0,True,False,True
2026-01-18 00:00:00,2026-01-17 19:00:00-05:00,2026-01-17,1,0.46844795,0,1,0,-1,0,False,False,False
2026-01-18 01:00:00,2026-01-17 20:00:00-05:00,2026-01-17,1,0.48266262,0,1,0,-1,0,False,False,False
2026-01-18 02:00:00,2026-01-17 21:00:00-05:00,2026-01-17,0,0.49029487,0,1,0,-1,0,True,False,True
2026-01-18 03:00:00,2026-01-17 22:00:00-05:00,2026-01-17,0,0.45225728,0,1,0,-1,0,True,False,True
2026-01-18 04:00:00,2026-01-17 23:00:00-05:00,2026-01-17,0,0.5144419,1,1,0,1,1,False,False,False
2026-01-18 05:00:00,2026-01-18 00:00:00-05:00,2026-01-18,1,0.5160817,1,1,0,1,1,True,True,True
2026-01-18 06:00:00,2026-01-18 01:00:00-05:00,2026-01-18,1,0.50346553,1,1,0,1,1,True,True,True
2026-01-18 07:00:00,2026-01-18 02:00:00-05:00,2026-01-18,0,0.44028476,0,1,0,-1,0,True,False,True
2026-01-18 08:00:00,2026-01-18 03:00:00-05:00,2026-01-18,0,0.46246836,0,1,0,-1,0,True,False,True
2026-01-18 09:00:00,2026-01-18 04:00:00-05:00,2026-01-18,1,0.51622605,1,1,0,1,1,True,True,True
2026-01-18 10:00:00,2026-01-18 05:00:00-05:00,2026-01-18,1,0.51112276,1,1,0,1,1,True,True,True
2026-01-18 11:00:00,2026-01-18 06:00:00-05:00,2026-01-18,0,0.45434904,0,1,0,-1,0,True,False,True
2026-01-18 12:00:00,2026-01-18 07:00:00-05:00,2026-01-18,0,0.52511775,1,1,0,1,1,False,False,False
2026-01-18 13:00:00,2026-01-18 08:00:00-05:00,2026-01-18,1,0.5778929,1,1,0,1,1,True,True,True
2026-01-18 14:00:00,2026-01-18 09:00:00-05:00,2026-01-18,1,0.61074215,1,1,0,1,1,True,True,True
2026-01-18 15:00:00,2026-01-18 10:00:00-05:00,2026-01-18,0,0.57336044,1,1,0,1,1,False,False,False
2026-01-18 16:00:00,2026-01-18 11:00:00-05:00,2026-01-18,0,0.51572573,1,1,0,1,1,False,False,False
2026-01-18 17:00:00,2026-01-18 12:00:00-05:00,2026-01-18,0,0.5084302,1,0,0,-1,0,False,False,True
2026-01-18 18:00:00,2026-01-18 13:00:00-05:00,2026-01-18,0,0.53620636,1,1,0,1,1,False,False,False
2026-01-18 19:00:00,2026-01-18 14:00:00-05:00,2026-01-18,0,0.58746594,1,1,0,1,1,False,False,False
2026-01-18 20:00:00,2026-01-18 15:00:00-05:00,2026-01-18,0,0.53558993,1,1,0,1,1,False,False,False
2026-01-18 21:00:00,2026-01-18 16:00:00-05:00,2026-01-18,1,0.4962998,0,1,0,-1,0,False,False,False
2026-01-18 22:00:00,2026-01-18 17:00:00-05:00,2026-01-18,0,0.5750347,1,0,0,-1,0,False,False,True
2026-01-18 23:00:00,2026-01-18 18:00:00-05:00,2026-01-18,0,0.58212554,1,1,0,1,1,False,False,False
2026-01-19 00:00:00,2026-01-18 19:00:00-05:00,2026-01-18,1,0.5023791,1,1,0,1,1,True,True,True
2026-01-19 01:00:00,2026-01-18 20:00:00-05:00,2026-01-18,0,0.51338583,1,0,0,-1,0,False,False,True
2026-01-19 02:00:00,2026-01-18 21:00:00-05:00,2026-01-18,1,0.4790363,0,0,0,0,0,False,False,False
2026-01-19 03:00:00,2026-01-18 22:00:00-05:00,2026-01-18,0,0.4659165,0,0,0,0,0,True,True,True
2026-01-19 04:00:00,2026-01-18 23:00:00-05:00,2026-01-18,0,0.48015603,0,0,0,0,0,True,True,True
2026-01-19 05:00:00,2026-01-19 00:00:00-05:00,2026-01-19,1,0.4719194,0,0,0,0,0,False,False,False
2026-01-19 06:00:00,2026-01-19 01:00:00-05:00,2026-01-19,0,0.47719255,0,0,0,0,0,True,True,True
2026-01-19 07:00:00,2026-01-19 02:00:00-05:00,2026-01-19,0,0.46883187,0,0,0,0,0,True,True,True
2026-01-19 08:00:00,2026-01-19 03:00:00-05:00,2026-01-19,0,0.5041158,1,0,0,-1,0,False,False,True
2026-01-19 09:00:00,2026-01-19 04:00:00-05:00,2026-01-19,1,0.55327594,1,1,0,1,1,True,True,True
2026-01-19 10:00:00,2026-01-19 05:00:00-05:00,2026-01-19,1,0.51431626,1,1,0,1,1,True,True,True
2026-01-19 11:00:00,2026-01-19 06:00:00-05:00,2026-01-19,0,0.48845842,0,1,0,-1,0,True,False,True
2026-01-19 12:00:00,2026-01-19 07:00:00-05:00,2026-01-19,0,0.548243,1,1,0,1,1,False,False,False
2026-01-19 13:00:00,2026-01-19 08:00:00-05:00,2026-01-19,0,0.56993884,1,1,0,1,1,False,False,False
2026-01-19 14:00:00,2026-01-19 09:00:00-05:00,2026-01-19,1,0.570956,1,1,0,1,1,True,True,True
2026-01-19 15:00:00,2026-01-19 10:00:00-05:00,2026-01-19,1,0.58591735,1,1,0,1,1,True,True,True
2026-01-19 16:00:00,2026-01-19 11:00:00-05:00,2026-01-19,1,0.5654058,1,1,0,1,1,True,True,True
2026-01-19 17:00:00,2026-01-19 12:00:00-05:00,2026-01-19,1,0.53851956,1,1,0,1,1,True,True,True
2026-01-19 18:00:00,2026-01-19 13:00:00-05:00,2026-01-19,1,0.51866657,1,1,0,1,1,True,True,True
2026-01-19 19:00:00,2026-01-19 14:00:00-05:00,2026-01-19,1,0.4805421,0,1,0,-1,0,False,False,False
2026-01-19 20:00:00,2026-01-19 15:00:00-05:00,2026-01-19,0,0.4309969,0,0,0,0,0,True,True,True
2026-01-19 21:00:00,2026-01-19 16:00:00-05:00,2026-01-19,0,0.40472427,0,0,0,0,0,True,True,True
2026-01-19 22:00:00,2026-01-19 17:00:00-05:00,2026-01-19,0,0.43165132,0,1,0,-1,0,True,False,True
2026-01-19 23:00:00,2026-01-19 18:00:00-05:00,2026-01-19,0,0.45249504,0,1,0,-1,0,True,False,True
2026-01-20 00:00:00,2026-01-19 19:00:00-05:00,2026-01-19,1,0.41881704,0,1,0,-1,0,False,False,False
2026-01-20 01:00:00,2026-01-19 20:00:00-05:00,2026-01-19,0,0.4950903,0,1,0,-1,0,True,False,True
2026-01-20 02:00:00,2026-01-19 21:00:00-05:00,2026-01-19,0,0.52289253,1,1,0,1,1,False,False,False
2026-01-20 03:00:00,2026-01-19 22:00:00-05:00,2026-01-19,0,0.5152514,1,1,0,1,1,False,False,False
2026-01-20 04:00:00,2026-01-19 23:00:00-05:00,2026-01-19,1,0.52619386,1,1,0,1,1,True,True,True
2026-01-20 05:00:00,2026-01-20 00:00:00-05:00,2026-01-20,1,0.4920829,0,1,0,-1,0,False,False,False
2026-01-20 06:00:00,2026-01-20 01:00:00-05:00,2026-01-20,0,0.44386265,0,1,0,-1,0,True,False,True
2026-01-20 07:00:00,2026-01-20 02:00:00-05:00,2026-01-20,1,0.4548454,0,1,0,-1,0,False,False,False
2026-01-20 08:00:00,2026-01-20 03:00:00-05:00,2026-01-20,1,0.50409263,1,1,0,1,1,True,True,True
2026-01-20 09:00:00,2026-01-20 04:00:00-05:00,2026-01-20,1,0.4486903,0,1,0,-1,0,False,False,False
2026-01-20 10:00:00,2026-01-20 05:00:00-05:00,2026-01-20,0,0.44015622,0,1,0,-1,0,True,False,True
2026-01-20 11:00:00,2026-01-20 06:00:00-05:00,2026-01-20,1,0.46409336,0,1,0,-1,0,False,False,False
2026-01-20 12:00:00,2026-01-20 07:00:00-05:00,2026-01-20,0,0.47176668,0,1,0,-1,0,True,False,True
2026-01-20 13:00:00,2026-01-20 08:00:00-05:00,2026-01-20,0,0.44548807,0,1,0,-1,0,True,False,True
2026-01-20 14:00:00,2026-01-20 09:00:00-05:00,2026-01-20,1,0.45053762,0,1,0,-1,0,False,False,False
2026-01-20 15:00:00,2026-01-20 10:00:00-05:00,2026-01-20,1,0.481055,0,1,0,-1,0,False,False,False
2026-01-20 16:00:00,2026-01-20 11:00:00-05:00,2026-01-20,0,0.5467605,1,1,0,1,1,False,False,False
2026-01-20 17:00:00,2026-01-20 12:00:00-05:00,2026-01-20,0,0.5497743,1,1,0,1,1,False,False,False
2026-01-20 18:00:00,2026-01-20 13:00:00-05:00,2026-01-20,1,0.531139,1,0,0,-1,0,True,False,False
2026-01-20 19:00:00,2026-01-20 14:00:00-05:00,2026-01-20,1,0.48030645,0,0,0,0,0,False,False,False
2026-01-20 20:00:00,2026-01-20 15:00:00-05:00,2026-01-20,1,0.5283993,1,0,0,-1,0,True,False,False
2026-01-20 21:00:00,2026-01-20 16:00:00-05:00,2026-01-20,0,0.5042049,1,1,0,1,1,False,False,False
2026-01-20 22:00:00,2026-01-20 17:00:00-05:00,2026-01-20,1,0.46221802,0,1,0,-1,0,False,False,False
2026-01-20 23:00:00,2026-01-20 18:00:00-05:00,2026-01-20,1,0.45490173,0,1,0,-1,0,False,False,False
2026-01-21 00:00:00,2026-01-20 19:00:00-05:00,2026-01-20,1,0.4481478,0,1,0,-1,0,False,False,False
2026-01-21 01:00:00,2026-01-20 20:00:00-05:00,2026-01-20,0,0.4967737,0,1,0,-1,0,True,False,True
2026-01-21 02:00:00,2026-01-20 21:00:00-05:00,2026-01-20,1,0.5263554,1,1,0,1,1,True,True,True
2026-01-21 03:00:00,2026-01-20 22:00:00-05:00,2026-01-20,0,0.46894258,0,1,0,-1,0,True,False,True
2026-01-21 04:00:00,2026-01-20 23:00:00-05:00,2026-01-20,0,0.4519449,0,1,0,-1,0,True,False,True
2026-01-21 05:00:00,2026-01-21 00:00:00-05:00,2026-01-21,0,0.5209714,1,1,0,1,1,False,False,False
2026-01-21 06:00:00,2026-01-21 01:00:00-05:00,2026-01-21,1,0.56909335,1,0,0,-1,0,True,False,False
2026-01-21 07:00:00,2026-01-21 02:00:00-05:00,2026-01-21,1,0.51435,1,1,0,1,1,True,True,True
2026-01-21 08:00:00,2026-01-21 03:00:00-05:00,2026-01-21,0,0.47510275,0,1,0,-1,0,True,False,True
2026-01-21 09:00:00,2026-01-21 04:00:00-05:00,2026-01-21,1,0.47119588,0,1,0,-1,0,False,False,False
2026-01-21 10:00:00,2026-01-21 05:00:00-05:00,2026-01-21,0,0.45829907,0,1,0,-1,0,True,False,True
2026-01-21 11:00:00,2026-01-21 06:00:00-05:00,2026-01-21,0,0.46126115,0,1,0,-1,0,True,False,True
2026-01-21 12:00:00,2026-01-21 07:00:00-05:00,2026-01-21,1,0.49299902,0,1,0,-1,0,False,False,False
2026-01-21 13:00:00,2026-01-21 08:00:00-05:00,2026-01-21,1,0.4642857,0,1,0,-1,0,False,False,False
2026-01-21 14:00:00,2026-01-21 09:00:00-05:00,2026-01-21,0,0.46757126,0,1,0,-1,0,True,False,True
2026-01-21 15:00:00,2026-01-21 10:00:00-05:00,2026-01-21,1,0.51933724,1,0,0,-1,0,True,False,False
2026-01-21 16:00:00,2026-01-21 11:00:00-05:00,2026-01-21,0,0.52031684,1,0,0,-1,0,False,False,True
2026-01-21 17:00:00,2026-01-21 12:00:00-05:00,2026-01-21,1,0.4510923,0,1,0,-1,0,False,False,False
2026-01-21 18:00:00,2026-01-21 13:00:00-05:00,2026-01-21,1,0.47569612,0,0,0,0,0,False,False,False
2026-01-21 19:00:00,2026-01-21 14:00:00-05:00,2026-01-21,0,0.52261287,1,1,0,1,1,False,False,False
2026-01-21 20:00:00,2026-01-21 15:00:00-05:00,2026-01-21,0,0.5309036,1,0,0,-1,0,False,False,True
2026-01-21 21:00:00,2026-01-21 16:00:00-05:00,2026-01-21,1,0.5570105,1,1,0,1,1,True,True,True
2026-01-21 22:00:00,2026-01-21 17:00:00-05:00,2026-01-21,0,0.49707052,0,1,0,-1,0,True,False,True
2026-01-21 23:00:00,2026-01-21 18:00:00-05:00,2026-01-21,0,0.49758816,0,1,0,-1,0,True,False,True
2026-01-22 00:00:00,2026-01-21 19:00:00-05:00,2026-01-21,1,0.53757817,1,1,0,1,1,True,True,True
2026-01-22 01:00:00,2026-01-21 20:00:00-05:00,2026-01-21,1,0.57986003,1,1,0,1,1,True,True,True
2026-01-22 02:00:00,2026-01-21 21:00:00-05:00,2026-01-21,1,0.53082293,1,1,0,1,1,True,True,True
2026-01-22 03:00:00,2026-01-21 22:00:00-05:00,2026-01-21,1,0.50052387,1,1,0,1,1,True,True,True
2026-01-22 04:00:00,2026-01-21 23:00:00-05:00,2026-01-21,0,0.47824442,0,1,0,-1,0,True,False,True
2026-01-22 05:00:00,2026-01-22 00:00:00-05:00,2026-01-22,1,0.4760107,0,1,0,-1,0,False,False,False
2026-01-22 06:00:00,2026-01-22 01:00:00-05:00,2026-01-22,1,0.50084823,1,1,0,1,1,True,True,True
2026-01-22 07:00:00,2026-01-22 02:00:00-05:00,2026-01-22,1,0.45673624,0,1,0,-1,0,False,False,False
2026-01-22 08:00:00,2026-01-22 03:00:00-05:00,2026-01-22,0,0.44737244,0,1,0,-1,0,True,False,True
2026-01-22 09:00:00,2026-01-22 04:00:00-05:00,2026-01-22,0,0.4425855,0,0,0,0,0,True,True,True
2026-01-22 10:00:00,2026-01-22 05:00:00-05:00,2026-01-22,0,0.41352358,0,0,0,0,0,True,True,True
2026-01-22 11:00:00,2026-01-22 06:00:00-05:00,2026-01-22,0,0.47900316,0,1,0,-1,0,True,False,True
2026-01-22 12:00:00,2026-01-22 07:00:00-05:00,2026-01-22,1,0.5056432,1,0,0,-1,0,True,False,False
2026-01-22 13:00:00,2026-01-22 08:00:00-05:00,2026-01-22,0,0.5437085,1,1,0,1,1,False,False,False
2026-01-22 14:00:00,2026-01-22 09:00:00-05:00,2026-01-22,0,0.57784945,1,1,0,1,1,False,False,False
2026-01-22 15:00:00,2026-01-22 10:00:00-05:00,2026-01-22,1,0.55117744,1,1,0,1,1,True,True,True
2026-01-22 16:00:00,2026-01-22 11:00:00-05:00,2026-01-22,1,0.5499753,1,1,0,1,1,True,True,True
2026-01-22 17:00:00,2026-01-22 12:00:00-05:00,2026-01-22,1,0.48403728,0,0,0,0,0,False,False,False
2026-01-22 18:00:00,2026-01-22 13:00:00-05:00,2026-01-22,1,0.5378152,1,0,0,-1,0,True,False,False
2026-01-22 19:00:00,2026-01-22 14:00:00-05:00,2026-01-22,0,0.5124629,1,0,0,-1,0,False,False,True
2026-01-22 20:00:00,2026-01-22 15:00:00-05:00,2026-01-22,0,0.4786928,0,0,0,0,0,True,True,True
2026-01-22 21:00:00,2026-01-22 16:00:00-05:00,2026-01-22,0,0.47483838,0,1,0,-1,0,True,False,True
2026-01-22 22:00:00,2026-01-22 17:00:00-05:00,2026-01-22,1,0.4948368,0,1,0,-1,0,False,False,False
2026-01-22 23:00:00,2026-01-22 18:00:00-05:00,2026-01-22,0,0.50176257,1,1,0,1,1,False,False,False
2026-01-23 00:00:00,2026-01-22 19:00:00-05:00,2026-01-22,1,0.49785018,0,1,0,-1,0,False,False,False
2026-01-23 01:00:00,2026-01-22 20:00:00-05:00,2026-01-22,0,0.4707971,0,1,0,-1,0,True,False,True
2026-01-23 02:00:00,2026-01-22 21:00:00-05:00,2026-01-22,0,0.4584682,0,1,0,-1,0,True,False,True
2026-01-23 03:00:00,2026-01-22 22:00:00-05:00,2026-01-22,0,0.47485125,0,1,0,-1,0,True,False,True
2026-01-23 04:00:00,2026-01-22 23:00:00-05:00,2026-01-22,1,0.48357454,0,1,0,-1,0,False,False,False
2026-01-23 05:00:00,2026-01-23 00:00:00-05:00,2026-01-23,0,0.5074494,1,1,0,1,1,False,False,False
2026-01-23 06:00:00,2026-01-23 01:00:00-05:00,2026-01-23,1,0.49947768,0,1,0,-1,0,False,False,False
2026-01-23 07:00:00,2026-01-23 02:00:00-05:00,2026-01-23,1,0.5284809,1,1,0,1,1,True,True,True
2026-01-23 08:00:00,2026-01-23 03:00:00-05:00,2026-01-23,0,0.49399173,0,1,0,-1,0,True,False,True
2026-01-23 09:00:00,2026-01-23 04:00:00-05:00,2026-01-23,0,0.45683447,0,1,0,-1,0,True,False,True
2026-01-23 10:00:00,2026-01-23 05:00:00-05:00,2026-01-23,1,0.44759712,0,1,0,-1,0,False,False,False
2026-01-23 11:00:00,2026-01-23 06:00:00-05:00,2026-01-23,1,0.46368843,0,1,0,-1,0,False,False,False
2026-01-23 12:00:00,2026-01-23 07:00:00-05:00,2026-01-23,1,0.4522303,0,1,0,-1,0,False,False,False
2026-01-23 13:00:00,2026-01-23 08:00:00-05:00,2026-01-23,0,0.5273792,1,1,0,1,1,False,False,False
2026-01-23 14:00:00,2026-01-23 09:00:00-05:00,2026-01-23,1,0.49696228,0,1,0,-1,0,False,False,False
2026-01-23 15:00:00,2026-01-23 10:00:00-05:00,2026-01-23,0,0.5127735,1,1,0,1,1,False,False,False
2026-01-23 16:00:00,2026-01-23 11:00:00-05:00,2026-01-23,0,0.5816189,1,1,0,1,1,False,False,False
2026-01-23 17:00:00,2026-01-23 12:00:00-05:00,2026-01-23,0,0.58383,1,1,0,1,1,False,False,False
2026-01-23 18:00:00,2026-01-23 13:00:00-05:00,2026-01-23,1,0.5591273,1,1,0,1,1,True,True,True
2026-01-23 19:00:00,2026-01-23 14:00:00-05:00,2026-01-23,0,0.5854377,1,1,0,1,1,False,False,False
2026-01-23 20:00:00,2026-01-23 15:00:00-05:00,2026-01-23,0,0.5666198,1,1,0,1,1,False,False,False
2026-01-23 21:00:00,2026-01-23 16:00:00-05:00,2026-01-23,0,0.52965194,1,1,0,1,1,False,False,False
2026-01-23 22:00:00,2026-01-23 17:00:00-05:00,2026-01-23,0,0.5488747,1,1,0,1,1,False,False,False
2026-01-23 23:00:00,2026-01-23 18:00:00-05:00,2026-01-23,1,0.56132966,1,1,0,1,1,True,True,True
2026-01-24 00:00:00,2026-01-23 19:00:00-05:00,2026-01-23,1,0.5149501,1,1,0,1,1,True,True,True
2026-01-24 01:00:00,2026-01-23 20:00:00-05:00,2026-01-23,0,0.5064845,1,1,0,1,1,False,False,False
2026-01-24 02:00:00,2026-01-23 21:00:00-05:00,2026-01-23,1,0.46972445,0,1,0,-1,0,False,False,False
2026-01-24 03:00:00,2026-01-23 22:00:00-05:00,2026-01-23,1,0.47380736,0,1,0,-1,0,False,False,False
2026-01-24 04:00:00,2026-01-23 23:00:00-05:00,2026-01-23,0,0.46636635,0,1,0,-1,0,True,False,True
2026-01-24 05:00:00,2026-01-24 00:00:00-05:00,2026-01-24,0,0.44455072,0,1,0,-1,0,True,False,True
2026-01-24 06:00:00,2026-01-24 01:00:00-05:00,2026-01-24,0,0.4378892,0,1,0,-1,0,True,False,True
2026-01-24 07:00:00,2026-01-24 02:00:00-05:00,2026-01-24,0,0.49291152,0,1,0,-1,0,True,False,True
2026-01-24 08:00:00,2026-01-24 03:00:00-05:00,2026-01-24,0,0.5153457,1,1,0,1,1,False,False,False
2026-01-24 09:00:00,2026-01-24 04:00:00-05:00,2026-01-24,0,0.50489914,1,1,0,1,1,False,False,False
2026-01-24 10:00:00,2026-01-24 05:00:00-05:00,2026-01-24,1,0.5075944,1,1,0,1,1,True,True,True
2026-01-24 11:00:00,2026-01-24 06:00:00-05:00,2026-01-24,0,0.6229508,1,1,0,1,1,False,False,False
2026-01-24 12:00:00,2026-01-24 07:00:00-05:00,2026-01-24,0,0.5855234,1,1,0,1,1,False,False,False
2026-01-24 13:00:00,2026-01-24 08:00:00-05:00,2026-01-24,1,0.5532754,1,1,0,1,1,True,True,True
2026-01-24 14:00:00,2026-01-24 09:00:00-05:00,2026-01-24,1,0.5289959,1,0,0,-1,0,True,False,False
2026-01-24 15:00:00,2026-01-24 10:00:00-05:00,2026-01-24,1,0.49748245,0,0,0,0,0,False,False,False
2026-01-24 16:00:00,2026-01-24 11:00:00-05:00,2026-01-24,0,0.46435258,0,0,0,0,0,True,True,True
2026-01-24 17:00:00,2026-01-24 12:00:00-05:00,2026-01-24,0,0.44683295,0,1,0,-1,0,True,False,True
2026-01-24 18:00:00,2026-01-24 13:00:00-05:00,2026-01-24,1,0.45363894,0,1,0,-1,0,False,False,False
2026-01-24 19:00:00,2026-01-24 14:00:00-05:00,2026-01-24,1,0.5047467,1,0,0,-1,0,True,False,False
2026-01-24 20:00:00,2026-01-24 15:00:00-05:00,2026-01-24,1,0.44000623,0,0,0,0,0,False,False,False
2026-01-24 21:00:00,2026-01-24 16:00:00-05:00,2026-01-24,1,0.4814367,0,0,0,0,0,False,False,False
2026-01-24 22:00:00,2026-01-24 17:00:00-05:00,2026-01-24,0,0.46426073,0,0,0,0,0,True,True,True
2026-01-24 23:00:00,2026-01-24 18:00:00-05:00,2026-01-24,0,0.44996247,0,0,0,0,0,True,True,True
2026-01-25 00:00:00,2026-01-24 19:00:00-05:00,2026-01-24,1,0.47704104,0,1,0,-1,0,False,False,False
2026-01-25 01:00:00,2026-01-24 20:00:00-05:00,2026-01-24,0,0.52445495,1,0,0,-1,0,False,False,True
2026-01-25 02:00:00,2026-01-24 21:00:00-05:00,2026-01-24,0,0.4987614,0,1,0,-1,0,True,False,True
2026-01-25 03:00:00,2026-01-24 22:00:00-05:00,2026-01-24,1,0.5457757,1,1,0,1,1,True,True,True
2026-01-25 04:00:00,2026-01-24 23:00:00-05:00,2026-01-24,1,0.52091444,1,1,0,1,1,True,True,True
2026-01-25 05:00:00,2026-01-25 00:00:00-05:00,2026-01-25,0,0.5040618,1,1,0,1,1,False,False,False
2026-01-25 06:00:00,2026-01-25 01:00:00-05:00,2026-01-25,0,0.4632134,0,1,0,-1,0,True,False,True
2026-01-25 07:00:00,2026-01-25 02:00:00-05:00,2026-01-25,1,0.46248558,0,1,0,-1,0,False,False,False
2026-01-25 08:00:00,2026-01-25 03:00:00-05:00,2026-01-25,1,0.48469254,0,1,0,-1,0,False,False,False
2026-01-25 09:00:00,2026-01-25 04:00:00-05:00,2026-01-25,0,0.46292534,0,1,0,-1,0,True,False,True
2026-01-25 10:00:00,2026-01-25 05:00:00-05:00,2026-01-25,0,0.44414967,0,1,0,-1,0,True,False,True
2026-01-25 11:00:00,2026-01-25 06:00:00-05:00,2026-01-25,1,0.46307206,0,1,0,-1,0,False,False,False
2026-01-25 12:00:00,2026-01-25 07:00:00-05:00,2026-01-25,1,0.4829153,0,1,0,-1,0,False,False,False
2026-01-25 13:00:00,2026-01-25 08:00:00-05:00,2026-01-25,1,0.4569906,0,1,0,-1,0,False,False,False
2026-01-25 14:00:00,2026-01-25 09:00:00-05:00,2026-01-25,1,0.44465676,0,1,0,-1,0,False,False,False
2026-01-25 15:00:00,2026-01-25 10:00:00-05:00,2026-01-25,1,0.4582169,0,1,0,-1,0,False,False,False
2026-01-25 16:00:00,2026-01-25 11:00:00-05:00,2026-01-25,1,0.4422594,0,1,0,-1,0,False,False,False
2026-01-25 17:00:00,2026-01-25 12:00:00-05:00,2026-01-25,0,0.44241613,0,1,0,-1,0,True,False,True
2026-01-25 18:00:00,2026-01-25 13:00:00-05:00,2026-01-25,0,0.44766986,0,1,0,-1,0,True,False,True
2026-01-25 19:00:00,2026-01-25 14:00:00-05:00,2026-01-25,0,0.4483085,0,1,0,-1,0,True,False,True
2026-01-25 20:00:00,2026-01-25 15:00:00-05:00,2026-01-25,0,0.491638,0,1,0,-1,0,True,False,True
2026-01-25 21:00:00,2026-01-25 16:00:00-05:00,2026-01-25,0,0.49061048,0,1,0,-1,0,True,False,True
2026-01-25 22:00:00,2026-01-25 17:00:00-05:00,2026-01-25,0,0.5258992,1,1,0,1,1,False,False,False
2026-01-25 23:00:00,2026-01-25 18:00:00-05:00,2026-01-25,0,0.520721,1,1,0,1,1,False,False,False
2026-01-26 00:00:00,2026-01-25 19:00:00-05:00,2026-01-25,0,0.5159272,1,0,0,-1,0,False,False,True
2026-01-26 01:00:00,2026-01-25 20:00:00-05:00,2026-01-25,1,0.5431046,1,0,0,-1,0,True,False,False
2026-01-26 02:00:00,2026-01-25 21:00:00-05:00,2026-01-25,0,0.51656026,1,0,0,-1,0,False,False,True
2026-01-26 03:00:00,2026-01-25 22:00:00-05:00,2026-01-25,0,0.48795614,0,1,0,-1,0,True,False,True
2026-01-26 04:00:00,2026-01-25 23:00:00-05:00,2026-01-25,0,0.50463694,1,1,0,1,1,False,False,False
2026-01-26 05:00:00,2026-01-26 00:00:00-05:00,2026-01-26,1,0.5487247,1,0,0,-1,0,True,False,False
2026-01-26 06:00:00,2026-01-26 01:00:00-05:00,2026-01-26,1,0.5424091,1,0,0,-1,0,True,False,False
2026-01-26 07:00:00,2026-01-26 02:00:00-05:00,2026-01-26,0,0.47699133,0,0,0,0,0,True,True,True
2026-01-26 08:00:00,2026-01-26 03:00:00-05:00,2026-01-26,1,0.46807176,0,0,0,0,0,False,False,False
2026-01-26 09:00:00,2026-01-26 04:00:00-05:00,2026-01-26,1,0.5724977,1,1,0,1,1,True,True,True
2026-01-26 10:00:00,2026-01-26 05:00:00-05:00,2026-01-26,1,0.5247946,1,1,0,1,1,True,True,True
2026-01-26 11:00:00,2026-01-26 06:00:00-05:00,2026-01-26,0,0.46081114,0,0,0,0,0,True,True,True
2026-01-26 12:00:00,2026-01-26 07:00:00-05:00,2026-01-26,1,0.4425639,0,0,0,0,0,False,False,False
2026-01-26 13:00:00,2026-01-26 08:00:00-05:00,2026-01-26,1,0.47229946,0,1,0,-1,0,False,False,False
2026-01-26 14:00:00,2026-01-26 09:00:00-05:00,2026-01-26,1,0.49050438,0,1,0,-1,0,False,False,False
2026-01-26 15:00:00,2026-01-26 10:00:00-05:00,2026-01-26,0,0.49104828,0,1,0,-1,0,True,False,True
2026-01-26 16:00:00,2026-01-26 11:00:00-05:00,2026-01-26,0,0.45145187,0,1,0,-1,0,True,False,True
2026-01-26 17:00:00,2026-01-26 12:00:00-05:00,2026-01-26,0,0.4638353,0,0,0,0,0,True,True,True
2026-01-26 18:00:00,2026-01-26 13:00:00-05:00,2026-01-26,0,0.4618484,0,1,0,-1,0,True,False,True
2026-01-26 19:00:00,2026-01-26 14:00:00-05:00,2026-01-26,1,0.5018996,1,1,0,1,1,True,True,True
2026-01-26 20:00:00,2026-01-26 15:00:00-05:00,2026-01-26,1,0.5166753,1,1,0,1,1,True,True,True
2026-01-26 21:00:00,2026-01-26 16:00:00-05:00,2026-01-26,0,0.52520907,1,1,0,1,1,False,False,False
2026-01-26 22:00:00,2026-01-26 17:00:00-05:00,2026-01-26,0,0.5878727,1,1,0,1,1,False,False,False
2026-01-26 23:00:00,2026-01-26 18:00:00-05:00,2026-01-26,1,0.54239446,1,1,0,1,1,True,True,True
2026-01-27 00:00:00,2026-01-26 19:00:00-05:00,2026-01-26,0,0.53454095,1,1,0,1,1,False,False,False
2026-01-27 01:00:00,2026-01-26 20:00:00-05:00,2026-01-26,1,0.467306,0,1,0,-1,0,False,False,False
2026-01-27 02:00:00,2026-01-26 21:00:00-05:00,2026-01-26,0,0.46864188,0,1,0,-1,0,True,False,True
2026-01-27 03:00:00,2026-01-26 22:00:00-05:00,2026-01-26,1,0.46822935,0,1,0,-1,0,False,False,False
2026-01-27 04:00:00,2026-01-26 23:00:00-05:00,2026-01-26,0,0.49130705,0,1,0,-1,0,True,False,True
2026-01-27 05:00:00,2026-01-27 00:00:00-05:00,2026-01-27,0,0.46864477,0,1,0,-1,0,True,False,True
2026-01-27 06:00:00,2026-01-27 01:00:00-05:00,2026-01-27,1,0.486711,0,1,0,-1,0,False,False,False
2026-01-27 07:00:00,2026-01-27 02:00:00-05:00,2026-01-27,1,0.5504777,1,1,0,1,1,True,True,True
2026-01-27 08:00:00,2026-01-27 03:00:00-05:00,2026-01-27,0,0.4907592,0,1,0,-1,0,True,False,True
2026-01-27 09:00:00,2026-01-27 04:00:00-05:00,2026-01-27,0,0.4668024,0,1,0,-1,0,True,False,True
2026-01-27 10:00:00,2026-01-27 05:00:00-05:00,2026-01-27,0,0.50845945,1,1,0,1,1,False,False,False
2026-01-27 11:00:00,2026-01-27 06:00:00-05:00,2026-01-27,1,0.5608594,1,1,0,1,1,True,True,True
2026-01-27 12:00:00,2026-01-27 07:00:00-05:00,2026-01-27,0,0.51024324,1,1,0,1,1,False,False,False
2026-01-27 13:00:00,2026-01-27 08:00:00-05:00,2026-01-27,0,0.51232666,1,1,0,1,1,False,False,False
2026-01-27 14:00:00,2026-01-27 09:00:00-05:00,2026-01-27,0,0.4854087,0,1,0,-1,0,True,False,True
2026-01-27 15:00:00,2026-01-27 10:00:00-05:00,2026-01-27,0,0.48897353,0,1,0,-1,0,True,False,True
2026-01-27 16:00:00,2026-01-27 11:00:00-05:00,2026-01-27,0,0.46034786,0,1,0,-1,0,True,False,True
2026-01-27 17:00:00,2026-01-27 12:00:00-05:00,2026-01-27,1,0.5265957,1,1,0,1,1,True,True,True
2026-01-27 18:00:00,2026-01-27 13:00:00-05:00,2026-01-27,1,0.5300391,1,1,0,1,1,True,True,True
2026-01-27 19:00:00,2026-01-27 14:00:00-05:00,2026-01-27,0,0.49372414,0,1,0,-1,0,True,False,True
2026-01-27 20:00:00,2026-01-27 15:00:00-05:00,2026-01-27,1,0.4988915,0,1,0,-1,0,False,False,False
2026-01-27 21:00:00,2026-01-27 16:00:00-05:00,2026-01-27,1,0.53798217,1,1,0,1,1,True,True,True
2026-01-27 22:00:00,2026-01-27 17:00:00-05:00,2026-01-27,0,0.5018221,1,1,0,1,1,False,False,False
2026-01-27 23:00:00,2026-01-27 18:00:00-05:00,2026-01-27,0,0.49853957,0,1,0,-1,0,True,False,True
2026-01-28 00:00:00,2026-01-27 19:00:00-05:00,2026-01-27,1,0.5263078,1,1,0,1,1,True,True,True
2026-01-28 01:00:00,2026-01-27 20:00:00-05:00,2026-01-27,0,0.48831585,0,1,0,-1,0,True,False,True
2026-01-28 02:00:00,2026-01-27 21:00:00-05:00,2026-01-27,1,0.53863657,1,1,0,1,1,True,True,True
2026-01-28 03:00:00,2026-01-27 22:00:00-05:00,2026-01-27,0,0.5574659,1,1,0,1,1,False,False,False
2026-01-28 04:00:00,2026-01-27 23:00:00-05:00,2026-01-27,0,0.529262,1,1,0,1,1,False,False,False
2026-01-28 05:00:00,2026-01-28 00:00:00-05:00,2026-01-28,0,0.46035758,0,1,0,-1,0,True,False,True
2026-01-28 06:00:00,2026-01-28 01:00:00-05:00,2026-01-28,1,0.5224445,1,1,0,1,1,True,True,True
2026-01-28 07:00:00,2026-01-28 02:00:00-05:00,2026-01-28,0,0.51074743,1,1,0,1,1,False,False,False
2026-01-28 08:00:00,2026-01-28 03:00:00-05:00,2026-01-28,0,0.519096,1,1,0,1,1,False,False,False
2026-01-28 09:00:00,2026-01-28 04:00:00-05:00,2026-01-28,1,0.53050256,1,1,0,1,1,True,True,True
2026-01-28 10:00:00,2026-01-28 05:00:00-05:00,2026-01-28,1,0.54665875,1,1,0,1,1,True,True,True
2026-01-28 11:00:00,2026-01-28 06:00:00-05:00,2026-01-28,1,0.5373869,1,1,0,1,1,True,True,True
2026-01-28 12:00:00,2026-01-28 07:00:00-05:00,2026-01-28,1,0.53682816,1,1,0,1,1,True,True,True
2026-01-28 13:00:00,2026-01-28 08:00:00-05:00,2026-01-28,1,0.50733596,1,1,0,1,1,True,True,True
2026-01-28 14:00:00,2026-01-28 09:00:00-05:00,2026-01-28,0,0.466285,0,1,0,-1,0,True,False,True
1 timestamp_utc timestamp_et date true_label dlinear_prob dlinear_pred nbeats_pred ttm_pred two_model_pred three_model_pred dlinear_correct two_model_correct three_model_correct
2 2026-01-08 00:00:00 2026-01-07 19:00:00-05:00 2026-01-07 1 0.57705873 1 0 1 -1 1 True False True
3 2026-01-08 01:00:00 2026-01-07 20:00:00-05:00 2026-01-07 0 0.49304762 0 0 1 0 0 True True True
4 2026-01-08 02:00:00 2026-01-07 21:00:00-05:00 2026-01-07 0 0.47215617 0 0 0 0 0 True True True
5 2026-01-08 03:00:00 2026-01-07 22:00:00-05:00 2026-01-07 1 0.46118113 0 0 0 0 0 False False False
6 2026-01-08 04:00:00 2026-01-07 23:00:00-05:00 2026-01-07 0 0.45929378 0 1 1 -1 1 True False False
7 2026-01-08 05:00:00 2026-01-08 00:00:00-05:00 2026-01-08 1 0.47178885 0 0 0 0 0 False False False
8 2026-01-08 06:00:00 2026-01-08 01:00:00-05:00 2026-01-08 1 0.5306643 1 1 1 1 1 True True True
9 2026-01-08 07:00:00 2026-01-08 02:00:00-05:00 2026-01-08 0 0.4891925 0 1 1 -1 1 True False False
10 2026-01-08 08:00:00 2026-01-08 03:00:00-05:00 2026-01-08 0 0.5191637 1 0 1 -1 1 False False False
11 2026-01-08 09:00:00 2026-01-08 04:00:00-05:00 2026-01-08 0 0.47131988 0 1 1 -1 1 True False False
12 2026-01-08 10:00:00 2026-01-08 05:00:00-05:00 2026-01-08 0 0.47046715 0 0 0 0 0 True True True
13 2026-01-08 11:00:00 2026-01-08 06:00:00-05:00 2026-01-08 1 0.5125343 1 0 1 -1 1 True False True
14 2026-01-08 12:00:00 2026-01-08 07:00:00-05:00 2026-01-08 1 0.5334059 1 1 1 1 1 True True True
15 2026-01-08 13:00:00 2026-01-08 08:00:00-05:00 2026-01-08 1 0.49018916 0 1 1 -1 1 False False True
16 2026-01-08 14:00:00 2026-01-08 09:00:00-05:00 2026-01-08 0 0.50079376 1 1 1 1 1 False False False
17 2026-01-08 15:00:00 2026-01-08 10:00:00-05:00 2026-01-08 1 0.46582156 0 1 1 -1 1 False False True
18 2026-01-08 16:00:00 2026-01-08 11:00:00-05:00 2026-01-08 1 0.46240616 0 1 1 -1 1 False False True
19 2026-01-08 17:00:00 2026-01-08 12:00:00-05:00 2026-01-08 0 0.48750654 0 1 1 -1 1 True False False
20 2026-01-08 18:00:00 2026-01-08 13:00:00-05:00 2026-01-08 0 0.47014055 0 1 1 -1 1 True False False
21 2026-01-08 19:00:00 2026-01-08 14:00:00-05:00 2026-01-08 1 0.49040872 0 1 1 -1 1 False False True
22 2026-01-08 20:00:00 2026-01-08 15:00:00-05:00 2026-01-08 0 0.5273022 1 1 1 1 1 False False False
23 2026-01-08 21:00:00 2026-01-08 16:00:00-05:00 2026-01-08 1 0.5106931 1 1 1 1 1 True True True
24 2026-01-08 22:00:00 2026-01-08 17:00:00-05:00 2026-01-08 0 0.5778609 1 1 1 1 1 False False False
25 2026-01-08 23:00:00 2026-01-08 18:00:00-05:00 2026-01-08 1 0.54889923 1 1 1 1 1 True True True
26 2026-01-09 00:00:00 2026-01-08 19:00:00-05:00 2026-01-08 1 0.51507455 1 1 1 1 1 True True True
27 2026-01-09 01:00:00 2026-01-08 20:00:00-05:00 2026-01-08 0 0.5170575 1 1 1 1 1 False False False
28 2026-01-09 02:00:00 2026-01-08 21:00:00-05:00 2026-01-08 1 0.4717993 0 1 1 -1 1 False False True
29 2026-01-09 03:00:00 2026-01-08 22:00:00-05:00 2026-01-08 0 0.5043716 1 1 1 1 1 False False False
30 2026-01-09 04:00:00 2026-01-08 23:00:00-05:00 2026-01-08 0 0.51360196 1 1 1 1 1 False False False
31 2026-01-09 05:00:00 2026-01-09 00:00:00-05:00 2026-01-09 1 0.469029 0 1 1 -1 1 False False True
32 2026-01-09 06:00:00 2026-01-09 01:00:00-05:00 2026-01-09 1 0.50669587 1 1 1 1 1 True True True
33 2026-01-09 07:00:00 2026-01-09 02:00:00-05:00 2026-01-09 0 0.5564795 1 1 1 1 1 False False False
34 2026-01-09 08:00:00 2026-01-09 03:00:00-05:00 2026-01-09 1 0.5820567 1 1 1 1 1 True True True
35 2026-01-09 09:00:00 2026-01-09 04:00:00-05:00 2026-01-09 1 0.5504754 1 1 1 1 1 True True True
36 2026-01-09 10:00:00 2026-01-09 05:00:00-05:00 2026-01-09 0 0.53064233 1 1 1 1 1 False False False
37 2026-01-09 11:00:00 2026-01-09 06:00:00-05:00 2026-01-09 0 0.50407845 1 1 1 1 1 False False False
38 2026-01-09 12:00:00 2026-01-09 07:00:00-05:00 2026-01-09 0 0.4576875 0 1 1 -1 1 True False False
39 2026-01-09 13:00:00 2026-01-09 08:00:00-05:00 2026-01-09 1 0.5189501 1 0 1 -1 1 True False True
40 2026-01-09 14:00:00 2026-01-09 09:00:00-05:00 2026-01-09 1 0.50425255 1 1 1 1 1 True True True
41 2026-01-09 15:00:00 2026-01-09 10:00:00-05:00 2026-01-09 0 0.45628017 0 1 1 -1 1 True False False
42 2026-01-09 16:00:00 2026-01-09 11:00:00-05:00 2026-01-09 1 0.44883052 0 0 1 0 0 False False False
43 2026-01-09 17:00:00 2026-01-09 12:00:00-05:00 2026-01-09 1 0.46907404 0 1 1 -1 1 False False True
44 2026-01-09 18:00:00 2026-01-09 13:00:00-05:00 2026-01-09 1 0.48777947 0 1 1 -1 1 False False True
45 2026-01-09 19:00:00 2026-01-09 14:00:00-05:00 2026-01-09 0 0.49411413 0 1 0 -1 0 True False True
46 2026-01-09 20:00:00 2026-01-09 15:00:00-05:00 2026-01-09 0 0.47928023 0 1 0 -1 0 True False True
47 2026-01-09 21:00:00 2026-01-09 16:00:00-05:00 2026-01-09 0 0.49988702 0 1 0 -1 0 True False True
48 2026-01-09 22:00:00 2026-01-09 17:00:00-05:00 2026-01-09 1 0.4896841 0 1 0 -1 0 False False False
49 2026-01-09 23:00:00 2026-01-09 18:00:00-05:00 2026-01-09 0 0.49358237 0 1 0 -1 0 True False True
50 2026-01-10 00:00:00 2026-01-09 19:00:00-05:00 2026-01-09 0 0.50367767 1 1 0 1 1 False False False
51 2026-01-10 01:00:00 2026-01-09 20:00:00-05:00 2026-01-09 1 0.52877015 1 1 0 1 1 True True True
52 2026-01-10 02:00:00 2026-01-09 21:00:00-05:00 2026-01-09 1 0.51958245 1 1 0 1 1 True True True
53 2026-01-10 03:00:00 2026-01-09 22:00:00-05:00 2026-01-09 0 0.53057724 1 1 0 1 1 False False False
54 2026-01-10 04:00:00 2026-01-09 23:00:00-05:00 2026-01-09 1 0.45753565 0 1 1 -1 1 False False True
55 2026-01-10 05:00:00 2026-01-10 00:00:00-05:00 2026-01-10 0 0.447833 0 1 0 -1 0 True False True
56 2026-01-10 06:00:00 2026-01-10 01:00:00-05:00 2026-01-10 1 0.4577814 0 1 1 -1 1 False False True
57 2026-01-10 07:00:00 2026-01-10 02:00:00-05:00 2026-01-10 0 0.47578683 0 1 1 -1 1 True False False
58 2026-01-10 08:00:00 2026-01-10 03:00:00-05:00 2026-01-10 0 0.4506602 0 1 1 -1 1 True False False
59 2026-01-10 09:00:00 2026-01-10 04:00:00-05:00 2026-01-10 0 0.5478148 1 1 0 1 1 False False False
60 2026-01-10 10:00:00 2026-01-10 05:00:00-05:00 2026-01-10 1 0.5990233 1 1 0 1 1 True True True
61 2026-01-10 11:00:00 2026-01-10 06:00:00-05:00 2026-01-10 1 0.5706127 1 1 0 1 1 True True True
62 2026-01-10 12:00:00 2026-01-10 07:00:00-05:00 2026-01-10 0 0.5276616 1 1 0 1 1 False False False
63 2026-01-10 13:00:00 2026-01-10 08:00:00-05:00 2026-01-10 1 0.51404405 1 1 0 1 1 True True True
64 2026-01-10 14:00:00 2026-01-10 09:00:00-05:00 2026-01-10 1 0.50078404 1 1 0 1 1 True True True
65 2026-01-10 15:00:00 2026-01-10 10:00:00-05:00 2026-01-10 1 0.45550296 0 1 0 -1 0 False False False
66 2026-01-10 16:00:00 2026-01-10 11:00:00-05:00 2026-01-10 1 0.44787562 0 1 0 -1 0 False False False
67 2026-01-10 17:00:00 2026-01-10 12:00:00-05:00 2026-01-10 0 0.44727677 0 1 0 -1 0 True False True
68 2026-01-10 18:00:00 2026-01-10 13:00:00-05:00 2026-01-10 1 0.44855183 0 1 0 -1 0 False False False
69 2026-01-10 19:00:00 2026-01-10 14:00:00-05:00 2026-01-10 0 0.45485023 0 1 0 -1 0 True False True
70 2026-01-10 20:00:00 2026-01-10 15:00:00-05:00 2026-01-10 0 0.47032186 0 1 0 -1 0 True False True
71 2026-01-10 21:00:00 2026-01-10 16:00:00-05:00 2026-01-10 0 0.48025206 0 1 0 -1 0 True False True
72 2026-01-10 22:00:00 2026-01-10 17:00:00-05:00 2026-01-10 0 0.5247036 1 1 0 1 1 False False False
73 2026-01-10 23:00:00 2026-01-10 18:00:00-05:00 2026-01-10 0 0.58018094 1 1 0 1 1 False False False
74 2026-01-11 00:00:00 2026-01-10 19:00:00-05:00 2026-01-10 0 0.59668726 1 1 0 1 1 False False False
75 2026-01-11 01:00:00 2026-01-10 20:00:00-05:00 2026-01-10 1 0.5909982 1 1 0 1 1 True True True
76 2026-01-11 02:00:00 2026-01-10 21:00:00-05:00 2026-01-10 1 0.5297047 1 1 0 1 1 True True True
77 2026-01-11 03:00:00 2026-01-10 22:00:00-05:00 2026-01-10 0 0.5227216 1 1 0 1 1 False False False
78 2026-01-11 04:00:00 2026-01-10 23:00:00-05:00 2026-01-10 1 0.4885675 0 1 0 -1 0 False False False
79 2026-01-11 05:00:00 2026-01-11 00:00:00-05:00 2026-01-11 1 0.47107595 0 1 0 -1 0 False False False
80 2026-01-11 06:00:00 2026-01-11 01:00:00-05:00 2026-01-11 1 0.4610141 0 1 0 -1 0 False False False
81 2026-01-11 07:00:00 2026-01-11 02:00:00-05:00 2026-01-11 0 0.4598382 0 1 1 -1 1 True False False
82 2026-01-11 08:00:00 2026-01-11 03:00:00-05:00 2026-01-11 1 0.48207 0 1 1 -1 1 False False True
83 2026-01-11 09:00:00 2026-01-11 04:00:00-05:00 2026-01-11 1 0.513094 1 1 1 1 1 True True True
84 2026-01-11 10:00:00 2026-01-11 05:00:00-05:00 2026-01-11 0 0.47068593 0 1 1 -1 1 True False False
85 2026-01-11 11:00:00 2026-01-11 06:00:00-05:00 2026-01-11 0 0.4895874 0 1 1 -1 1 True False False
86 2026-01-11 12:00:00 2026-01-11 07:00:00-05:00 2026-01-11 1 0.46443766 0 1 1 -1 1 False False True
87 2026-01-11 13:00:00 2026-01-11 08:00:00-05:00 2026-01-11 1 0.4845122 0 1 1 -1 1 False False True
88 2026-01-11 14:00:00 2026-01-11 09:00:00-05:00 2026-01-11 1 0.50560534 1 1 1 1 1 True True True
89 2026-01-11 15:00:00 2026-01-11 10:00:00-05:00 2026-01-11 0 0.47123846 0 0 1 0 0 True True True
90 2026-01-11 16:00:00 2026-01-11 11:00:00-05:00 2026-01-11 0 0.45919827 0 1 1 -1 1 True False False
91 2026-01-11 17:00:00 2026-01-11 12:00:00-05:00 2026-01-11 1 0.52895916 1 0 1 -1 1 True False True
92 2026-01-11 18:00:00 2026-01-11 13:00:00-05:00 2026-01-11 1 0.5049388 1 1 1 1 1 True True True
93 2026-01-11 19:00:00 2026-01-11 14:00:00-05:00 2026-01-11 1 0.48020154 0 1 1 -1 1 False False True
94 2026-01-11 20:00:00 2026-01-11 15:00:00-05:00 2026-01-11 1 0.49915588 0 1 1 -1 1 False False True
95 2026-01-11 21:00:00 2026-01-11 16:00:00-05:00 2026-01-11 0 0.5144025 1 1 0 1 1 False False False
96 2026-01-11 22:00:00 2026-01-11 17:00:00-05:00 2026-01-11 1 0.49443942 0 1 0 -1 0 False False False
97 2026-01-11 23:00:00 2026-01-11 18:00:00-05:00 2026-01-11 1 0.4918472 0 1 0 -1 0 False False False
98 2026-01-12 00:00:00 2026-01-11 19:00:00-05:00 2026-01-11 0 0.4723591 0 0 1 0 0 True True True
99 2026-01-12 01:00:00 2026-01-11 20:00:00-05:00 2026-01-11 0 0.45115232 0 1 1 -1 1 True False False
100 2026-01-12 02:00:00 2026-01-11 21:00:00-05:00 2026-01-11 1 0.47599584 0 1 1 -1 1 False False True
101 2026-01-12 03:00:00 2026-01-11 22:00:00-05:00 2026-01-11 0 0.51635396 1 0 1 -1 1 False False False
102 2026-01-12 04:00:00 2026-01-11 23:00:00-05:00 2026-01-11 1 0.47296882 0 1 0 -1 0 False False False
103 2026-01-12 05:00:00 2026-01-12 00:00:00-05:00 2026-01-12 1 0.48586744 0 1 0 -1 0 False False False
104 2026-01-12 06:00:00 2026-01-12 01:00:00-05:00 2026-01-12 0 0.47855884 0 1 0 -1 0 True False True
105 2026-01-12 07:00:00 2026-01-12 02:00:00-05:00 2026-01-12 0 0.4667405 0 1 0 -1 0 True False True
106 2026-01-12 08:00:00 2026-01-12 03:00:00-05:00 2026-01-12 1 0.47844118 0 1 0 -1 0 False False False
107 2026-01-12 09:00:00 2026-01-12 04:00:00-05:00 2026-01-12 1 0.49896824 0 1 0 -1 0 False False False
108 2026-01-12 10:00:00 2026-01-12 05:00:00-05:00 2026-01-12 1 0.4889443 0 1 0 -1 0 False False False
109 2026-01-12 11:00:00 2026-01-12 06:00:00-05:00 2026-01-12 0 0.45522135 0 1 0 -1 0 True False True
110 2026-01-12 12:00:00 2026-01-12 07:00:00-05:00 2026-01-12 1 0.45169547 0 0 0 0 0 False False False
111 2026-01-12 13:00:00 2026-01-12 08:00:00-05:00 2026-01-12 0 0.48637372 0 0 0 0 0 True True True
112 2026-01-12 14:00:00 2026-01-12 09:00:00-05:00 2026-01-12 0 0.45615003 0 1 0 -1 0 True False True
113 2026-01-12 15:00:00 2026-01-12 10:00:00-05:00 2026-01-12 1 0.45153883 0 1 0 -1 0 False False False
114 2026-01-12 16:00:00 2026-01-12 11:00:00-05:00 2026-01-12 1 0.47393757 0 0 0 0 0 False False False
115 2026-01-12 17:00:00 2026-01-12 12:00:00-05:00 2026-01-12 1 0.47199413 0 1 0 -1 0 False False False
116 2026-01-12 18:00:00 2026-01-12 13:00:00-05:00 2026-01-12 0 0.43410957 0 1 1 -1 1 True False False
117 2026-01-12 19:00:00 2026-01-12 14:00:00-05:00 2026-01-12 0 0.44708502 0 1 1 -1 1 True False False
118 2026-01-12 20:00:00 2026-01-12 15:00:00-05:00 2026-01-12 1 0.58962935 1 1 1 1 1 True True True
119 2026-01-12 21:00:00 2026-01-12 16:00:00-05:00 2026-01-12 1 0.5799872 1 1 1 1 1 True True True
120 2026-01-12 22:00:00 2026-01-12 17:00:00-05:00 2026-01-12 0 0.5379354 1 1 0 1 1 False False False
121 2026-01-12 23:00:00 2026-01-12 18:00:00-05:00 2026-01-12 1 0.52044976 1 0 0 -1 0 True False False
122 2026-01-13 00:00:00 2026-01-12 19:00:00-05:00 2026-01-12 1 0.546228 1 0 0 -1 0 True False False
123 2026-01-13 01:00:00 2026-01-12 20:00:00-05:00 2026-01-12 0 0.50185496 1 0 0 -1 0 False False True
124 2026-01-13 02:00:00 2026-01-12 21:00:00-05:00 2026-01-12 1 0.5131573 1 0 0 -1 0 True False False
125 2026-01-13 03:00:00 2026-01-12 22:00:00-05:00 2026-01-12 0 0.49922633 0 1 1 -1 1 True False False
126 2026-01-13 04:00:00 2026-01-12 23:00:00-05:00 2026-01-12 1 0.47469753 0 1 0 -1 0 False False False
127 2026-01-13 05:00:00 2026-01-13 00:00:00-05:00 2026-01-13 1 0.45794666 0 1 1 -1 1 False False True
128 2026-01-13 06:00:00 2026-01-13 01:00:00-05:00 2026-01-13 1 0.45379728 0 1 1 -1 1 False False True
129 2026-01-13 07:00:00 2026-01-13 02:00:00-05:00 2026-01-13 0 0.45295683 0 1 1 -1 1 True False False
130 2026-01-13 08:00:00 2026-01-13 03:00:00-05:00 2026-01-13 0 0.44573298 0 1 0 -1 0 True False True
131 2026-01-13 09:00:00 2026-01-13 04:00:00-05:00 2026-01-13 1 0.4709209 0 1 0 -1 0 False False False
132 2026-01-13 10:00:00 2026-01-13 05:00:00-05:00 2026-01-13 1 0.54900587 1 1 1 1 1 True True True
133 2026-01-13 11:00:00 2026-01-13 06:00:00-05:00 2026-01-13 0 0.54181457 1 1 1 1 1 False False False
134 2026-01-13 12:00:00 2026-01-13 07:00:00-05:00 2026-01-13 0 0.47640413 0 1 1 -1 1 True False False
135 2026-01-13 13:00:00 2026-01-13 08:00:00-05:00 2026-01-13 1 0.5774929 1 0 1 -1 1 True False True
136 2026-01-13 14:00:00 2026-01-13 09:00:00-05:00 2026-01-13 0 0.55843365 1 1 1 1 1 False False False
137 2026-01-13 15:00:00 2026-01-13 10:00:00-05:00 2026-01-13 0 0.53291476 1 1 1 1 1 False False False
138 2026-01-13 16:00:00 2026-01-13 11:00:00-05:00 2026-01-13 0 0.5417355 1 0 1 -1 1 False False False
139 2026-01-13 17:00:00 2026-01-13 12:00:00-05:00 2026-01-13 0 0.526461 1 1 1 1 1 False False False
140 2026-01-13 18:00:00 2026-01-13 13:00:00-05:00 2026-01-13 1 0.5094685 1 1 1 1 1 True True True
141 2026-01-13 19:00:00 2026-01-13 14:00:00-05:00 2026-01-13 1 0.50502 1 0 1 -1 1 True False True
142 2026-01-13 20:00:00 2026-01-13 15:00:00-05:00 2026-01-13 0 0.5074931 1 1 1 1 1 False False False
143 2026-01-13 21:00:00 2026-01-13 16:00:00-05:00 2026-01-13 1 0.50496596 1 1 1 1 1 True True True
144 2026-01-13 22:00:00 2026-01-13 17:00:00-05:00 2026-01-13 1 0.5311341 1 1 1 1 1 True True True
145 2026-01-13 23:00:00 2026-01-13 18:00:00-05:00 2026-01-13 1 0.5194955 1 0 1 -1 1 True False True
146 2026-01-14 00:00:00 2026-01-13 19:00:00-05:00 2026-01-13 0 0.51150465 1 0 1 -1 1 False False False
147 2026-01-14 01:00:00 2026-01-13 20:00:00-05:00 2026-01-13 0 0.53702235 1 0 1 -1 1 False False False
148 2026-01-14 02:00:00 2026-01-13 21:00:00-05:00 2026-01-13 1 0.4907134 0 0 0 0 0 False False False
149 2026-01-14 03:00:00 2026-01-13 22:00:00-05:00 2026-01-13 0 0.4840499 0 0 0 0 0 True True True
150 2026-01-14 04:00:00 2026-01-13 23:00:00-05:00 2026-01-13 0 0.4611074 0 0 0 0 0 True True True
151 2026-01-14 05:00:00 2026-01-14 00:00:00-05:00 2026-01-14 1 0.45327646 0 0 0 0 0 False False False
152 2026-01-14 06:00:00 2026-01-14 01:00:00-05:00 2026-01-14 1 0.49665686 0 0 0 0 0 False False False
153 2026-01-14 07:00:00 2026-01-14 02:00:00-05:00 2026-01-14 0 0.4882262 0 0 1 0 0 True True True
154 2026-01-14 08:00:00 2026-01-14 03:00:00-05:00 2026-01-14 0 0.46823135 0 1 1 -1 1 True False False
155 2026-01-14 09:00:00 2026-01-14 04:00:00-05:00 2026-01-14 0 0.47908562 0 1 0 -1 0 True False True
156 2026-01-14 10:00:00 2026-01-14 05:00:00-05:00 2026-01-14 0 0.55422133 1 1 0 1 1 False False False
157 2026-01-14 11:00:00 2026-01-14 06:00:00-05:00 2026-01-14 1 0.610168 1 1 1 1 1 True True True
158 2026-01-14 12:00:00 2026-01-14 07:00:00-05:00 2026-01-14 0 0.5614474 1 1 1 1 1 False False False
159 2026-01-14 13:00:00 2026-01-14 08:00:00-05:00 2026-01-14 1 0.56697947 1 0 1 -1 1 True False True
160 2026-01-14 14:00:00 2026-01-14 09:00:00-05:00 2026-01-14 1 0.52636105 1 0 1 -1 1 True False True
161 2026-01-14 15:00:00 2026-01-14 10:00:00-05:00 2026-01-14 1 0.50316924 1 0 1 -1 1 True False True
162 2026-01-14 16:00:00 2026-01-14 11:00:00-05:00 2026-01-14 0 0.5363478 1 0 1 -1 1 False False False
163 2026-01-14 17:00:00 2026-01-14 12:00:00-05:00 2026-01-14 0 0.47355476 0 0 1 0 0 True True True
164 2026-01-14 18:00:00 2026-01-14 13:00:00-05:00 2026-01-14 0 0.48269007 0 0 1 0 0 True True True
165 2026-01-14 19:00:00 2026-01-14 14:00:00-05:00 2026-01-14 1 0.50739765 1 0 1 -1 1 True False True
166 2026-01-14 20:00:00 2026-01-14 15:00:00-05:00 2026-01-14 1 0.501837 1 0 1 -1 1 True False True
167 2026-01-14 21:00:00 2026-01-14 16:00:00-05:00 2026-01-14 0 0.4608701 0 0 1 0 0 True True True
168 2026-01-14 22:00:00 2026-01-14 17:00:00-05:00 2026-01-14 1 0.4631222 0 0 1 0 0 False False False
169 2026-01-14 23:00:00 2026-01-14 18:00:00-05:00 2026-01-14 0 0.5486243 1 0 1 -1 1 False False False
170 2026-01-15 00:00:00 2026-01-14 19:00:00-05:00 2026-01-14 0 0.55234075 1 0 1 -1 1 False False False
171 2026-01-15 01:00:00 2026-01-14 20:00:00-05:00 2026-01-14 1 0.4942968 0 1 1 -1 1 False False True
172 2026-01-15 02:00:00 2026-01-14 21:00:00-05:00 2026-01-14 1 0.5293442 1 1 1 1 1 True True True
173 2026-01-15 03:00:00 2026-01-14 22:00:00-05:00 2026-01-14 0 0.5131931 1 1 1 1 1 False False False
174 2026-01-15 04:00:00 2026-01-14 23:00:00-05:00 2026-01-14 0 0.48758185 0 1 1 -1 1 True False False
175 2026-01-15 05:00:00 2026-01-15 00:00:00-05:00 2026-01-15 0 0.4538339 0 0 1 0 0 True True True
176 2026-01-15 06:00:00 2026-01-15 01:00:00-05:00 2026-01-15 1 0.48841763 0 1 1 -1 1 False False True
177 2026-01-15 07:00:00 2026-01-15 02:00:00-05:00 2026-01-15 1 0.5323837 1 1 1 1 1 True True True
178 2026-01-15 08:00:00 2026-01-15 03:00:00-05:00 2026-01-15 0 0.5621298 1 1 1 1 1 False False False
179 2026-01-15 09:00:00 2026-01-15 04:00:00-05:00 2026-01-15 1 0.46453536 0 1 1 -1 1 False False True
180 2026-01-15 10:00:00 2026-01-15 05:00:00-05:00 2026-01-15 1 0.5253816 1 1 1 1 1 True True True
181 2026-01-15 11:00:00 2026-01-15 06:00:00-05:00 2026-01-15 0 0.5148865 1 1 1 1 1 False False False
182 2026-01-15 12:00:00 2026-01-15 07:00:00-05:00 2026-01-15 0 0.4899683 0 1 1 -1 1 True False False
183 2026-01-15 13:00:00 2026-01-15 08:00:00-05:00 2026-01-15 1 0.4946566 0 0 1 0 0 False False False
184 2026-01-15 14:00:00 2026-01-15 09:00:00-05:00 2026-01-15 0 0.4987126 0 0 1 0 0 True True True
185 2026-01-15 15:00:00 2026-01-15 10:00:00-05:00 2026-01-15 0 0.48068026 0 1 1 -1 1 True False False
186 2026-01-15 16:00:00 2026-01-15 11:00:00-05:00 2026-01-15 0 0.48770115 0 0 1 0 0 True True True
187 2026-01-15 17:00:00 2026-01-15 12:00:00-05:00 2026-01-15 1 0.4844149 0 0 0 0 0 False False False
188 2026-01-15 18:00:00 2026-01-15 13:00:00-05:00 2026-01-15 0 0.4631365 0 0 0 0 0 True True True
189 2026-01-15 19:00:00 2026-01-15 14:00:00-05:00 2026-01-15 1 0.45610657 0 0 0 0 0 False False False
190 2026-01-15 20:00:00 2026-01-15 15:00:00-05:00 2026-01-15 0 0.4881056 0 0 0 0 0 True True True
191 2026-01-15 21:00:00 2026-01-15 16:00:00-05:00 2026-01-15 0 0.46482596 0 0 0 0 0 True True True
192 2026-01-15 22:00:00 2026-01-15 17:00:00-05:00 2026-01-15 1 0.51136947 1 0 0 -1 0 True False False
193 2026-01-15 23:00:00 2026-01-15 18:00:00-05:00 2026-01-15 1 0.55429316 1 0 0 -1 0 True False False
194 2026-01-16 00:00:00 2026-01-15 19:00:00-05:00 2026-01-15 0 0.5584868 1 0 0 -1 0 False False True
195 2026-01-16 01:00:00 2026-01-15 20:00:00-05:00 2026-01-15 1 0.51578057 1 0 0 -1 0 True False False
196 2026-01-16 02:00:00 2026-01-15 21:00:00-05:00 2026-01-15 0 0.5562782 1 0 0 -1 0 False False True
197 2026-01-16 03:00:00 2026-01-15 22:00:00-05:00 2026-01-15 1 0.5080268 1 0 0 -1 0 True False False
198 2026-01-16 04:00:00 2026-01-15 23:00:00-05:00 2026-01-15 1 0.49140474 0 0 0 0 0 False False False
199 2026-01-16 05:00:00 2026-01-16 00:00:00-05:00 2026-01-16 1 0.46221796 0 0 0 0 0 False False False
200 2026-01-16 06:00:00 2026-01-16 01:00:00-05:00 2026-01-16 0 0.4448804 0 0 0 0 0 True True True
201 2026-01-16 07:00:00 2026-01-16 02:00:00-05:00 2026-01-16 1 0.4457024 0 0 0 0 0 False False False
202 2026-01-16 08:00:00 2026-01-16 03:00:00-05:00 2026-01-16 1 0.49323055 0 0 0 0 0 False False False
203 2026-01-16 09:00:00 2026-01-16 04:00:00-05:00 2026-01-16 0 0.49757826 0 0 0 0 0 True True True
204 2026-01-16 10:00:00 2026-01-16 05:00:00-05:00 2026-01-16 1 0.5067139 1 0 0 -1 0 True False False
205 2026-01-16 11:00:00 2026-01-16 06:00:00-05:00 2026-01-16 0 0.5102587 1 0 0 -1 0 False False True
206 2026-01-16 12:00:00 2026-01-16 07:00:00-05:00 2026-01-16 0 0.5843353 1 0 0 -1 0 False False True
207 2026-01-16 13:00:00 2026-01-16 08:00:00-05:00 2026-01-16 0 0.5425463 1 0 0 -1 0 False False True
208 2026-01-16 14:00:00 2026-01-16 09:00:00-05:00 2026-01-16 0 0.5690049 1 0 0 -1 0 False False True
209 2026-01-16 15:00:00 2026-01-16 10:00:00-05:00 2026-01-16 0 0.54829246 1 0 0 -1 0 False False True
210 2026-01-16 16:00:00 2026-01-16 11:00:00-05:00 2026-01-16 1 0.5046336 1 0 0 -1 0 True False False
211 2026-01-16 17:00:00 2026-01-16 12:00:00-05:00 2026-01-16 1 0.5086898 1 0 0 -1 0 True False False
212 2026-01-16 18:00:00 2026-01-16 13:00:00-05:00 2026-01-16 0 0.4823461 0 0 0 0 0 True True True
213 2026-01-16 19:00:00 2026-01-16 14:00:00-05:00 2026-01-16 1 0.4856351 0 0 0 0 0 False False False
214 2026-01-16 20:00:00 2026-01-16 15:00:00-05:00 2026-01-16 0 0.4558552 0 0 0 0 0 True True True
215 2026-01-16 21:00:00 2026-01-16 16:00:00-05:00 2026-01-16 0 0.4780688 0 0 0 0 0 True True True
216 2026-01-16 22:00:00 2026-01-16 17:00:00-05:00 2026-01-16 1 0.48367676 0 0 0 0 0 False False False
217 2026-01-16 23:00:00 2026-01-16 18:00:00-05:00 2026-01-16 1 0.5118516 1 0 0 -1 0 True False False
218 2026-01-17 00:00:00 2026-01-16 19:00:00-05:00 2026-01-16 0 0.5440252 1 0 0 -1 0 False False True
219 2026-01-17 01:00:00 2026-01-16 20:00:00-05:00 2026-01-16 0 0.52165735 1 1 0 1 1 False False False
220 2026-01-17 02:00:00 2026-01-16 21:00:00-05:00 2026-01-16 0 0.57716566 1 1 0 1 1 False False False
221 2026-01-17 03:00:00 2026-01-16 22:00:00-05:00 2026-01-16 1 0.53289396 1 1 0 1 1 True True True
222 2026-01-17 04:00:00 2026-01-16 23:00:00-05:00 2026-01-16 0 0.5077436 1 0 0 -1 0 False False True
223 2026-01-17 05:00:00 2026-01-17 00:00:00-05:00 2026-01-17 1 0.49432057 0 1 0 -1 0 False False False
224 2026-01-17 06:00:00 2026-01-17 01:00:00-05:00 2026-01-17 1 0.50985175 1 1 0 1 1 True True True
225 2026-01-17 07:00:00 2026-01-17 02:00:00-05:00 2026-01-17 1 0.4898242 0 1 0 -1 0 False False False
226 2026-01-17 08:00:00 2026-01-17 03:00:00-05:00 2026-01-17 1 0.45491692 0 1 0 -1 0 False False False
227 2026-01-17 09:00:00 2026-01-17 04:00:00-05:00 2026-01-17 0 0.45771605 0 1 0 -1 0 True False True
228 2026-01-17 10:00:00 2026-01-17 05:00:00-05:00 2026-01-17 0 0.45357528 0 1 0 -1 0 True False True
229 2026-01-17 11:00:00 2026-01-17 06:00:00-05:00 2026-01-17 1 0.4550014 0 1 0 -1 0 False False False
230 2026-01-17 12:00:00 2026-01-17 07:00:00-05:00 2026-01-17 1 0.46508005 0 1 0 -1 0 False False False
231 2026-01-17 13:00:00 2026-01-17 08:00:00-05:00 2026-01-17 0 0.4753723 0 1 0 -1 0 True False True
232 2026-01-17 14:00:00 2026-01-17 09:00:00-05:00 2026-01-17 0 0.49003577 0 0 0 0 0 True True True
233 2026-01-17 15:00:00 2026-01-17 10:00:00-05:00 2026-01-17 0 0.60734934 1 0 0 -1 0 False False True
234 2026-01-17 16:00:00 2026-01-17 11:00:00-05:00 2026-01-17 0 0.5786392 1 0 0 -1 0 False False True
235 2026-01-17 17:00:00 2026-01-17 12:00:00-05:00 2026-01-17 1 0.5754632 1 1 0 1 1 True True True
236 2026-01-17 18:00:00 2026-01-17 13:00:00-05:00 2026-01-17 0 0.59382933 1 1 0 1 1 False False False
237 2026-01-17 19:00:00 2026-01-17 14:00:00-05:00 2026-01-17 0 0.5593247 1 1 0 1 1 False False False
238 2026-01-17 20:00:00 2026-01-17 15:00:00-05:00 2026-01-17 1 0.5365127 1 1 0 1 1 True True True
239 2026-01-17 21:00:00 2026-01-17 16:00:00-05:00 2026-01-17 1 0.50084764 1 1 0 1 1 True True True
240 2026-01-17 22:00:00 2026-01-17 17:00:00-05:00 2026-01-17 1 0.46421826 0 1 0 -1 0 False False False
241 2026-01-17 23:00:00 2026-01-17 18:00:00-05:00 2026-01-17 0 0.48463318 0 1 0 -1 0 True False True
242 2026-01-18 00:00:00 2026-01-17 19:00:00-05:00 2026-01-17 1 0.46844795 0 1 0 -1 0 False False False
243 2026-01-18 01:00:00 2026-01-17 20:00:00-05:00 2026-01-17 1 0.48266262 0 1 0 -1 0 False False False
244 2026-01-18 02:00:00 2026-01-17 21:00:00-05:00 2026-01-17 0 0.49029487 0 1 0 -1 0 True False True
245 2026-01-18 03:00:00 2026-01-17 22:00:00-05:00 2026-01-17 0 0.45225728 0 1 0 -1 0 True False True
246 2026-01-18 04:00:00 2026-01-17 23:00:00-05:00 2026-01-17 0 0.5144419 1 1 0 1 1 False False False
247 2026-01-18 05:00:00 2026-01-18 00:00:00-05:00 2026-01-18 1 0.5160817 1 1 0 1 1 True True True
248 2026-01-18 06:00:00 2026-01-18 01:00:00-05:00 2026-01-18 1 0.50346553 1 1 0 1 1 True True True
249 2026-01-18 07:00:00 2026-01-18 02:00:00-05:00 2026-01-18 0 0.44028476 0 1 0 -1 0 True False True
250 2026-01-18 08:00:00 2026-01-18 03:00:00-05:00 2026-01-18 0 0.46246836 0 1 0 -1 0 True False True
251 2026-01-18 09:00:00 2026-01-18 04:00:00-05:00 2026-01-18 1 0.51622605 1 1 0 1 1 True True True
252 2026-01-18 10:00:00 2026-01-18 05:00:00-05:00 2026-01-18 1 0.51112276 1 1 0 1 1 True True True
253 2026-01-18 11:00:00 2026-01-18 06:00:00-05:00 2026-01-18 0 0.45434904 0 1 0 -1 0 True False True
254 2026-01-18 12:00:00 2026-01-18 07:00:00-05:00 2026-01-18 0 0.52511775 1 1 0 1 1 False False False
255 2026-01-18 13:00:00 2026-01-18 08:00:00-05:00 2026-01-18 1 0.5778929 1 1 0 1 1 True True True
256 2026-01-18 14:00:00 2026-01-18 09:00:00-05:00 2026-01-18 1 0.61074215 1 1 0 1 1 True True True
257 2026-01-18 15:00:00 2026-01-18 10:00:00-05:00 2026-01-18 0 0.57336044 1 1 0 1 1 False False False
258 2026-01-18 16:00:00 2026-01-18 11:00:00-05:00 2026-01-18 0 0.51572573 1 1 0 1 1 False False False
259 2026-01-18 17:00:00 2026-01-18 12:00:00-05:00 2026-01-18 0 0.5084302 1 0 0 -1 0 False False True
260 2026-01-18 18:00:00 2026-01-18 13:00:00-05:00 2026-01-18 0 0.53620636 1 1 0 1 1 False False False
261 2026-01-18 19:00:00 2026-01-18 14:00:00-05:00 2026-01-18 0 0.58746594 1 1 0 1 1 False False False
262 2026-01-18 20:00:00 2026-01-18 15:00:00-05:00 2026-01-18 0 0.53558993 1 1 0 1 1 False False False
263 2026-01-18 21:00:00 2026-01-18 16:00:00-05:00 2026-01-18 1 0.4962998 0 1 0 -1 0 False False False
264 2026-01-18 22:00:00 2026-01-18 17:00:00-05:00 2026-01-18 0 0.5750347 1 0 0 -1 0 False False True
265 2026-01-18 23:00:00 2026-01-18 18:00:00-05:00 2026-01-18 0 0.58212554 1 1 0 1 1 False False False
266 2026-01-19 00:00:00 2026-01-18 19:00:00-05:00 2026-01-18 1 0.5023791 1 1 0 1 1 True True True
267 2026-01-19 01:00:00 2026-01-18 20:00:00-05:00 2026-01-18 0 0.51338583 1 0 0 -1 0 False False True
268 2026-01-19 02:00:00 2026-01-18 21:00:00-05:00 2026-01-18 1 0.4790363 0 0 0 0 0 False False False
269 2026-01-19 03:00:00 2026-01-18 22:00:00-05:00 2026-01-18 0 0.4659165 0 0 0 0 0 True True True
270 2026-01-19 04:00:00 2026-01-18 23:00:00-05:00 2026-01-18 0 0.48015603 0 0 0 0 0 True True True
271 2026-01-19 05:00:00 2026-01-19 00:00:00-05:00 2026-01-19 1 0.4719194 0 0 0 0 0 False False False
272 2026-01-19 06:00:00 2026-01-19 01:00:00-05:00 2026-01-19 0 0.47719255 0 0 0 0 0 True True True
273 2026-01-19 07:00:00 2026-01-19 02:00:00-05:00 2026-01-19 0 0.46883187 0 0 0 0 0 True True True
274 2026-01-19 08:00:00 2026-01-19 03:00:00-05:00 2026-01-19 0 0.5041158 1 0 0 -1 0 False False True
275 2026-01-19 09:00:00 2026-01-19 04:00:00-05:00 2026-01-19 1 0.55327594 1 1 0 1 1 True True True
276 2026-01-19 10:00:00 2026-01-19 05:00:00-05:00 2026-01-19 1 0.51431626 1 1 0 1 1 True True True
277 2026-01-19 11:00:00 2026-01-19 06:00:00-05:00 2026-01-19 0 0.48845842 0 1 0 -1 0 True False True
278 2026-01-19 12:00:00 2026-01-19 07:00:00-05:00 2026-01-19 0 0.548243 1 1 0 1 1 False False False
279 2026-01-19 13:00:00 2026-01-19 08:00:00-05:00 2026-01-19 0 0.56993884 1 1 0 1 1 False False False
280 2026-01-19 14:00:00 2026-01-19 09:00:00-05:00 2026-01-19 1 0.570956 1 1 0 1 1 True True True
281 2026-01-19 15:00:00 2026-01-19 10:00:00-05:00 2026-01-19 1 0.58591735 1 1 0 1 1 True True True
282 2026-01-19 16:00:00 2026-01-19 11:00:00-05:00 2026-01-19 1 0.5654058 1 1 0 1 1 True True True
283 2026-01-19 17:00:00 2026-01-19 12:00:00-05:00 2026-01-19 1 0.53851956 1 1 0 1 1 True True True
284 2026-01-19 18:00:00 2026-01-19 13:00:00-05:00 2026-01-19 1 0.51866657 1 1 0 1 1 True True True
285 2026-01-19 19:00:00 2026-01-19 14:00:00-05:00 2026-01-19 1 0.4805421 0 1 0 -1 0 False False False
286 2026-01-19 20:00:00 2026-01-19 15:00:00-05:00 2026-01-19 0 0.4309969 0 0 0 0 0 True True True
287 2026-01-19 21:00:00 2026-01-19 16:00:00-05:00 2026-01-19 0 0.40472427 0 0 0 0 0 True True True
288 2026-01-19 22:00:00 2026-01-19 17:00:00-05:00 2026-01-19 0 0.43165132 0 1 0 -1 0 True False True
289 2026-01-19 23:00:00 2026-01-19 18:00:00-05:00 2026-01-19 0 0.45249504 0 1 0 -1 0 True False True
290 2026-01-20 00:00:00 2026-01-19 19:00:00-05:00 2026-01-19 1 0.41881704 0 1 0 -1 0 False False False
291 2026-01-20 01:00:00 2026-01-19 20:00:00-05:00 2026-01-19 0 0.4950903 0 1 0 -1 0 True False True
292 2026-01-20 02:00:00 2026-01-19 21:00:00-05:00 2026-01-19 0 0.52289253 1 1 0 1 1 False False False
293 2026-01-20 03:00:00 2026-01-19 22:00:00-05:00 2026-01-19 0 0.5152514 1 1 0 1 1 False False False
294 2026-01-20 04:00:00 2026-01-19 23:00:00-05:00 2026-01-19 1 0.52619386 1 1 0 1 1 True True True
295 2026-01-20 05:00:00 2026-01-20 00:00:00-05:00 2026-01-20 1 0.4920829 0 1 0 -1 0 False False False
296 2026-01-20 06:00:00 2026-01-20 01:00:00-05:00 2026-01-20 0 0.44386265 0 1 0 -1 0 True False True
297 2026-01-20 07:00:00 2026-01-20 02:00:00-05:00 2026-01-20 1 0.4548454 0 1 0 -1 0 False False False
298 2026-01-20 08:00:00 2026-01-20 03:00:00-05:00 2026-01-20 1 0.50409263 1 1 0 1 1 True True True
299 2026-01-20 09:00:00 2026-01-20 04:00:00-05:00 2026-01-20 1 0.4486903 0 1 0 -1 0 False False False
300 2026-01-20 10:00:00 2026-01-20 05:00:00-05:00 2026-01-20 0 0.44015622 0 1 0 -1 0 True False True
301 2026-01-20 11:00:00 2026-01-20 06:00:00-05:00 2026-01-20 1 0.46409336 0 1 0 -1 0 False False False
302 2026-01-20 12:00:00 2026-01-20 07:00:00-05:00 2026-01-20 0 0.47176668 0 1 0 -1 0 True False True
303 2026-01-20 13:00:00 2026-01-20 08:00:00-05:00 2026-01-20 0 0.44548807 0 1 0 -1 0 True False True
304 2026-01-20 14:00:00 2026-01-20 09:00:00-05:00 2026-01-20 1 0.45053762 0 1 0 -1 0 False False False
305 2026-01-20 15:00:00 2026-01-20 10:00:00-05:00 2026-01-20 1 0.481055 0 1 0 -1 0 False False False
306 2026-01-20 16:00:00 2026-01-20 11:00:00-05:00 2026-01-20 0 0.5467605 1 1 0 1 1 False False False
307 2026-01-20 17:00:00 2026-01-20 12:00:00-05:00 2026-01-20 0 0.5497743 1 1 0 1 1 False False False
308 2026-01-20 18:00:00 2026-01-20 13:00:00-05:00 2026-01-20 1 0.531139 1 0 0 -1 0 True False False
309 2026-01-20 19:00:00 2026-01-20 14:00:00-05:00 2026-01-20 1 0.48030645 0 0 0 0 0 False False False
310 2026-01-20 20:00:00 2026-01-20 15:00:00-05:00 2026-01-20 1 0.5283993 1 0 0 -1 0 True False False
311 2026-01-20 21:00:00 2026-01-20 16:00:00-05:00 2026-01-20 0 0.5042049 1 1 0 1 1 False False False
312 2026-01-20 22:00:00 2026-01-20 17:00:00-05:00 2026-01-20 1 0.46221802 0 1 0 -1 0 False False False
313 2026-01-20 23:00:00 2026-01-20 18:00:00-05:00 2026-01-20 1 0.45490173 0 1 0 -1 0 False False False
314 2026-01-21 00:00:00 2026-01-20 19:00:00-05:00 2026-01-20 1 0.4481478 0 1 0 -1 0 False False False
315 2026-01-21 01:00:00 2026-01-20 20:00:00-05:00 2026-01-20 0 0.4967737 0 1 0 -1 0 True False True
316 2026-01-21 02:00:00 2026-01-20 21:00:00-05:00 2026-01-20 1 0.5263554 1 1 0 1 1 True True True
317 2026-01-21 03:00:00 2026-01-20 22:00:00-05:00 2026-01-20 0 0.46894258 0 1 0 -1 0 True False True
318 2026-01-21 04:00:00 2026-01-20 23:00:00-05:00 2026-01-20 0 0.4519449 0 1 0 -1 0 True False True
319 2026-01-21 05:00:00 2026-01-21 00:00:00-05:00 2026-01-21 0 0.5209714 1 1 0 1 1 False False False
320 2026-01-21 06:00:00 2026-01-21 01:00:00-05:00 2026-01-21 1 0.56909335 1 0 0 -1 0 True False False
321 2026-01-21 07:00:00 2026-01-21 02:00:00-05:00 2026-01-21 1 0.51435 1 1 0 1 1 True True True
322 2026-01-21 08:00:00 2026-01-21 03:00:00-05:00 2026-01-21 0 0.47510275 0 1 0 -1 0 True False True
323 2026-01-21 09:00:00 2026-01-21 04:00:00-05:00 2026-01-21 1 0.47119588 0 1 0 -1 0 False False False
324 2026-01-21 10:00:00 2026-01-21 05:00:00-05:00 2026-01-21 0 0.45829907 0 1 0 -1 0 True False True
325 2026-01-21 11:00:00 2026-01-21 06:00:00-05:00 2026-01-21 0 0.46126115 0 1 0 -1 0 True False True
326 2026-01-21 12:00:00 2026-01-21 07:00:00-05:00 2026-01-21 1 0.49299902 0 1 0 -1 0 False False False
327 2026-01-21 13:00:00 2026-01-21 08:00:00-05:00 2026-01-21 1 0.4642857 0 1 0 -1 0 False False False
328 2026-01-21 14:00:00 2026-01-21 09:00:00-05:00 2026-01-21 0 0.46757126 0 1 0 -1 0 True False True
329 2026-01-21 15:00:00 2026-01-21 10:00:00-05:00 2026-01-21 1 0.51933724 1 0 0 -1 0 True False False
330 2026-01-21 16:00:00 2026-01-21 11:00:00-05:00 2026-01-21 0 0.52031684 1 0 0 -1 0 False False True
331 2026-01-21 17:00:00 2026-01-21 12:00:00-05:00 2026-01-21 1 0.4510923 0 1 0 -1 0 False False False
332 2026-01-21 18:00:00 2026-01-21 13:00:00-05:00 2026-01-21 1 0.47569612 0 0 0 0 0 False False False
333 2026-01-21 19:00:00 2026-01-21 14:00:00-05:00 2026-01-21 0 0.52261287 1 1 0 1 1 False False False
334 2026-01-21 20:00:00 2026-01-21 15:00:00-05:00 2026-01-21 0 0.5309036 1 0 0 -1 0 False False True
335 2026-01-21 21:00:00 2026-01-21 16:00:00-05:00 2026-01-21 1 0.5570105 1 1 0 1 1 True True True
336 2026-01-21 22:00:00 2026-01-21 17:00:00-05:00 2026-01-21 0 0.49707052 0 1 0 -1 0 True False True
337 2026-01-21 23:00:00 2026-01-21 18:00:00-05:00 2026-01-21 0 0.49758816 0 1 0 -1 0 True False True
338 2026-01-22 00:00:00 2026-01-21 19:00:00-05:00 2026-01-21 1 0.53757817 1 1 0 1 1 True True True
339 2026-01-22 01:00:00 2026-01-21 20:00:00-05:00 2026-01-21 1 0.57986003 1 1 0 1 1 True True True
340 2026-01-22 02:00:00 2026-01-21 21:00:00-05:00 2026-01-21 1 0.53082293 1 1 0 1 1 True True True
341 2026-01-22 03:00:00 2026-01-21 22:00:00-05:00 2026-01-21 1 0.50052387 1 1 0 1 1 True True True
342 2026-01-22 04:00:00 2026-01-21 23:00:00-05:00 2026-01-21 0 0.47824442 0 1 0 -1 0 True False True
343 2026-01-22 05:00:00 2026-01-22 00:00:00-05:00 2026-01-22 1 0.4760107 0 1 0 -1 0 False False False
344 2026-01-22 06:00:00 2026-01-22 01:00:00-05:00 2026-01-22 1 0.50084823 1 1 0 1 1 True True True
345 2026-01-22 07:00:00 2026-01-22 02:00:00-05:00 2026-01-22 1 0.45673624 0 1 0 -1 0 False False False
346 2026-01-22 08:00:00 2026-01-22 03:00:00-05:00 2026-01-22 0 0.44737244 0 1 0 -1 0 True False True
347 2026-01-22 09:00:00 2026-01-22 04:00:00-05:00 2026-01-22 0 0.4425855 0 0 0 0 0 True True True
348 2026-01-22 10:00:00 2026-01-22 05:00:00-05:00 2026-01-22 0 0.41352358 0 0 0 0 0 True True True
349 2026-01-22 11:00:00 2026-01-22 06:00:00-05:00 2026-01-22 0 0.47900316 0 1 0 -1 0 True False True
350 2026-01-22 12:00:00 2026-01-22 07:00:00-05:00 2026-01-22 1 0.5056432 1 0 0 -1 0 True False False
351 2026-01-22 13:00:00 2026-01-22 08:00:00-05:00 2026-01-22 0 0.5437085 1 1 0 1 1 False False False
352 2026-01-22 14:00:00 2026-01-22 09:00:00-05:00 2026-01-22 0 0.57784945 1 1 0 1 1 False False False
353 2026-01-22 15:00:00 2026-01-22 10:00:00-05:00 2026-01-22 1 0.55117744 1 1 0 1 1 True True True
354 2026-01-22 16:00:00 2026-01-22 11:00:00-05:00 2026-01-22 1 0.5499753 1 1 0 1 1 True True True
355 2026-01-22 17:00:00 2026-01-22 12:00:00-05:00 2026-01-22 1 0.48403728 0 0 0 0 0 False False False
356 2026-01-22 18:00:00 2026-01-22 13:00:00-05:00 2026-01-22 1 0.5378152 1 0 0 -1 0 True False False
357 2026-01-22 19:00:00 2026-01-22 14:00:00-05:00 2026-01-22 0 0.5124629 1 0 0 -1 0 False False True
358 2026-01-22 20:00:00 2026-01-22 15:00:00-05:00 2026-01-22 0 0.4786928 0 0 0 0 0 True True True
359 2026-01-22 21:00:00 2026-01-22 16:00:00-05:00 2026-01-22 0 0.47483838 0 1 0 -1 0 True False True
360 2026-01-22 22:00:00 2026-01-22 17:00:00-05:00 2026-01-22 1 0.4948368 0 1 0 -1 0 False False False
361 2026-01-22 23:00:00 2026-01-22 18:00:00-05:00 2026-01-22 0 0.50176257 1 1 0 1 1 False False False
362 2026-01-23 00:00:00 2026-01-22 19:00:00-05:00 2026-01-22 1 0.49785018 0 1 0 -1 0 False False False
363 2026-01-23 01:00:00 2026-01-22 20:00:00-05:00 2026-01-22 0 0.4707971 0 1 0 -1 0 True False True
364 2026-01-23 02:00:00 2026-01-22 21:00:00-05:00 2026-01-22 0 0.4584682 0 1 0 -1 0 True False True
365 2026-01-23 03:00:00 2026-01-22 22:00:00-05:00 2026-01-22 0 0.47485125 0 1 0 -1 0 True False True
366 2026-01-23 04:00:00 2026-01-22 23:00:00-05:00 2026-01-22 1 0.48357454 0 1 0 -1 0 False False False
367 2026-01-23 05:00:00 2026-01-23 00:00:00-05:00 2026-01-23 0 0.5074494 1 1 0 1 1 False False False
368 2026-01-23 06:00:00 2026-01-23 01:00:00-05:00 2026-01-23 1 0.49947768 0 1 0 -1 0 False False False
369 2026-01-23 07:00:00 2026-01-23 02:00:00-05:00 2026-01-23 1 0.5284809 1 1 0 1 1 True True True
370 2026-01-23 08:00:00 2026-01-23 03:00:00-05:00 2026-01-23 0 0.49399173 0 1 0 -1 0 True False True
371 2026-01-23 09:00:00 2026-01-23 04:00:00-05:00 2026-01-23 0 0.45683447 0 1 0 -1 0 True False True
372 2026-01-23 10:00:00 2026-01-23 05:00:00-05:00 2026-01-23 1 0.44759712 0 1 0 -1 0 False False False
373 2026-01-23 11:00:00 2026-01-23 06:00:00-05:00 2026-01-23 1 0.46368843 0 1 0 -1 0 False False False
374 2026-01-23 12:00:00 2026-01-23 07:00:00-05:00 2026-01-23 1 0.4522303 0 1 0 -1 0 False False False
375 2026-01-23 13:00:00 2026-01-23 08:00:00-05:00 2026-01-23 0 0.5273792 1 1 0 1 1 False False False
376 2026-01-23 14:00:00 2026-01-23 09:00:00-05:00 2026-01-23 1 0.49696228 0 1 0 -1 0 False False False
377 2026-01-23 15:00:00 2026-01-23 10:00:00-05:00 2026-01-23 0 0.5127735 1 1 0 1 1 False False False
378 2026-01-23 16:00:00 2026-01-23 11:00:00-05:00 2026-01-23 0 0.5816189 1 1 0 1 1 False False False
379 2026-01-23 17:00:00 2026-01-23 12:00:00-05:00 2026-01-23 0 0.58383 1 1 0 1 1 False False False
380 2026-01-23 18:00:00 2026-01-23 13:00:00-05:00 2026-01-23 1 0.5591273 1 1 0 1 1 True True True
381 2026-01-23 19:00:00 2026-01-23 14:00:00-05:00 2026-01-23 0 0.5854377 1 1 0 1 1 False False False
382 2026-01-23 20:00:00 2026-01-23 15:00:00-05:00 2026-01-23 0 0.5666198 1 1 0 1 1 False False False
383 2026-01-23 21:00:00 2026-01-23 16:00:00-05:00 2026-01-23 0 0.52965194 1 1 0 1 1 False False False
384 2026-01-23 22:00:00 2026-01-23 17:00:00-05:00 2026-01-23 0 0.5488747 1 1 0 1 1 False False False
385 2026-01-23 23:00:00 2026-01-23 18:00:00-05:00 2026-01-23 1 0.56132966 1 1 0 1 1 True True True
386 2026-01-24 00:00:00 2026-01-23 19:00:00-05:00 2026-01-23 1 0.5149501 1 1 0 1 1 True True True
387 2026-01-24 01:00:00 2026-01-23 20:00:00-05:00 2026-01-23 0 0.5064845 1 1 0 1 1 False False False
388 2026-01-24 02:00:00 2026-01-23 21:00:00-05:00 2026-01-23 1 0.46972445 0 1 0 -1 0 False False False
389 2026-01-24 03:00:00 2026-01-23 22:00:00-05:00 2026-01-23 1 0.47380736 0 1 0 -1 0 False False False
390 2026-01-24 04:00:00 2026-01-23 23:00:00-05:00 2026-01-23 0 0.46636635 0 1 0 -1 0 True False True
391 2026-01-24 05:00:00 2026-01-24 00:00:00-05:00 2026-01-24 0 0.44455072 0 1 0 -1 0 True False True
392 2026-01-24 06:00:00 2026-01-24 01:00:00-05:00 2026-01-24 0 0.4378892 0 1 0 -1 0 True False True
393 2026-01-24 07:00:00 2026-01-24 02:00:00-05:00 2026-01-24 0 0.49291152 0 1 0 -1 0 True False True
394 2026-01-24 08:00:00 2026-01-24 03:00:00-05:00 2026-01-24 0 0.5153457 1 1 0 1 1 False False False
395 2026-01-24 09:00:00 2026-01-24 04:00:00-05:00 2026-01-24 0 0.50489914 1 1 0 1 1 False False False
396 2026-01-24 10:00:00 2026-01-24 05:00:00-05:00 2026-01-24 1 0.5075944 1 1 0 1 1 True True True
397 2026-01-24 11:00:00 2026-01-24 06:00:00-05:00 2026-01-24 0 0.6229508 1 1 0 1 1 False False False
398 2026-01-24 12:00:00 2026-01-24 07:00:00-05:00 2026-01-24 0 0.5855234 1 1 0 1 1 False False False
399 2026-01-24 13:00:00 2026-01-24 08:00:00-05:00 2026-01-24 1 0.5532754 1 1 0 1 1 True True True
400 2026-01-24 14:00:00 2026-01-24 09:00:00-05:00 2026-01-24 1 0.5289959 1 0 0 -1 0 True False False
401 2026-01-24 15:00:00 2026-01-24 10:00:00-05:00 2026-01-24 1 0.49748245 0 0 0 0 0 False False False
402 2026-01-24 16:00:00 2026-01-24 11:00:00-05:00 2026-01-24 0 0.46435258 0 0 0 0 0 True True True
403 2026-01-24 17:00:00 2026-01-24 12:00:00-05:00 2026-01-24 0 0.44683295 0 1 0 -1 0 True False True
404 2026-01-24 18:00:00 2026-01-24 13:00:00-05:00 2026-01-24 1 0.45363894 0 1 0 -1 0 False False False
405 2026-01-24 19:00:00 2026-01-24 14:00:00-05:00 2026-01-24 1 0.5047467 1 0 0 -1 0 True False False
406 2026-01-24 20:00:00 2026-01-24 15:00:00-05:00 2026-01-24 1 0.44000623 0 0 0 0 0 False False False
407 2026-01-24 21:00:00 2026-01-24 16:00:00-05:00 2026-01-24 1 0.4814367 0 0 0 0 0 False False False
408 2026-01-24 22:00:00 2026-01-24 17:00:00-05:00 2026-01-24 0 0.46426073 0 0 0 0 0 True True True
409 2026-01-24 23:00:00 2026-01-24 18:00:00-05:00 2026-01-24 0 0.44996247 0 0 0 0 0 True True True
410 2026-01-25 00:00:00 2026-01-24 19:00:00-05:00 2026-01-24 1 0.47704104 0 1 0 -1 0 False False False
411 2026-01-25 01:00:00 2026-01-24 20:00:00-05:00 2026-01-24 0 0.52445495 1 0 0 -1 0 False False True
412 2026-01-25 02:00:00 2026-01-24 21:00:00-05:00 2026-01-24 0 0.4987614 0 1 0 -1 0 True False True
413 2026-01-25 03:00:00 2026-01-24 22:00:00-05:00 2026-01-24 1 0.5457757 1 1 0 1 1 True True True
414 2026-01-25 04:00:00 2026-01-24 23:00:00-05:00 2026-01-24 1 0.52091444 1 1 0 1 1 True True True
415 2026-01-25 05:00:00 2026-01-25 00:00:00-05:00 2026-01-25 0 0.5040618 1 1 0 1 1 False False False
416 2026-01-25 06:00:00 2026-01-25 01:00:00-05:00 2026-01-25 0 0.4632134 0 1 0 -1 0 True False True
417 2026-01-25 07:00:00 2026-01-25 02:00:00-05:00 2026-01-25 1 0.46248558 0 1 0 -1 0 False False False
418 2026-01-25 08:00:00 2026-01-25 03:00:00-05:00 2026-01-25 1 0.48469254 0 1 0 -1 0 False False False
419 2026-01-25 09:00:00 2026-01-25 04:00:00-05:00 2026-01-25 0 0.46292534 0 1 0 -1 0 True False True
420 2026-01-25 10:00:00 2026-01-25 05:00:00-05:00 2026-01-25 0 0.44414967 0 1 0 -1 0 True False True
421 2026-01-25 11:00:00 2026-01-25 06:00:00-05:00 2026-01-25 1 0.46307206 0 1 0 -1 0 False False False
422 2026-01-25 12:00:00 2026-01-25 07:00:00-05:00 2026-01-25 1 0.4829153 0 1 0 -1 0 False False False
423 2026-01-25 13:00:00 2026-01-25 08:00:00-05:00 2026-01-25 1 0.4569906 0 1 0 -1 0 False False False
424 2026-01-25 14:00:00 2026-01-25 09:00:00-05:00 2026-01-25 1 0.44465676 0 1 0 -1 0 False False False
425 2026-01-25 15:00:00 2026-01-25 10:00:00-05:00 2026-01-25 1 0.4582169 0 1 0 -1 0 False False False
426 2026-01-25 16:00:00 2026-01-25 11:00:00-05:00 2026-01-25 1 0.4422594 0 1 0 -1 0 False False False
427 2026-01-25 17:00:00 2026-01-25 12:00:00-05:00 2026-01-25 0 0.44241613 0 1 0 -1 0 True False True
428 2026-01-25 18:00:00 2026-01-25 13:00:00-05:00 2026-01-25 0 0.44766986 0 1 0 -1 0 True False True
429 2026-01-25 19:00:00 2026-01-25 14:00:00-05:00 2026-01-25 0 0.4483085 0 1 0 -1 0 True False True
430 2026-01-25 20:00:00 2026-01-25 15:00:00-05:00 2026-01-25 0 0.491638 0 1 0 -1 0 True False True
431 2026-01-25 21:00:00 2026-01-25 16:00:00-05:00 2026-01-25 0 0.49061048 0 1 0 -1 0 True False True
432 2026-01-25 22:00:00 2026-01-25 17:00:00-05:00 2026-01-25 0 0.5258992 1 1 0 1 1 False False False
433 2026-01-25 23:00:00 2026-01-25 18:00:00-05:00 2026-01-25 0 0.520721 1 1 0 1 1 False False False
434 2026-01-26 00:00:00 2026-01-25 19:00:00-05:00 2026-01-25 0 0.5159272 1 0 0 -1 0 False False True
435 2026-01-26 01:00:00 2026-01-25 20:00:00-05:00 2026-01-25 1 0.5431046 1 0 0 -1 0 True False False
436 2026-01-26 02:00:00 2026-01-25 21:00:00-05:00 2026-01-25 0 0.51656026 1 0 0 -1 0 False False True
437 2026-01-26 03:00:00 2026-01-25 22:00:00-05:00 2026-01-25 0 0.48795614 0 1 0 -1 0 True False True
438 2026-01-26 04:00:00 2026-01-25 23:00:00-05:00 2026-01-25 0 0.50463694 1 1 0 1 1 False False False
439 2026-01-26 05:00:00 2026-01-26 00:00:00-05:00 2026-01-26 1 0.5487247 1 0 0 -1 0 True False False
440 2026-01-26 06:00:00 2026-01-26 01:00:00-05:00 2026-01-26 1 0.5424091 1 0 0 -1 0 True False False
441 2026-01-26 07:00:00 2026-01-26 02:00:00-05:00 2026-01-26 0 0.47699133 0 0 0 0 0 True True True
442 2026-01-26 08:00:00 2026-01-26 03:00:00-05:00 2026-01-26 1 0.46807176 0 0 0 0 0 False False False
443 2026-01-26 09:00:00 2026-01-26 04:00:00-05:00 2026-01-26 1 0.5724977 1 1 0 1 1 True True True
444 2026-01-26 10:00:00 2026-01-26 05:00:00-05:00 2026-01-26 1 0.5247946 1 1 0 1 1 True True True
445 2026-01-26 11:00:00 2026-01-26 06:00:00-05:00 2026-01-26 0 0.46081114 0 0 0 0 0 True True True
446 2026-01-26 12:00:00 2026-01-26 07:00:00-05:00 2026-01-26 1 0.4425639 0 0 0 0 0 False False False
447 2026-01-26 13:00:00 2026-01-26 08:00:00-05:00 2026-01-26 1 0.47229946 0 1 0 -1 0 False False False
448 2026-01-26 14:00:00 2026-01-26 09:00:00-05:00 2026-01-26 1 0.49050438 0 1 0 -1 0 False False False
449 2026-01-26 15:00:00 2026-01-26 10:00:00-05:00 2026-01-26 0 0.49104828 0 1 0 -1 0 True False True
450 2026-01-26 16:00:00 2026-01-26 11:00:00-05:00 2026-01-26 0 0.45145187 0 1 0 -1 0 True False True
451 2026-01-26 17:00:00 2026-01-26 12:00:00-05:00 2026-01-26 0 0.4638353 0 0 0 0 0 True True True
452 2026-01-26 18:00:00 2026-01-26 13:00:00-05:00 2026-01-26 0 0.4618484 0 1 0 -1 0 True False True
453 2026-01-26 19:00:00 2026-01-26 14:00:00-05:00 2026-01-26 1 0.5018996 1 1 0 1 1 True True True
454 2026-01-26 20:00:00 2026-01-26 15:00:00-05:00 2026-01-26 1 0.5166753 1 1 0 1 1 True True True
455 2026-01-26 21:00:00 2026-01-26 16:00:00-05:00 2026-01-26 0 0.52520907 1 1 0 1 1 False False False
456 2026-01-26 22:00:00 2026-01-26 17:00:00-05:00 2026-01-26 0 0.5878727 1 1 0 1 1 False False False
457 2026-01-26 23:00:00 2026-01-26 18:00:00-05:00 2026-01-26 1 0.54239446 1 1 0 1 1 True True True
458 2026-01-27 00:00:00 2026-01-26 19:00:00-05:00 2026-01-26 0 0.53454095 1 1 0 1 1 False False False
459 2026-01-27 01:00:00 2026-01-26 20:00:00-05:00 2026-01-26 1 0.467306 0 1 0 -1 0 False False False
460 2026-01-27 02:00:00 2026-01-26 21:00:00-05:00 2026-01-26 0 0.46864188 0 1 0 -1 0 True False True
461 2026-01-27 03:00:00 2026-01-26 22:00:00-05:00 2026-01-26 1 0.46822935 0 1 0 -1 0 False False False
462 2026-01-27 04:00:00 2026-01-26 23:00:00-05:00 2026-01-26 0 0.49130705 0 1 0 -1 0 True False True
463 2026-01-27 05:00:00 2026-01-27 00:00:00-05:00 2026-01-27 0 0.46864477 0 1 0 -1 0 True False True
464 2026-01-27 06:00:00 2026-01-27 01:00:00-05:00 2026-01-27 1 0.486711 0 1 0 -1 0 False False False
465 2026-01-27 07:00:00 2026-01-27 02:00:00-05:00 2026-01-27 1 0.5504777 1 1 0 1 1 True True True
466 2026-01-27 08:00:00 2026-01-27 03:00:00-05:00 2026-01-27 0 0.4907592 0 1 0 -1 0 True False True
467 2026-01-27 09:00:00 2026-01-27 04:00:00-05:00 2026-01-27 0 0.4668024 0 1 0 -1 0 True False True
468 2026-01-27 10:00:00 2026-01-27 05:00:00-05:00 2026-01-27 0 0.50845945 1 1 0 1 1 False False False
469 2026-01-27 11:00:00 2026-01-27 06:00:00-05:00 2026-01-27 1 0.5608594 1 1 0 1 1 True True True
470 2026-01-27 12:00:00 2026-01-27 07:00:00-05:00 2026-01-27 0 0.51024324 1 1 0 1 1 False False False
471 2026-01-27 13:00:00 2026-01-27 08:00:00-05:00 2026-01-27 0 0.51232666 1 1 0 1 1 False False False
472 2026-01-27 14:00:00 2026-01-27 09:00:00-05:00 2026-01-27 0 0.4854087 0 1 0 -1 0 True False True
473 2026-01-27 15:00:00 2026-01-27 10:00:00-05:00 2026-01-27 0 0.48897353 0 1 0 -1 0 True False True
474 2026-01-27 16:00:00 2026-01-27 11:00:00-05:00 2026-01-27 0 0.46034786 0 1 0 -1 0 True False True
475 2026-01-27 17:00:00 2026-01-27 12:00:00-05:00 2026-01-27 1 0.5265957 1 1 0 1 1 True True True
476 2026-01-27 18:00:00 2026-01-27 13:00:00-05:00 2026-01-27 1 0.5300391 1 1 0 1 1 True True True
477 2026-01-27 19:00:00 2026-01-27 14:00:00-05:00 2026-01-27 0 0.49372414 0 1 0 -1 0 True False True
478 2026-01-27 20:00:00 2026-01-27 15:00:00-05:00 2026-01-27 1 0.4988915 0 1 0 -1 0 False False False
479 2026-01-27 21:00:00 2026-01-27 16:00:00-05:00 2026-01-27 1 0.53798217 1 1 0 1 1 True True True
480 2026-01-27 22:00:00 2026-01-27 17:00:00-05:00 2026-01-27 0 0.5018221 1 1 0 1 1 False False False
481 2026-01-27 23:00:00 2026-01-27 18:00:00-05:00 2026-01-27 0 0.49853957 0 1 0 -1 0 True False True
482 2026-01-28 00:00:00 2026-01-27 19:00:00-05:00 2026-01-27 1 0.5263078 1 1 0 1 1 True True True
483 2026-01-28 01:00:00 2026-01-27 20:00:00-05:00 2026-01-27 0 0.48831585 0 1 0 -1 0 True False True
484 2026-01-28 02:00:00 2026-01-27 21:00:00-05:00 2026-01-27 1 0.53863657 1 1 0 1 1 True True True
485 2026-01-28 03:00:00 2026-01-27 22:00:00-05:00 2026-01-27 0 0.5574659 1 1 0 1 1 False False False
486 2026-01-28 04:00:00 2026-01-27 23:00:00-05:00 2026-01-27 0 0.529262 1 1 0 1 1 False False False
487 2026-01-28 05:00:00 2026-01-28 00:00:00-05:00 2026-01-28 0 0.46035758 0 1 0 -1 0 True False True
488 2026-01-28 06:00:00 2026-01-28 01:00:00-05:00 2026-01-28 1 0.5224445 1 1 0 1 1 True True True
489 2026-01-28 07:00:00 2026-01-28 02:00:00-05:00 2026-01-28 0 0.51074743 1 1 0 1 1 False False False
490 2026-01-28 08:00:00 2026-01-28 03:00:00-05:00 2026-01-28 0 0.519096 1 1 0 1 1 False False False
491 2026-01-28 09:00:00 2026-01-28 04:00:00-05:00 2026-01-28 1 0.53050256 1 1 0 1 1 True True True
492 2026-01-28 10:00:00 2026-01-28 05:00:00-05:00 2026-01-28 1 0.54665875 1 1 0 1 1 True True True
493 2026-01-28 11:00:00 2026-01-28 06:00:00-05:00 2026-01-28 1 0.5373869 1 1 0 1 1 True True True
494 2026-01-28 12:00:00 2026-01-28 07:00:00-05:00 2026-01-28 1 0.53682816 1 1 0 1 1 True True True
495 2026-01-28 13:00:00 2026-01-28 08:00:00-05:00 2026-01-28 1 0.50733596 1 1 0 1 1 True True True
496 2026-01-28 14:00:00 2026-01-28 09:00:00-05:00 2026-01-28 0 0.466285 0 1 0 -1 0 True False True
+58
View File
@@ -0,0 +1,58 @@
{
"测试期间": {
"开始日期": "2026-01-01 00:00:00",
"结束日期": "2026-01-30 00:00:00",
"总天数": 22,
"总样本数": 495
},
"单模型性能": {
"DLinear": {
"准确率": "53.33%",
"预测上涨比例": "47.1%"
},
"N-BEATS": {
"准确率": "49.90%",
"预测上涨比例": "73.5%"
},
"TTM-R2": {
"准确率": "51.31%",
"预测上涨比例": "23.6%"
}
},
"集成策略性能": {
"双模型交集": {
"准确率": "53.39%",
"覆盖率": "47.68%",
"有效预测数": 236
},
"三模型投票": {
"准确率": "52.93%",
"覆盖率": "100.00%",
"有效预测数": 495
}
},
"DLinear概率分布": {
"0.4-0.5区间": "262个 (52.9%)",
"0.5-0.6区间": "229个 (46.3%)",
"0.6-0.7区间": "4个 (0.8%)",
"平均概率": "0.501"
},
"最佳/最差交易日": {
"双模型最佳日": {
"日期": "2026-01-11",
"准确率": "83.3%"
},
"双模型最差日": {
"日期": "2026-01-23",
"准确率": "28.6%"
},
"三模型最佳日": {
"日期": "2026-01-28",
"准确率": "80.0%"
},
"三模型最差日": {
"日期": "2026-01-23",
"准确率": "29.2%"
}
}
}
+458
View File
@@ -0,0 +1,458 @@
"""保存所有测试结果到项目 - 生成完整报告"""
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()
+451
View File
@@ -0,0 +1,451 @@
"""智能休息策略回测:基于历史准确率动态决定是否交易"""
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 data.fetcher import BinanceDataFetcher
# ==================== 预处理器定义 ====================
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)
print("\n策略规则:")
print(" 1. 如果前一天准确率超过70% → 当天休息")
print(" 2. 如果连续两天准确率超过60% → 第三天休息")
print(" 3. 其他情况 → 正常交易")
print("="*80)
# ========== 加载模型 ==========
print("\n[1/4] 加载模型...")
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# 加载DLinear
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)
# 加载N-BEATS
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)
# 加载TTM-R2
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(f"✓ 三个模型已加载")
# ========== 获取数据 ==========
print("\n[2/4] 获取2026年1月测试数据(1月1日-1月30日)...")
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)} 行")
print(f"✓ 时间范围: {df_2026['timestamp'].min()} 至 {df_2026['timestamp'].max()}")
# ========== 预处理 ==========
print("\n[3/4] 预处理数据...")
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]
y_nbeats = y_nbeats[:min_samples]
# ========== 预测 ==========
print("\n[4/4] 执行预测...")
# 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))
dlinear_preds = (dlinear_probs > 0.5).astype(int).flatten()
# 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
# ========== 构建每日数据 ==========
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
results_df = pd.DataFrame({
'date': dates,
'timestamp_et': timestamps_et,
'true_label': true_labels,
'two_model_pred': two_model_preds,
'three_model_pred': three_model_preds,
'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'
})
daily_stats.columns = ['total_samples', 'two_model_valid', 'two_model_correct', 'three_model_correct']
# 计算每日准确率
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_accuracy'] = daily_stats['two_model_accuracy'].fillna(0)
# ========== 应用智能休息策略 ==========
print("\n" + "="*80)
print("📊 应用智能休息策略")
print("="*80)
# 为每一天添加"是否休息"标记
daily_stats['two_model_rest'] = False
daily_stats['three_model_rest'] = False
daily_stats['rest_reason'] = ''
dates_list = daily_stats.index.tolist()
for i, date in enumerate(dates_list):
if i == 0:
continue # 第一天没有历史数据,不休息
# 获取前一天和前两天的数据
prev_date = dates_list[i-1]
prev2_date = dates_list[i-2] if i >= 2 else None
# 双模型休息逻辑
# 规则1:前一天准确率超过70%
if daily_stats.loc[prev_date, 'two_model_accuracy'] > 70:
daily_stats.loc[date, 'two_model_rest'] = True
daily_stats.loc[date, 'rest_reason'] = f'前日准确率{daily_stats.loc[prev_date, "two_model_accuracy"]:.1f}%>70%'
# 规则2:连续两天准确率超过60%
elif (prev2_date is not None and
daily_stats.loc[prev_date, 'two_model_accuracy'] > 60 and
daily_stats.loc[prev2_date, 'two_model_accuracy'] > 60):
daily_stats.loc[date, 'two_model_rest'] = True
daily_stats.loc[date, 'rest_reason'] = f'连续2天>60% ({daily_stats.loc[prev2_date, "two_model_accuracy"]:.1f}%, {daily_stats.loc[prev_date, "two_model_accuracy"]:.1f}%)'
# 三模型休息逻辑
# 规则1:前一天准确率超过70%
if daily_stats.loc[prev_date, 'three_model_accuracy'] > 70:
daily_stats.loc[date, 'three_model_rest'] = True
if not daily_stats.loc[date, 'rest_reason']:
daily_stats.loc[date, 'rest_reason'] = f'三模前日{daily_stats.loc[prev_date, "three_model_accuracy"]:.1f}%>70%'
# 规则2:连续两天准确率超过60%
elif (prev2_date is not None and
daily_stats.loc[prev_date, 'three_model_accuracy'] > 60 and
daily_stats.loc[prev2_date, 'three_model_accuracy'] > 60):
daily_stats.loc[date, 'three_model_rest'] = True
if not daily_stats.loc[date, 'rest_reason']:
daily_stats.loc[date, 'rest_reason'] = f'三模连续2天>60%'
# ========== 统计休息天数 ==========
two_model_rest_days = daily_stats['two_model_rest'].sum()
three_model_rest_days = daily_stats['three_model_rest'].sum()
total_days = len(daily_stats)
print(f"\n休息天数统计:")
print(f" 总交易日: {total_days}天")
print(f" 双模型休息: {two_model_rest_days}天 ({two_model_rest_days/total_days*100:.1f}%)")
print(f" 三模型休息: {three_model_rest_days}天 ({three_model_rest_days/total_days*100:.1f}%)")
# ========== 计算休息策略的实际表现 ==========
print("\n" + "="*80)
print("📈 策略对比:正常交易 vs 智能休息")
print("="*80)
# 双模型策略
print(f"\n🔵 双模型交集策略:")
# 正常交易(不休息)
two_normal_correct = daily_stats['two_model_correct'].sum()
two_normal_total = daily_stats['two_model_valid'].sum()
two_normal_acc = two_normal_correct / two_normal_total * 100 if two_normal_total > 0 else 0
print(f"\n 📊 正常交易(不休息):")
print(f" 交易天数: {total_days}天")
print(f" 有效预测: {two_normal_total}次")
print(f" 正确预测: {two_normal_correct}次")
print(f" 准确率: {two_normal_acc:.2f}%")
# 智能休息
two_rest_mask = ~daily_stats['two_model_rest']
two_rest_correct = daily_stats.loc[two_rest_mask, 'two_model_correct'].sum()
two_rest_total = daily_stats.loc[two_rest_mask, 'two_model_valid'].sum()
two_rest_acc = two_rest_correct / two_rest_total * 100 if two_rest_total > 0 else 0
two_trade_days = two_rest_mask.sum()
print(f"\n 🛌 智能休息策略:")
print(f" 交易天数: {two_trade_days}天 (休息{two_model_rest_days}天)")
print(f" 有效预测: {two_rest_total}次")
print(f" 正确预测: {two_rest_correct}次")
print(f" 准确率: {two_rest_acc:.2f}%")
two_acc_diff = two_rest_acc - two_normal_acc
two_diff_mark = "✅" if two_acc_diff > 0 else ("⚠️" if two_acc_diff < 0 else "")
print(f"\n 准确率变化: {two_acc_diff:+.2f}% {two_diff_mark}")
# 休息日的实际表现
if two_model_rest_days > 0:
two_rest_days_mask = daily_stats['two_model_rest']
two_rest_day_correct = daily_stats.loc[two_rest_days_mask, 'two_model_correct'].sum()
two_rest_day_total = daily_stats.loc[two_rest_days_mask, 'two_model_valid'].sum()
two_rest_day_acc = two_rest_day_correct / two_rest_day_total * 100 if two_rest_day_total > 0 else 0
print(f"\n 🔍 被休息的{two_model_rest_days}天实际表现:")
print(f" 准确率: {two_rest_day_acc:.2f}%")
avoided_loss = "避免了低准确率" if two_rest_day_acc < two_normal_acc else "⚠️ 错过了好机会"
print(f" 评价: {avoided_loss}")
# 三模型策略
print(f"\n🟢 三模型投票策略:")
# 正常交易
three_normal_correct = daily_stats['three_model_correct'].sum()
three_normal_total = daily_stats['total_samples'].sum()
three_normal_acc = three_normal_correct / three_normal_total * 100
print(f"\n 📊 正常交易(不休息):")
print(f" 交易天数: {total_days}天")
print(f" 有效预测: {three_normal_total}次")
print(f" 正确预测: {three_normal_correct}次")
print(f" 准确率: {three_normal_acc:.2f}%")
# 智能休息
three_rest_mask = ~daily_stats['three_model_rest']
three_rest_correct = daily_stats.loc[three_rest_mask, 'three_model_correct'].sum()
three_rest_total = daily_stats.loc[three_rest_mask, 'total_samples'].sum()
three_rest_acc = three_rest_correct / three_rest_total * 100 if three_rest_total > 0 else 0
three_trade_days = three_rest_mask.sum()
print(f"\n 🛌 智能休息策略:")
print(f" 交易天数: {three_trade_days}天 (休息{three_model_rest_days}天)")
print(f" 有效预测: {three_rest_total}次")
print(f" 正确预测: {three_rest_correct}次")
print(f" 准确率: {three_rest_acc:.2f}%")
three_acc_diff = three_rest_acc - three_normal_acc
three_diff_mark = "✅" if three_acc_diff > 0 else ("⚠️" if three_acc_diff < 0 else "")
print(f"\n 准确率变化: {three_acc_diff:+.2f}% {three_diff_mark}")
# 休息日的实际表现
if three_model_rest_days > 0:
three_rest_days_mask = daily_stats['three_model_rest']
three_rest_day_correct = daily_stats.loc[three_rest_days_mask, 'three_model_correct'].sum()
three_rest_day_total = daily_stats.loc[three_rest_days_mask, 'total_samples'].sum()
three_rest_day_acc = three_rest_day_correct / three_rest_day_total * 100
print(f"\n 🔍 被休息的{three_model_rest_days}天实际表现:")
print(f" 准确率: {three_rest_day_acc:.2f}%")
avoided_loss = "避免了低准确率" if three_rest_day_acc < three_normal_acc else "⚠️ 错过了好机会"
print(f" 评价: {avoided_loss}")
# ========== 详细日志 ==========
print("\n" + "="*80)
print("📅 每日详细记录")
print("="*80)
print(f"\n{'日期':<12} {'双模准确率':>10} {'三模准确率':>10} {'休息状态':<12} {'休息原因':<40}")
print("-" * 95)
for date, row in daily_stats.iterrows():
two_acc = row['two_model_accuracy']
three_acc = row['three_model_accuracy']
rest_status = ""
if row['two_model_rest']:
rest_status += "双休 "
if row['three_model_rest']:
rest_status += "三休"
if not rest_status:
rest_status = "正常交易"
# 准确率标记
two_mark = "🔥" if two_acc >= 70 else ("✅" if two_acc >= 60 else "")
three_mark = "🔥" if three_acc >= 70 else ("✅" if three_acc >= 60 else "")
print(f"{date} {two_acc:>9.1f}{two_mark} {three_acc:>9.1f}{three_mark} "
f"{rest_status:<12} {row['rest_reason']:<40}")
print("\n" + "="*80)
print("✓ 智能休息策略回测完成!")
print("="*80)
if __name__ == "__main__":
main()
-232
View File
@@ -1,232 +0,0 @@
"""在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()
+271
View File
@@ -0,0 +1,271 @@
"""训练CatBoost二分类模型用于BTC 1小时涨跌预测"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import numpy as np
import pandas as pd
import pandas_ta as ta
from catboost import CatBoostClassifier, Pool
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score, confusion_matrix
from data.fetcher import BinanceDataFetcher
import pickle
from datetime import datetime
def add_technical_features(df):
"""添加丰富的技术指标特征"""
df = df.copy()
# 基础价格特征
df['price_change'] = df['close'] - df['open']
df['price_change_pct'] = (df['close'] - df['open']) / df['open']
df['high_low_range'] = df['high'] - df['low']
df['high_low_range_pct'] = (df['high'] - df['low']) / df['low']
# 移动平均线
df['sma_7'] = ta.sma(df['close'], length=7)
df['sma_25'] = ta.sma(df['close'], length=25)
df['sma_99'] = ta.sma(df['close'], length=99)
df['ema_12'] = ta.ema(df['close'], length=12)
df['ema_26'] = ta.ema(df['close'], length=26)
# RSI
df['rsi_14'] = ta.rsi(df['close'], length=14)
df['rsi_7'] = ta.rsi(df['close'], length=7)
# MACD
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]
# 布林带
bbands = ta.bbands(df['close'], length=20)
if bbands is not None and not bbands.empty:
df['bb_upper'] = bbands.iloc[:, 0]
df['bb_mid'] = bbands.iloc[:, 1]
df['bb_lower'] = bbands.iloc[:, 2]
df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_mid']
df['bb_position'] = (df['close'] - df['bb_lower']) / (df['bb_upper'] - df['bb_lower'])
# ATR (Average True Range)
df['atr'] = ta.atr(df['high'], df['low'], df['close'], length=14)
# 成交量特征
df['volume_sma_7'] = ta.sma(df['volume'], length=7)
df['volume_ratio'] = df['volume'] / df['volume_sma_7']
# OBV (On-Balance Volume)
df['obv'] = ta.obv(df['close'], df['volume'])
# Stochastic Oscillator
stoch = ta.stoch(df['high'], df['low'], df['close'])
if stoch is not None and not stoch.empty:
df['stoch_k'] = stoch.iloc[:, 0]
df['stoch_d'] = stoch.iloc[:, 1]
# ADX (Average Directional Index)
adx = ta.adx(df['high'], df['low'], df['close'])
if adx is not None and not adx.empty:
df['adx'] = adx.iloc[:, 0]
# 价格动量
df['momentum_1'] = df['close'].pct_change(1)
df['momentum_3'] = df['close'].pct_change(3)
df['momentum_7'] = df['close'].pct_change(7)
return df
def create_labels(df, forecast_horizon=1):
"""创建二分类标签"""
df = df.copy()
df['future_price'] = df['close'].shift(-forecast_horizon)
df['future_return'] = (df['future_price'] - df['close']) / df['close']
df['label'] = (df['future_return'] > 0).astype(int) # 1=涨, 0=跌
return df[:-forecast_horizon]
def prepare_data(df):
"""准备训练数据"""
# 添加技术指标
df = add_technical_features(df)
# 创建标签
df = create_labels(df, forecast_horizon=1)
# 删除NaN
df = df.dropna().reset_index(drop=True)
# 选择特征列(排除原始时间戳和标签列)
exclude_cols = ['timestamp', 'label', 'future_price', 'future_return']
feature_cols = [col for col in df.columns if col not in exclude_cols]
X = df[feature_cols]
y = df['label']
return X, y, feature_cols
def main():
print("="*80)
print("训练CatBoost二分类模型 - BTC 1小时涨跌预测")
print("="*80)
# ========== 步骤1: 获取数据 ==========
print("\n[1/5] 获取历史数据...")
fetcher = BinanceDataFetcher()
# 获取训练数据 (2020-2024)
df_train = fetcher.fetch_ohlcv('BTC', '1h', start_date='2020-01-01',
end_date='2024-12-31', use_cache=True)
# 获取验证数据 (2025)
df_val = fetcher.fetch_ohlcv('BTC', '1h', start_date='2025-01-01',
end_date='2025-12-31', use_cache=True)
print(f"✓ 训练数据: {len(df_train)} 行 ({df_train['timestamp'].min()} 至 {df_train['timestamp'].max()})")
print(f"✓ 验证数据: {len(df_val)} 行 ({df_val['timestamp'].min()} 至 {df_val['timestamp'].max()})")
# ========== 步骤2: 特征工程 ==========
print("\n[2/5] 特征工程...")
X_train, y_train, feature_cols = prepare_data(df_train)
X_val, y_val, _ = prepare_data(df_val)
print(f"✓ 特征数量: {len(feature_cols)}")
print(f"✓ 训练样本: {len(X_train)} (涨: {(y_train==1).sum()}, 跌: {(y_train==0).sum()})")
print(f"✓ 验证样本: {len(X_val)} (涨: {(y_val==1).sum()}, 跌: {(y_val==0).sum()})")
# 显示部分特征
print(f"\n特征列表 (前20个):")
for i, col in enumerate(feature_cols[:20], 1):
print(f" {i:2d}. {col}")
if len(feature_cols) > 20:
print(f" ... 还有 {len(feature_cols)-20} 个特征")
# ========== 步骤3: 训练CatBoost模型 ==========
print("\n[3/5] 训练CatBoost模型...")
# 创建CatBoost数据池
train_pool = Pool(X_train, y_train)
val_pool = Pool(X_val, y_val)
# 配置CatBoost参数
model = CatBoostClassifier(
iterations=1000,
learning_rate=0.05,
depth=6,
l2_leaf_reg=3,
loss_function='Logloss',
eval_metric='Accuracy',
random_seed=42,
verbose=100,
early_stopping_rounds=50,
task_type='CPU', # 如果有GPU可改为'GPU'
)
# 训练模型
model.fit(
train_pool,
eval_set=val_pool,
use_best_model=True,
plot=False
)
print(f"\n✓ 训练完成")
print(f" 最佳迭代轮次: {model.get_best_iteration()}")
print(f" 最佳验证准确率: {model.get_best_score()['validation']['Accuracy']:.4f}")
# ========== 步骤4: 评估模型 ==========
print("\n[4/5] 评估模型性能...")
# 训练集预测
y_train_pred = model.predict(X_train)
train_acc = accuracy_score(y_train, y_train_pred)
# 验证集预测
y_val_pred = model.predict(X_val)
val_acc = accuracy_score(y_val, y_val_pred)
print(f"\n训练集性能:")
print(f" 准确率: {train_acc*100:.2f}%")
print(f"\n验证集性能:")
print(f" 准确率: {val_acc*100:.2f}%")
# 混淆矩阵
cm = confusion_matrix(y_val, y_val_pred)
print(f"\n混淆矩阵 (验证集):")
print(f" 预测↓ 预测↑")
print(f"真实↓ {cm[0,0]:4d} {cm[0,1]:4d}")
print(f"真实↑ {cm[1,0]:4d} {cm[1,1]:4d}")
# 分类报告
print(f"\n分类报告 (验证集):")
print(classification_report(y_val, y_val_pred, target_names=['下跌', '上涨']))
# 特征重要性 (Top 20)
feature_importance = model.get_feature_importance()
importance_df = pd.DataFrame({
'feature': feature_cols,
'importance': feature_importance
}).sort_values('importance', ascending=False)
print(f"\nTop 20 重要特征:")
for i, (idx, row) in enumerate(importance_df.head(20).iterrows(), 1):
print(f" {i:2d}. {row['feature']:<20s} {row['importance']:7.2f}")
# ========== 步骤5: 保存模型 ==========
print("\n[5/5] 保存模型...")
checkpoint_dir = './models/checkpoints/catboost_classification_2026'
os.makedirs(checkpoint_dir, exist_ok=True)
# 保存CatBoost模型
model_path = f'{checkpoint_dir}/catboost_model.cbm'
model.save_model(model_path, format='cbm')
# 保存特征列表和元数据
metadata = {
'feature_cols': feature_cols,
'train_acc': float(train_acc),
'val_acc': float(val_acc),
'best_iteration': int(model.get_best_iteration()),
'train_samples': len(X_train),
'val_samples': len(X_val),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'train_date_range': f"{df_train['timestamp'].min()} - {df_train['timestamp'].max()}",
'val_date_range': f"{df_val['timestamp'].min()} - {df_val['timestamp'].max()}",
}
with open(f'{checkpoint_dir}/metadata.pkl', 'wb') as f:
pickle.dump(metadata, f)
print(f"✓ 模型已保存到: {model_path}")
print(f"✓ 元数据已保存到: {checkpoint_dir}/metadata.pkl")
# ========== 总结 ==========
print("\n" + "="*80)
print("✓ CatBoost二分类模型训练完成!")
print("="*80)
print(f"\n模型性能总结:")
print(f" 训练集准确率: {train_acc*100:.2f}%")
print(f" 验证集准确率: {val_acc*100:.2f}%")
print(f" 特征数量: {len(feature_cols)}")
print(f" 最佳迭代: {model.get_best_iteration()}")
print(f"\n保存位置:")
print(f" {checkpoint_dir}/")
print("\n" + "="*80)
if __name__ == "__main__":
main()
+283
View File
@@ -0,0 +1,283 @@
"""使用N-BEATS模型训练:预测未来1小时BTC价格"""
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import numpy as np
import pandas as pd
import pandas_ta as ta
from sklearn.preprocessing import StandardScaler
import torch
import torch.nn as nn
from torch.utils.data import DataLoader, TensorDataset
from data.fetcher import BinanceDataFetcher
from models.nbeats.model import NBeats, NBeatsConfig
import json
import pickle
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:
if not self.is_fitted:
raise ValueError("Must fit before transform")
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 train_nbeats():
print("="*80)
print("N-BEATS模型训练 - 预测未来1小时BTC价格")
print("="*80)
# ========== 步骤1: 获取数据 ==========
print("\n[1/8] 获取BTC 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(f"✓ 价格: ${df['close'].iloc[0]:,.2f} → ${df['close'].iloc[-1]:,.2f}")
# ========== 步骤2: 预处理 ==========
print("\n[2/8] 数据预处理...")
preprocessor = NBeatsPreprocessor(lookback=168, forecast_horizon=1)
X, y, processed_df = preprocessor.preprocess(df, fit=True)
print(f"✓ 序列数量: {len(X):,}")
print(f"✓ 输入形状: {X.shape}")
print(f"✓ 输出形状: {y.shape}")
# 还原真实价格看统计
y_real = preprocessor.inverse_transform_price(y)
print(f"\n✓ 目标价格统计:")
print(f" 最小值: ${y_real.min():,.2f}")
print(f" 最大值: ${y_real.max():,.2f}")
print(f" 均值: ${y_real.mean():,.2f}")
# ========== 步骤3: 划分数据集 ==========
print("\n[3/8] 划分数据集(90%训练 + 10%验证)...")
n = len(X)
train_end = int(n * 0.9)
X_train, y_train = X[:train_end], y[:train_end]
X_val, y_val = X[train_end:], y[train_end:]
print(f" 训练集: {len(X_train):,} 样本")
print(f" 验证集: {len(X_val):,} 样本")
# ========== 步骤4: 创建数据加载器 ==========
print("\n[4/8] 创建数据加载器...")
train_dataset = TensorDataset(
torch.FloatTensor(X_train),
torch.FloatTensor(y_train)
)
val_dataset = TensorDataset(
torch.FloatTensor(X_val),
torch.FloatTensor(y_val)
)
train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=128, shuffle=False)
# ========== 步骤5: 创建N-BEATS模型 ==========
print("\n[5/8] 创建N-BEATS模型...")
config = NBeatsConfig(
input_size=168,
forecast_size=1,
stacks=['trend', 'seasonality', 'generic'],
num_blocks_per_stack=3,
block_layers=4,
layer_size=256,
trend_degree=2,
seasonality_harmonics=4
)
model = NBeats(config)
device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
model = model.to(device)
print(f"✓ 模型参数: {model.count_parameters():,}")
print(f"✓ 设备: {device}")
print(f"✓ 模型架构: Trend + Seasonality + Generic")
# ========== 步骤6: 训练 ==========
print("\n[6/8] 开始训练...")
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5, verbose=True
)
best_val_loss = float('inf')
patience = 15
patience_counter = 0
max_epochs = 50
for epoch in range(max_epochs):
# 训练
model.train()
train_loss = 0
for batch_X, batch_y in train_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
optimizer.zero_grad()
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss /= len(train_loader)
# 验证
model.eval()
val_loss = 0
with torch.no_grad():
for batch_X, batch_y in val_loader:
batch_X, batch_y = batch_X.to(device), batch_y.to(device)
outputs = model(batch_X)
loss = criterion(outputs, batch_y)
val_loss += loss.item()
val_loss /= len(val_loader)
# 学习率调度
scheduler.step(val_loss)
print(f"Epoch {epoch+1}/{max_epochs}")
print(f" Train loss: {train_loss:.6f}")
print(f" Val loss: {val_loss:.6f}")
print(f" LR: {optimizer.param_groups[0]['lr']:.6f}")
# 保存最佳模型
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
os.makedirs('./models/checkpoints/nbeats_btc_1h', exist_ok=True)
model.save('./models/checkpoints/nbeats_btc_1h/best_model.pt')
print(f" Saved best model (val_loss: {val_loss:.6f})")
else:
patience_counter += 1
# Early stopping
if patience_counter >= patience:
print(f"\nEarly stopping at epoch {epoch+1}")
break
# ========== 步骤7: 评估 ==========
print("\n[7/8] 评估模型...")
# 重新加载最佳模型
checkpoint = torch.load('./models/checkpoints/nbeats_btc_1h/best_model.pt', map_location=device)
model = NBeats(checkpoint['config'])
model.load_state_dict(checkpoint['model_state_dict'])
model = model.to(device)
model.eval()
val_preds = []
with torch.no_grad():
for batch_X, _ in val_loader:
batch_X = batch_X.to(device)
pred = model(batch_X)
val_preds.extend(pred.cpu().numpy())
val_preds = np.array(val_preds)
# 还原真实价格
pred_prices = preprocessor.inverse_transform_price(val_preds)
true_prices = preprocessor.inverse_transform_price(y_val)
# 当前价格
val_start_idx = train_end + 168
current_prices = processed_df['close'].iloc[val_start_idx:val_start_idx+len(pred_prices)].values
# 方向准确率
pred_direction = (pred_prices > current_prices).astype(int)
true_direction = (true_prices > current_prices).astype(int)
direction_acc = (pred_direction == true_direction).mean()
# 价格预测误差
price_mae = np.abs(pred_prices - true_prices).mean()
price_rmse = np.sqrt(((pred_prices - true_prices) ** 2).mean())
price_mape = np.mean(np.abs((true_prices - pred_prices) / true_prices)) * 100
print("\n" + "="*80)
print("训练完成 - N-BEATS模型性能")
print("="*80)
print(f"\n📊 价格预测误差:")
print(f" MAE: ${price_mae:,.2f}")
print(f" RMSE: ${price_rmse:,.2f}")
print(f" MAPE: {price_mape:.2f}%")
print(f"\n📈 方向预测:")
print(f" 方向准确率: {direction_acc*100:.2f}%")
# ========== 步骤8: 保存 ==========
print("\n[8/8] 保存模型和预处理器...")
with open('./models/checkpoints/nbeats_btc_1h/preprocessor.pkl', 'wb') as f:
pickle.dump(preprocessor, f)
metrics = {
'price_mae': float(price_mae),
'price_rmse': float(price_rmse),
'price_mape': float(price_mape),
'direction_accuracy': float(direction_acc),
'best_val_loss': float(best_val_loss)
}
with open('./models/checkpoints/nbeats_btc_1h/metrics.json', 'w') as f:
json.dump(metrics, f, indent=2)
print(f"\n✓ 模型保存至: ./models/checkpoints/nbeats_btc_1h/")
print("="*80)
if __name__ == "__main__":
train_nbeats()