TensorFlow doesn't write .onnx files directly. The bridge is tf2onnx, a Microsoft-maintained converter that reads TensorFlow's SavedModel format and emits ONNX. This article covers the general TF-to-ONNX workflow for any architecture — dense, conv, sequence, mixed.

The version-pinned install

Use a fresh virtual environment with these versions. Other combinations work but break sporadically:

install.sh
python -m venv .venv .venv\Scripts\activate # Windows; or source .venv/bin/activate pip install tensorflow==2.10.0 pip install tf2onnx==1.16.1 pip install onnx==1.16.0 pip install "numpy<2.0" # numpy 2.x breaks tf2onnx 1.16

Why TF 2.10? It's the last version with a native Windows GPU build on PyPI — later versions require WSL2 for GPU acceleration. MetaQuotes' own ONNX tutorials use 2.10 for exactly this reason.

Save as SavedModel (not HDF5)

tf2onnx reads SavedModel format reliably. HDF5 (.h5) sometimes loses subclass model metadata. Always save SavedModel:

save.py
# Right way: model.save("saved_model/") # trailing slash = SavedModel # or explicitly: tf.saved_model.save(model, "saved_model/") # Avoid: model.save("model.h5") # HDF5 can lose info

Run tf2onnx

From the command line:

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

The --opset 17 targets the ONNX opset version MT5 reliably supports in Build 5572. See the opset guide for the full reasoning.

Input signature: when to provide one

tf2onnx auto-detects the input signature from the SavedModel. Sometimes the detection picks up unwanted dynamic dimensions, or misses a dtype. Override explicitly:

explicit input signature
import tf2onnx import tensorflow as tf model = tf.keras.models.load_model("saved_model/") # Batch dynamic, sequence=120, features=4, dtype float32 spec = (tf.TensorSpec((None, 120, 4), tf.float32, name="input"),) tf2onnx.convert.from_keras( model, input_signature=spec, opset=17, output_path="model.onnx", )

This pins the shape exactly the way you want for MT5. None means dynamic (the batch dim); concrete numbers stay fixed.

Common conversion errors

ModuleNotFoundError: No module named 'tf2onnx'

You're in the wrong virtualenv. Activate the one with tf2onnx installed.

Unsupported op type: TFLite_Detection_PostProcess

You used a TFLite-specific op. Rewrite that part in regular TF or do post-processing on the MQL5 side.

Conversion succeeds but ONNX output diverges from TF output

Almost always batch normalization in training mode. Make sure model.trainable = False or that you're using tf.keras.Model.save after training (which fixes the mode).

"Failed to load SavedModel" on the convert step

The SavedModel was created with a different TF version. Re-save with TF 2.10 in the same environment used for conversion.