You trained a PyTorch model in Python. Now it needs to live inside an MQL5 Expert Advisor. The bridge is ONNX — an interchange format that PyTorch exports natively and MetaTrader 5 loads natively. The conversion is one function call. Getting it to actually work in MT5 is where it gets interesting.
This article walks through the 2026 export workflow: the right opset, the right way to handle dynamic axes, the validation step nobody mentions, and the LSTM gotcha that costs people hours.
What's in this article
The minimal working export
A complete, working PyTorch-to-ONNX pipeline for an MT5-bound model:
This produces eurusd.onnx, ready to drop into MQL5\Files\ and load with OnnxCreate. Three things in this snippet are important and easy to miss:
model.eval()— turns off dropout and batch normalization's training-mode behavior. Forget this and your exported model produces different outputs from training-time inference, ruining backtests.opset_version=17— explicit version. Newer PyTorch defaults to opset 18 or higher, which MT5 may not support yet. Pin to 17.- Dynamic only on dimension 0 — only the batch dimension. Anything else dynamic (seq_len, features) opens up the LSTM trap (see below).
The new exporter vs the legacy one
PyTorch ships two ONNX exporters in 2026:
- Legacy (TorchScript-based):
torch.onnx.export(...). Battle-tested. Has known limitations with newer ops. Works for almost every retail-scale model. - New (TorchDynamo-based):
torch.onnx.export(..., dynamo=True). Better support for modern model patterns, larger op coverage. Sometimes more brittle on LSTM-heavy models.
Start with the legacy exporter. Only switch to dynamo=True if the legacy one fails on your specific model.
Dynamic axes — what to keep dynamic
When you declare dynamic_axes, you're saying "this dimension's size will be set at runtime, not export time." On the MQL5 side, you'll set it with OnnxSetInputShape.
Rule of thumb: only the batch dimension should be dynamic. Everything else — sequence length, feature count, hidden size — fix at export time. This is especially important for LSTMs (see below).
Validate before shipping to MT5
Don't drop a freshly-exported model into MQL5 and hope. Validate in Python first by running inference through onnxruntime and comparing to the original PyTorch output:
If outputs match within a small tolerance (1e-5 is generous), the export is faithful. If they diverge, something went wrong — usually model.eval() wasn't called, or there's an op that didn't translate correctly. Don't proceed to MT5 until this passes.
Inspect with Netron
Open netron.app and drag your .onnx file in. You see the full computation graph. What to check:
- Input shape and name. Click the input node — should show
inputwith shape[batch, 120, 1](or whatever your seq/features are). - Output shape and name. Same check on the output.
- Op types. Should be standard ops (LSTM, Linear, Conv, etc.) — not anything experimental or vendor-specific.
The shape and name you see in Netron are what you pass to OnnxSetInputShape in MQL5. They must match exactly.
LSTM — the seq_len trap
If your model contains an LSTM and you declare seq_len as a dynamic axis, the export sometimes silently produces a broken model (different output from PyTorch). The full diagnosis and fix is in the PyTorch LSTM export guide.
Short version: keep seq_len static at export time. If you need to run inference on different sequence lengths, re-export the model for each one.
Training on GPU is dramatically faster.
If you're exporting models, you're training them. Cloud GPUs are hourly:
Affiliate links — see full comparison.