Keras and TensorFlow don't export to ONNX directly — you need tf2onnx, a separate converter. It's reliable for most architectures, including the CNN-LSTM pattern that's become popular for retail price-prediction. This article walks through the exact stack that works in 2026, including the TensorFlow version pitfall the MetaQuotes documentation specifically calls out.

The stack that works (TensorFlow 2.10.0)

This matters because newer TensorFlow versions have known installation problems on Windows and intermittent tf2onnx compatibility issues. The combination that's been reliable for MT5-bound exports throughout 2025-2026:

requirements.txt
tensorflow==2.10.0 tf2onnx==1.16.1 onnx==1.16.0 numpy<2.0

Use a fresh virtual environment with these exact pins. tensorflow>2.10 often fails to install on Windows (the GPU build was dropped from PyPI starting with 2.11). The official MetaQuotes documentation specifically recommends 2.10.0 for this reason.

A working CNN-LSTM in Keras

The canonical architecture for price prediction: a 1D convolution over the time dimension to extract local patterns, followed by an LSTM over the convolution outputs:

model.py
import tensorflow as tf from tensorflow.keras import layers, Sequential model = Sequential([ layers.Input(shape=(120, 1)), # 120 bars, 1 feature layers.Conv1D(32, kernel_size=5, activation="relu"), layers.MaxPooling1D(pool_size=2), layers.Conv1D(64, kernel_size=3, activation="relu"), layers.LSTM(64), layers.Dense(32, activation="relu"), layers.Dense(1), ]) model.compile(optimizer="adam", loss="mse") model.fit(X_train, y_train, epochs=20, batch_size=32) model.save("saved_model/") # SavedModel format

Converting to ONNX with tf2onnx

Once the model is saved, tf2onnx converts it from the command line:

command prompt
> python -m tf2onnx.convert \ --saved-model saved_model/ \ --output eurusd_cnn_lstm.onnx \ --opset 17

The --opset 17 flag is critical — same reason as PyTorch. tf2onnx defaults to a higher opset which MT5 may not load.

If you need it in Python instead of CLI

convert.py
import tf2onnx import tensorflow as tf model = tf.keras.models.load_model("saved_model/") spec = (tf.TensorSpec((None, 120, 1), tf.float32, name="input"),) tf2onnx.convert.from_keras( model, input_signature=spec, opset=17, output_path="eurusd_cnn_lstm.onnx" )

Normalization: the part everyone forgets

You almost certainly normalized your training data (z-score, min-max, etc.). The model expects normalized inputs at inference time too — including inside the MQL5 EA. The normalization parameters used during training must be saved and re-applied at inference, identically.

The standard pattern: serialize the normalization stats to a separate file alongside the ONNX:

save normalization params
import json stats = { "mean": float(X_train.mean()), "std": float(X_train.std()), } with open("normalization.json", "w") as f: json.dump(stats, f)

Copy normalization.json next to the .onnx in MQL5\Files\. Read it in OnInit and apply (x - mean) / std to your features before OnnxRun. We document the full MQL5 side in the normalization article.

Validation pass

Same pattern as PyTorch — run the same input through Keras and through ONNX, compare outputs:

validate.py
import numpy as np import onnxruntime as ort x = np.random.randn(1, 120, 1).astype(np.float32) y_keras = model.predict(x) sess = ort.InferenceSession("eurusd_cnn_lstm.onnx") y_onnx = sess.run(None, {"input": x})[0] assert np.allclose(y_keras, y_onnx, atol=1e-4) print("OK")

If outputs match, ship to MT5. If not, check that you saved with tf.keras.models.save_model(...) in SavedModel format (not HDF5), and that the opset is 17.