Обучение модели на основе LSTM для прогноза цены
LSTM (Long Short-Term Memory) — тип рекуррентной нейронной сети, специально разработанный для работы с последовательностями и долгосрочными зависимостями. В отличие от обычных RNN, LSTM имеет механизм gate'ов, который позволяет «запоминать» важную информацию на длительные периоды и «забывать» нерелевантную.
Архитектура LSTM для финансовых данных
import torch
import torch.nn as nn
class CryptoLSTM(nn.Module):
def __init__(self, input_size, hidden_size=128, num_layers=2,
dropout=0.2, output_size=1):
super().__init__()
self.lstm = nn.LSTM(
input_size=input_size,
hidden_size=hidden_size,
num_layers=num_layers,
dropout=dropout,
batch_first=True,
bidirectional=False # для прогноза: только прямое направление
)
self.attention = nn.MultiheadAttention(
embed_dim=hidden_size,
num_heads=8,
dropout=dropout,
batch_first=True
)
self.fc = nn.Sequential(
nn.Linear(hidden_size, 64),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(64, output_size)
)
def forward(self, x):
# x: (batch, seq_len, input_size)
lstm_out, (hidden, cell) = self.lstm(x)
# Attention над LSTM выходами
attn_out, _ = self.attention(lstm_out, lstm_out, lstm_out)
# Берём последний timestep
out = self.fc(attn_out[:, -1, :])
return out
Подготовка данных
Sequence creation: LSTM принимает последовательности. Для каждого момента t создаём input из последних N свечей:
def create_sequences(features, targets, seq_length=60):
X, y = [], []
for i in range(seq_length, len(features)):
X.append(features[i-seq_length:i])
y.append(targets[i])
return np.array(X), np.array(y)
Нормализация: обязательна для LSTM. MinMaxScaler или StandardScaler по каждому feature независимо. Важно: scaler обучается только на train данных.
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# Обучаем только на train данных!
train_features_scaled = scaler.fit_transform(train_features)
val_features_scaled = scaler.transform(val_features) # только transform
Sequence length: обычно 30–120 свечей. Для 1h таймфрейма: 60 свечей = 60 часов истории.
Training pipeline
from torch.utils.data import DataLoader, TensorDataset
def train_model(model, X_train, y_train, X_val, y_val,
learning_rate=0.001, n_epochs=100, batch_size=64):
# DataLoaders
train_dataset = TensorDataset(
torch.FloatTensor(X_train),
torch.FloatTensor(y_train)
)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=False)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate,
weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, patience=10, factor=0.5
)
criterion = nn.MSELoss()
best_val_loss = float('inf')
patience_counter = 0
for epoch in range(n_epochs):
# Train
model.train()
train_loss = 0
for X_batch, y_batch in train_loader:
optimizer.zero_grad()
pred = model(X_batch)
loss = criterion(pred.squeeze(), y_batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # gradient clipping
optimizer.step()
train_loss += loss.item()
# Validation
model.eval()
with torch.no_grad():
val_pred = model(torch.FloatTensor(X_val)).squeeze()
val_loss = criterion(val_pred, torch.FloatTensor(y_val)).item()
scheduler.step(val_loss)
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_model.pth')
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= 20:
print(f"Early stopping at epoch {epoch}")
break
model.load_state_dict(torch.load('best_model.pth'))
return model
Многошаговое прогнозирование
Direct approach: отдельная модель для каждого горизонта (t+1, t+4, t+24).
Recursive approach: модель предсказывает t+1, затем использует предсказание как input для t+2 и т.д. Ошибки накапливаются.
Seq2Seq с Encoder-Decoder: LSTM encoder обрабатывает историю, LSTM decoder генерирует последовательность будущих значений.
class Seq2SeqLSTM(nn.Module):
def __init__(self, input_size, hidden_size, output_steps):
super().__init__()
self.encoder = nn.LSTM(input_size, hidden_size, batch_first=True)
self.decoder = nn.LSTM(hidden_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, 1)
self.output_steps = output_steps
def forward(self, x):
_, (h, c) = self.encoder(x)
# Decoder получает последний hidden state как начальное состояние
decoder_input = x[:, -1:, :] # последняя входная свеча
outputs = []
for _ in range(self.output_steps):
out, (h, c) = self.decoder(decoder_input, (h, c))
pred = self.fc(out)
outputs.append(pred)
# Используем предсказание как следующий input
decoder_input = out
return torch.cat(outputs, dim=1)
Метрики качества
Для регрессии: RMSE, MAE, MAPE, R²
Directional accuracy: главное для торговой модели — правильно ли предсказывается направление, а не точная цена.
def directional_accuracy(y_true, y_pred):
true_direction = np.sign(y_true)
pred_direction = np.sign(y_pred)
return (true_direction == pred_direction).mean()
Profit simulation: симулируем торговлю на тестовых данных: лонг при predict > threshold, шорт при predict < -threshold.
Hyperparameter Tuning
Оптимальные гиперпараметры подбираются через Optuna или Ray Tune:
- hidden_size: 64, 128, 256
- num_layers: 1, 2, 3
- seq_length: 30, 60, 120
- dropout: 0.1–0.4
- learning_rate: 1e-4 – 1e-2
Walk-forward hyperparameter tuning: гиперпараметры оптимизируются на validation окне, модель переобучается при выходе новых данных.
Разрабатываем и обучаем LSTM модели для прогноза цены выбранных криптоактивов: feature engineering (техника + on-chain), walk-forward training pipeline, attention механизм, Seq2Seq для многошагового прогноза, production API и мониторинг деградации.







