The CNN-LSTM is the textbook deep-learning architecture for time-series price prediction: a 1D convolution extracts local patterns over a few bars, then an LSTM aggregates them over the longer sequence. It's the architecture you'll find in academic papers, retail tutorials, and a surprising number of production MT5 EAs. This article shows the end-to-end pipeline: model, training, export, MQL5 integration.

Caveat upfront: price forecasting is genuinely hard. The CNN-LSTM doesn't outperform a coin flip out of the box. The architecture is fine; the data, features, and labeling are what matter. We'll cover the parts that are usually wrong.

The architecture

model.py
import torch import torch.nn as nn class CNNLSTM(nn.Module): def __init__(self, in_features=4, conv_out=32, lstm_hidden=64): super().__init__() self.conv1 = nn.Conv1d(in_features, conv_out, kernel_size=5, padding=2) self.conv2 = nn.Conv1d(conv_out, conv_out, kernel_size=3, padding=1) self.lstm = nn.LSTM(conv_out, lstm_hidden, batch_first=True) self.fc = nn.Linear(lstm_hidden, 1) def forward(self, x): # x: (batch, seq, features) — permute for Conv1d x = x.permute(0, 2, 1) x = torch.relu(self.conv1(x)) x = torch.relu(self.conv2(x)) x = x.permute(0, 2, 1) out, _ = self.lstm(x) return self.fc(out[:, -1, :])

Labels: what to actually predict

"Predict the next close" is the wrong objective — almost-perfect regression on price is trivial (next close looks like current close) and useless for trading. Two better choices:

The classifier approach is often more practical. The architecture above ends with a single output for return prediction; swap the last Linear to nn.Linear(lstm_hidden, 2) for binary classification.

Inputs and normalization

Reasonable feature vector for a 1-hour timeframe model:

That's 4 features × 120 bars. Normalize each feature by its rolling mean/std over a 500-bar window so the model sees stationary inputs across regimes (see normalization article).

Export the model

The crucial bit for CNN-LSTM: seq_len must be static at export time (the LSTM trap). See PyTorch LSTM to ONNX.

export.py
model.eval() dummy = torch.randn(1, 120, 4) # static seq, 4 features torch.onnx.export( model, dummy, "cnn_lstm.onnx", opset_version=17, input_names=["input"], output_names=["output"], dynamic_axes={"input": {0: "batch"}, "output": {0: "batch"}}, do_constant_folding=True, )

Use in MQL5

cnn_lstm_ea.mq5 (skeleton)
#resource "models\cnn_lstm.onnx" as uchar Model[] #define SEQ_LEN 120 #define N_FEATURES 4 long hModel; int OnInit() { hModel = OnnxCreateFromBuffer(Model, ONNX_DEFAULT); const long in_shape[] = {1, SEQ_LEN, N_FEATURES}; const long out_shape[] = {1, 1}; OnnxSetInputShape(hModel, 0, in_shape); OnnxSetOutputShape(hModel, 0, out_shape); return(INIT_SUCCEEDED); } void OnTick() { static datetime last_bar; datetime now = iTime(NULL, PERIOD_CURRENT, 0); if(now == last_bar) return; last_bar = now; matrixf input(1, SEQ_LEN * N_FEATURES); // flattened vectorf out(1); BuildAndNormalizeFeatures(input); // IMPORTANT: same as training OnnxRun(hModel, ONNX_NO_CONVERSION, input, out); double predicted_return = out[0]; if(predicted_return > 0.0010) PlaceBuy(); if(predicted_return < -0.0010) PlaceSell(); }

What to expect

Realistic outcomes from a CNN-LSTM trained without genius:

Pair the CNN-LSTM with a regime filter (see market-structure classifier) for substantially better risk-adjusted returns than either alone.