Cómo integrar correctamente un modelo ONNX en un EA de MetaTrader 5
Diagnóstico del error 5805, dimensiones de tensores y reproducción exacta de variables
Resumen
Integrar un modelo ONNX en MetaTrader 5 no consiste solamente en cargar un archivo y llamar a OnnxRun. El EA debe respetar un contrato preciso: cantidad y orden de entradas y salidas, dimensiones de cada tensor, tipos de datos y el mismo procedimiento de preparación de variables utilizado durante el entrenamiento.
Esta publicación técnica presenta un caso real en el que el Strategy Tester detenía el EA durante OnInit con el mensaje:
Error configurando forma de salida ONNX. Código: 5805 tester stopped because OnInit returns non-zero code 1
El origen no era el Strategy Tester ni la ubicación del archivo. El modelo tenía una entrada de 60 variables y dos salidas diferentes: una etiqueta y un vector de dos probabilidades. Configurar una única salida con forma [1, 1] no describía ese modelo.
Además de corregir las dimensiones, veremos cómo impedir un fallo más peligroso: obtener inferencias aparentemente válidas con variables construidas de forma distinta a las usadas en Python.

Flujo reproducible de integración ONNX entre Python y MetaTrader 5
1. El contrato entre el modelo y el EA
Un archivo ONNX define una interfaz. Antes de escribir la lógica de trading hay que conocer:
- Cuántas entradas tiene el modelo.
- El nombre, tipo y dimensiones de cada entrada.
- Cuántas salidas tiene.
- El nombre, tipo y dimensiones de cada salida.
- El orden exacto de las variables.
- La normalización aplicada durante el entrenamiento.
- El significado de cada posición de la salida.
En el caso analizado, la inspección del modelo produjo esta interfaz:
| Índice | Nombre | Tipo ONNX | Dimensión exportada | Dimensión usada en MT5 |
|---|---|---|---|---|
| Entrada 0 | input | float32 | [batch, 60] | [1, 60] |
| Salida 0 | label | int64 | [batch] | [1] |
| Salida 1 | probabilities | float32 | [batch, 2] | [1, 2] |
La primera dimensión era dinámica. Para inferir una observación por vez, el EA debía resolver batch como 1.
Esta tabla es el contrato de inferencia. Si cambia el modelo, el contrato debe revisarse de nuevo.

Contrato de entradas y salidas del modelo ONNX
2. ¿Qué significa realmente el error 5805?
En MQL5, el código 5805 corresponde a ERR_ONNX_INVALID_PARAMETER: valor de parámetro ONNX incorrecto.
Puede aparecer, entre otros casos, cuando:
- Se intenta configurar un índice de entrada o salida inexistente.
- La forma declarada no es compatible con el tensor del modelo.
- Se supone que existe una sola salida cuando el modelo exportó dos.
- Se confunde una etiqueta [1] con una matriz de probabilidades [1, 2].
- Se copia la forma usada por otro modelo sin inspeccionar el archivo actual.
La dimensión [1, 1] no es incorrecta por naturaleza. Es apropiada para determinados modelos de regresión o modelos con una única salida escalar. El error consiste en asumir que sirve para cualquier modelo.

Comparación entre la forma de salida incorrecta y la interfaz real
En este caso concreto:
const long output_shape[] = {1, 1};
OnnxSetOutputShape(handle, 0, output_shape); No representaba la interfaz real. El modelo exportado por el clasificador tenía dos salidas:
- Label: clase prevista, tipo int64, forma [batch].
- Probabilities: probabilidades de las dos clases, tipo float32, forma [batch, 2].
La configuración correcta fue:
const long label_shape[] = {1};
const long probability_shape[] = {1, 2};
if(!OnnxSetOutputShape(handle, 0, label_shape))
{
Print("No se pudo configurar label. Error: ", GetLastError());
return(INIT_FAILED);
}
if(!OnnxSetOutputShape(handle, 1, probability_shape))
{
Print("No se pudieron configurar probabilities. Error: ", GetLastError());
return(INIT_FAILED);
} Observe que no solo cambia la forma: también se configura explícitamente la salida de índice 1.
3. Inspeccionar el modelo antes de programar
3.1. Inspección rápida con Python
La forma más sencilla de comprobar la interfaz es usar ONNX Runtime:
import onnxruntime as ort
model_path = "ai_signal_h1_target100.onnx"
session = ort.InferenceSession(
model_path,
providers=["CPUExecutionProvider"]
)
print("ENTRADAS")
for i, item in enumerate(session.get_inputs()):
print(i, item.name, item.type, item.shape)
print("SALIDAS")
for i, item in enumerate(session.get_outputs()):
print(i, item.name, item.type, item.shape) Para el modelo de este caso, la salida equivalente es:
ENTRADAS 0 input tensor(float) [None, 60] SALIDAS 0 label tensor(int64) [None] 1 probabilities tensor(float) [None, 2]
None indica una dimensión dinámica. No debe copiarse como cero. Si el EA procesa una sola observación, se resuelve como 1.
3.2. Inspección desde MQL5
MQL5 también permite consultar el modelo mediante:
- OnnxGetInputCount y OnnxGetOutputCount.
- OnnxGetInputName y OnnxGetOutputName.
- OnnxGetInputTypeInfo y OnnxGetOutputTypeInfo.
Como comprobación mínima:
long inputs = OnnxGetInputCount(handle);
long outputs = OnnxGetOutputCount(handle);
Print("Entradas ONNX: ", inputs);
Print("Salidas ONNX: ", outputs);
for(long i = 0; i < inputs; i++)
Print("Input ", i, ": ", OnnxGetInputName(handle, i));
for(long i = 0; i < outputs; i++)
Print("Output ", i, ": ", OnnxGetOutputName(handle, i)); Este control evita continuar si el archivo cargado no es el modelo para el cual fue escrito el EA.
4. Cargar el modelo como recurso
Para un EA autónomo resulta conveniente compilar el modelo como recurso:
#resource "ai_signal_h1_target100.onnx" as const uchar ModelData[]; long g_onnx_handle = INVALID_HANDLE;
Luego, durante OnInit:
g_onnx_handle = OnnxCreateFromBuffer(ModelData, ONNX_DEFAULT);
if(g_onnx_handle == INVALID_HANDLE)
{
Print("OnnxCreateFromBuffer falló. Error: ", GetLastError());
return(INIT_FAILED);
} Ventajas de este enfoque:
- No depende de copiar manualmente el.onnx a MQL5\Files.
- Funciona mejor en el entorno aislado del Strategy Tester.
- El modelo viaja dentro del ejecutable compilado.
- Reduce errores de rutas y nombres de archivo.
Hay una consecuencia importante: si se reemplaza el modelo, hay que recompilar el EA. El ejecutable anterior sigue conteniendo el recurso anterior.
5. Configurar todas las dimensiones
El modelo recibe una ventana de diez velas y seis variables por vela:
10 velas × 6 variables = 60 entradas
La inicialización completa queda así:
int ConfigureOnnxModel(const long handle)
{
if(OnnxGetInputCount(handle) != 1)
{
Print("Interfaz inesperada: se esperaba 1 entrada.");
return(INIT_FAILED);
}
if(OnnxGetOutputCount(handle) != 2)
{
Print("Interfaz inesperada: se esperaban 2 salidas.");
return(INIT_FAILED);
}
const long input_shape[] = {1, 60};
const long label_shape[] = {1};
const long probability_shape[] = {1, 2};
ResetLastError();
if(!OnnxSetInputShape(handle, 0, input_shape))
{
Print("OnnxSetInputShape falló. Error: ", GetLastError());
return(INIT_FAILED);
}
ResetLastError();
if(!OnnxSetOutputShape(handle, 0, label_shape))
{
Print("Salida label inválida. Error: ", GetLastError());
return(INIT_FAILED);
}
ResetLastError();
if(!OnnxSetOutputShape(handle, 1, probability_shape))
{
Print("Salida probabilities inválida. Error: ", GetLastError());
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
} Es preferible validar los conteos antes de definir las formas. Así, un modelo sustituido accidentalmente genera un mensaje comprensible en lugar de un fallo ambiguo.
6. El problema silencioso: reproducir exactamente las variables
Corregir el error 5805 permite inicializar el modelo, pero no garantiza predicciones correctas.
Un modelo puede aceptar 60 números y devolver probabilidades sin detectar que el EA cambió:
- El orden de las variables.
- La dirección temporal de la ventana.
- La fórmula de una mecha.
- La escala de normalización.
- La fuente de precios.
- El tratamiento de divisiones por cero.
- El uso de la vela actual en lugar de velas cerradas.
Ese tipo de error no produce un código 5805. Produce inferencias técnicamente válidas, pero semánticamente incorrectas.
6.1. Contrato de variables del ejemplo
Para cada una de las diez velas cerradas se construyeron seis variables, siempre en este orden:
- Proporción del cuerpo.
- Proporción de la mecha superior.
- Proporción de la mecha inferior.
- Dirección de la vela.
- Retorno logarítmico.
- Rango relativo.
Las fórmulas fueron:
rango = máximo - mínimo cuerpo = |cierre - apertura| mecha_sup = máximo - max(apertura, cierre) mecha_inf = min(apertura, cierre) - mínimo body_pct = cuerpo / rango upper_pct = mecha_sup / rango lower_pct = mecha_inf / rango direction = +1 si cierre >= apertura; -1 en caso contrario log_return = ln(cierre / cierre_anterior) range_pct = rango / cierre
La implementación MQL5 equivalente es:
double range = high_price - low_price; if(range <= 0.0) range = 0.00001; double body = MathAbs(close_price - open_price); double upper_wick = high_price - MathMax(open_price, close_price); double lower_wick = MathMin(open_price, close_price) - low_price; input_data[index++] = (float)(body / range); input_data[index++] = (float)(upper_wick / range); input_data[index++] = (float)(lower_wick / range); input_data[index++] = (close_price >= open_price) ? 1.0f: -1.0f; input_data[index++] = (float)MathLog(close_price / previous_close); input_data[index++] = (float)(range / close_price);
6.2. Orden temporal
La ventana se carga desde la vela más antigua hacia la más reciente:

Ventana de diez velas cerradas sin utilizar la vela abierta
for(int shift = 10; shift >= 1; shift--)
{
// Construir las seis variables de cada vela cerrada.
} Usar shift=1 hasta shift=10 invertiría la secuencia. Las dimensiones seguirían siendo [1, 60], pero el modelo recibiría una estructura temporal diferente.
6.3. Evitar información futura
La vela 0 está abierta y puede cambiar hasta su cierre. Si el entrenamiento se realizó con velas cerradas, la inferencia debe usar también velas cerradas. Por eso la ventana termina en shift=1.
Esta correspondencia es indispensable para que el comportamiento histórico pueda reproducirse en tiempo real.
7. Preparar los búferes de salida
Los búferes deben coincidir tanto en tamaño como en tipo:
float input_data[]; ArrayResize(input_data, 60); long label_output[]; ArrayResize(label_output, 1); float probability_output[]; ArrayResize(probability_output, 2);
El modelo devuelve:
probability_output[0] = probabilidad de la clase 0 probability_output[1] = probabilidad de la clase 1
La inferencia se ejecuta respetando el orden de los parámetros ONNX: entrada 0, salida 0 y salida 1.
ResetLastError();
if(!OnnxRun(g_onnx_handle,
ONNX_DEFAULT,
input_data,
label_output,
probability_output))
{
Print("OnnxRun falló. Error: ", GetLastError());
return;
}
float probability_class_1 = probability_output[1]; MQL5 puede convertir automáticamente determinados tipos cuando no se usa ONNX_NO_CONVERSION. Durante el diagnóstico es mejor hacer coincidir los tipos del EA con los del modelo: float para tensor(float) y long para tensor(int64).
Cuando esa equivalencia está comprobada, ONNX_NO_CONVERSION puede utilizarse para evitar conversiones adicionales. No debe activarse antes de verificar tipos y tamaños.
8. Validación cruzada entre Python y MetaTrader 5
Una integración no debería considerarse terminada porque OnnxRun devuelve true. Hay que comparar el resultado de ambos entornos usando exactamente la misma muestra.

Validación cruzada de variables y probabilidades entre Python y MetaTrader 5
8.1. Guardar una muestra de referencia en Python
import numpy as np
import onnxruntime as ort
session = ort.InferenceSession(
"ai_signal_h1_target100.onnx",
providers=["CPUExecutionProvider"]
)
# X_reference debe contener las mismas 60 variables y el mismo orden.
X_reference = np.asarray(
X_reference,
dtype=np.float32
).reshape(1, 60)
label, probabilities = session.run(
None,
{"input": X_reference}
)
print("label:", label)
print("probabilities:", probabilities) Además de las predicciones, conviene exportar las 60 variables de la muestra.
8.2. Registrar la misma muestra en MQL5
for(int i = 0; i < ArraySize(input_data); i++)
PrintFormat("X[%02d] = %.9f", i, input_data[i]);
PrintFormat("label=%d p0=%.9f p1=%.9f",
label_output[0],
probability_output[0],
probability_output[1]); La comprobación debe hacerse en dos etapas:
- Comparar las 60 variables.
- Comparar las dos probabilidades.
Si las variables difieren, todavía no tiene sentido investigar la salida del modelo.
9. Prueba correcta en el Strategy Tester
Un procedimiento práctico es:
- Compilar el EA y confirmar 0 errors, 0 warnings.
- Ejecutar un intervalo corto con registros ONNX detallados.
- Confirmar que OnInit crea la sesión y configura las tres formas.
- Verificar que no aparezcan valores NaN o infinitos.
- Registrar una muestra y compararla con Python.
- Ejecutar una prueba fuera del periodo de entrenamiento.
- Reiniciar MT5 y repetir una prueba para confirmar que el recurso está integrado.
- Desactivar los registros extensos antes de una simulación larga.
Si el EA toma decisiones únicamente al abrir una nueva vela H1 y usa datos cerrados, el modo de ticks puede no cambiar las señales de inferencia. Sin embargo, sí puede modificar la ejecución de órdenes, el diferencial, los stops y el resultado económico. La elección del modelado debe responder a lo que se intenta validar.
10. Tabla de diagnóstico rápido
| Código | Significado | Primera comprobación |
|---|---|---|
| 5803 | Fallo al ejecutar ONNX Runtime | Revisar operadores compatibles y registros detallados |
| 5804 | Cantidad incorrecta de parámetros en OnnxRun | Contar todas las entradas y salidas |
| 5805 | Valor de parámetro incorrecto | Revisar índices y formas declaradas |
| 5806 | Tipo de parámetro incorrecto | Comparar float, double, long, matrices y vectores |
| 5807 | Tamaño de parámetro incorrecto | Comparar cantidad de elementos de cada buffer |
| 5808 | Dimensión no definida o inválida | Configurar todas las formas dinámicas |
Una práctica útil consiste en separar cada comprobación y registrar el error inmediatamente:
ResetLastError();
bool ok = OnnxSetOutputShape(handle, 1, probability_shape);
int error = GetLastError();
if(!ok)
Print("OnnxSetOutputShape(output=1) falló. Error: ", error); Así se evita que una llamada posterior reemplace el código que se intentaba diagnosticar.
11. Lista de comprobación antes de operar
Modelo:
- El archivo ONNX inspeccionado es exactamente el incorporado al EA.
- Se conocen los nombres, tipos y formas de todas las entradas y salidas.
- Las dimensiones dinámicas se resolvieron explícitamente.
- Se recompiló el EA después de reemplazar el recurso.
Variables:
- La cantidad de variables coincide.
- El orden coincide con el entrenamiento.
- Las fórmulas y constantes son idénticas.
- La escala y normalización coinciden.
- El orden temporal coincide.
- Solo se usan datos disponibles en el momento de la señal.
Inferencia:
- Los búferes tienen el tipo correcto.
- Los búferes tienen el número correcto de elementos.
- OnnxRun recibe entradas y salidas en el orden del modelo.
- Una muestra produce resultados equivalentes en Python y MT5.
Validación:
- Se probó un periodo no utilizado en el entrenamiento.
- Se incluyeron diferencial, comisión y restricciones reales del símbolo.
- Los registros extensos pueden desactivarse.
- El EA libera la sesión mediante OnnxRelease.
Conclusión
El error 5805 fue útil porque detuvo una integración incompatible. El problema más peligroso habría sido otro: que el modelo aceptara 60 valores construidos de manera distinta y devolviera probabilidades plausibles sin advertirlo.
La integración correcta de ONNX en MetaTrader 5 exige mantener un contrato explícito entre entrenamiento e inferencia:
modelo correcto + formas correctas + tipos correctos + variables idénticas + orden temporal idéntico + validación cruzada = inferencia reproducible
Solo después de comprobar ese contrato tiene sentido evaluar umbrales, reglas de entrada, gestión del riesgo o rentabilidad. ONNX transporta el modelo; no corrige automáticamente las diferencias entre el entorno de investigación y el EA.
Referencias oficiales
- Modelos ONNX en MQL5: https://www.mql5.com/es/docs/onnx.
- OnnxCreateFromBuffer: https://www.mql5.com/en/docs/onnx/onnxcreatefrombuffer.
- OnnxSetInputShape: https://www.mql5.com/en/docs/onnx/onnxsetinputshape.
- OnnxSetOutputShape: https://www.mql5.com/en/docs/onnx/onnxsetoutputshape.
- OnnxRun: https://www.mql5.com/en/docs/onnx/onnxrun.
- Estructuras de datos ONNX: https://www.mql5.com/en/docs/onnx/onnx_structures.
- Códigos de error de MQL5: https://www.mql5.com/es/docs/constants/errorswarnings/errorcodes.


