Initial commit: BTC 1h prediction model (53.10% accuracy)

🎯 项目成果:
- 准确率: 53.10% (2026年测试集)
- 任务类型: 二分类 (预测涨/跌)
- 特征: OHLCV + RSI + MACD (9个特征)
- 训练数据: 2020-2025年BTC小时数据 (52,351序列)
- 测试数据: 2026年1-29日 (484序列)

📊 性能指标:
- 精确率: 51.79%
- 召回率: 49.36%
- F1分数: 0.5054
- 预测分布: 非常平衡 (偏差仅-11个)

🔬 实验历史:
- 尝试了5种方案 (回归/分类, 不同数据划分)
- 分类任务优于回归任务 (53.10% vs 49.5%)
- 全量训练避免了过拟合问题

📦 主要模型文件:
- models/checkpoints/dlinear_classification_2026/
  - best_model.pt (最佳模型)
  - preprocessor.pkl (预处理器)
  - test_2026_results.json (测试结果)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
crypto_predictorandClaude Sonnet 4.5 committed 2026-01-29 21:27:15 +08:00
commit 9bbdc8d42b
82 files changed
+8742

No files matched your search

+182
View File
@@ -0,0 +1,182 @@
"""Base model interface for all prediction models."""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Literal, Optional
import torch
import torch.nn as nn
@dataclass
class ModelConfig:
"""Base configuration for all models."""
input_dim: int # Number of input features
lookback: int # Sequence length
task: Literal['classification', 'regression'] = 'classification'
hidden_dim: int = 512
dropout: float = 0.1
class BasePredictor(nn.Module, ABC):
"""Abstract base class for all prediction models."""
def __init__(self, config: ModelConfig):
"""Initialize model with configuration.
Args:
config: Model configuration
"""
super().__init__()
self.config = config
@abstractmethod
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""Forward pass.
Args:
x: Input tensor of shape (batch_size, lookback, input_dim)
Returns:
Output tensor. Shape depends on task:
- Classification: (batch_size, 1) with sigmoid output
- Regression: (batch_size, 1) with raw value
"""
pass
def predict(self, x: torch.Tensor) -> torch.Tensor:
"""Make predictions (inference mode).
Args:
x: Input tensor
Returns:
Predictions
"""
self.eval()
with torch.no_grad():
return self.forward(x)
def get_loss_function(self, pos_weight=None, device='cpu') -> nn.Module:
"""Get appropriate loss function for the task.
Args:
pos_weight: Weight for positive class (for imbalanced data)
device: Device to place tensors on
Returns:
Loss function
"""
if self.config.task == 'classification':
if pos_weight is not None:
# 使用float32以兼容MPS (Mac M1)
pos_weight_tensor = torch.tensor([pos_weight], dtype=torch.float32).to(device)
return nn.BCEWithLogitsLoss(pos_weight=pos_weight_tensor)
else:
return nn.BCELoss()
else: # regression
return nn.MSELoss()
def save(self, path: str):
"""Save model checkpoint.
Args:
path: Path to save checkpoint
"""
torch.save({
'model_state_dict': self.state_dict(),
'config': self.config
}, path)
@classmethod
def load(cls, path: str, device: Optional[str] = None):
"""Load model from checkpoint.
Args:
path: Path to checkpoint
device: Device to load model to
Returns:
Loaded model
"""
checkpoint = torch.load(path, map_location=device)
model = cls(checkpoint['config'])
model.load_state_dict(checkpoint['model_state_dict'])
return model
def to_onnx(self, path: str, dummy_input: Optional[torch.Tensor] = None):
"""Export model to ONNX format.
Args:
path: Path to save ONNX model
dummy_input: Example input tensor. If None, will create default.
"""
# Move to CPU for ONNX export (critical for M1)
self.cpu()
self.eval()
# Create dummy input if not provided
if dummy_input is None:
dummy_input = torch.randn(
1,
self.config.lookback,
self.config.input_dim
)
# Ensure dummy input is on CPU
dummy_input = dummy_input.cpu()
# Export to ONNX
torch.onnx.export(
self,
dummy_input,
path,
export_params=True,
opset_version=14,
do_constant_folding=True,
input_names=['input'],
output_names=['output'],
dynamic_axes={
'input': {0: 'batch_size'},
'output': {0: 'batch_size'}
}
)
print(f"Model exported to ONNX: {path}")
def count_parameters(self) -> int:
"""Count trainable parameters.
Returns:
Number of trainable parameters
"""
return sum(p.numel() for p in self.parameters() if p.requires_grad)
if __name__ == "__main__":
# Test base class (with a dummy implementation)
class DummyModel(BasePredictor):
def __init__(self, config: ModelConfig):
super().__init__(config)
self.fc = nn.Linear(config.input_dim * config.lookback, 1)
if config.task == 'classification':
self.activation = nn.Sigmoid()
else:
self.activation = nn.Identity()
def forward(self, x):
batch_size = x.size(0)
x = x.view(batch_size, -1)
x = self.fc(x)
return self.activation(x)
# Test
config = ModelConfig(input_dim=10, lookback=168, task='classification')
model = DummyModel(config)
print(f"Parameters: {model.count_parameters()}")
# Test forward pass
x = torch.randn(32, 168, 10)
output = model(x)
print(f"Output shape: {output.shape}")