
Interacción entre MetaTrader 5 y MATLAB
Introducción
Mi primer artículo, Interacción entre MetaTrader 4 y el motor de MATLAB (máquina MATLAB virtual), fue seguido por la comunidad MQL. Algunos lectores (1Q2W3E4R5T) pudieron incluso trasladar este proyecto desde Borland a VS2008. Pero el tiempo pasa inexorablemente y (aunque triste pero cierto) MetaTrader 4 está desapareciendo y dando paso a su sucesor MetaTrader 5 con MQL5, que introdujo los punteros y la memoria dinámica.
Gracias a estas innovaciones tenemos la oportunidad de escribir una librería universal para la interacción con la máquina virtual del motor MATLAB y vincular directamente librerías generadas por MATLAB con MetaTrader 5. Este artículo trata sobre dicha funcionalidad. El artículo es una continuación del anterior y trata en mayor profundidad el problema de la interacción entre MetaTrader 5 y MATLAB.
Para que el alcance de este artículo sea más comprensible para los lectores menos expertos vamos a dividirlo en tres partes: teoría, referencia y práctica. La teoría tratará sobre los tipos de datos usados en MQL5 y MATLAB, así como sobre su conversión mutua. En la referencia aprenderemos las estructuras lingüísticas y la sintaxis de las funciones necesarias para crear una DLL. Y en la práctica vamos a analizar las "dificultades" de esta interacción.
Los lectores más experimentados pueden pasar por alto la teoría y la referencia y comenzar con la práctica. Los demás deben leer la teoría y la referencia y solo entonces pasar a la práctica. También merece la pena leer los libros citados en el apartado de "Bibliografía".
1. Teoría
1.1 Tipos de datos en MATLAB y MQL5
1.1.1 Tipos de datos simples
Vamos con ello.
En primer lugar, debemos familiarizarnos con el vocabulario de MQL5 y MATLAB. Tras una breve inspección de los tipos de variables concluimos que son casi idénticas:
MQL5 |
Tamaño en bytes |
Valor mínimo |
Valor máximo |
MATLAB |
---|---|---|---|---|
char |
1 |
-128 |
127 |
Matriz int8/char |
uchar |
1 |
0 |
255 |
Matriz int8/char |
bool |
1 |
0(falso) |
1(verdadero) |
Matriz lógica |
short |
2 |
-32768 |
32767 |
Matriz int16 |
ushort |
2 |
0 |
65535 |
Matriz int16 |
int |
4 |
-2147483648 |
2147483647 |
Matriz int32 |
uint |
4 |
0 |
4294967295 |
Matriz int32 |
long |
8 |
-9223372036854775808 |
9223372036854775807 | Matriz int64 |
ulong |
8 |
0 |
18446744073709551615 |
Matriz int64 |
float |
4 |
1.175494351e-38 |
3.402823466e+38 |
Matriz única |
doble |
8 |
2.225073858507201e-308 |
1.7976931348623158e+308 |
Matriz doble |
Tabla 1. Tipos de datos en MATLAB y MQL5
Hay una gran diferencia: las variables en MQL5 pueden ser simples o compuestas (complejas) mientras que en MATLAB todas las variables son multidimensionales (complejas), es decir, matrices. ¡Siempre debemos recordar esta diferencia!
1.1.2 Tipos de datos complejos
En MQL5 hay 4 tipos de datos complejos: matrices, strings, estructuras y clases. El tipo de dato complejo se compone de varios tipos de datos simples combinados en un bloque de memoria de cierta longitud. Al trabajar con estos datos siempre necesitaremos conocer el tamaño del bloque de memoria en bytes o el número de elementos (excepto las clases). Solo nos interesan las matrices y los strings ya que no tiene sentido enviar clases y estructuras de MQL5 a MATLAB.
Al pasar matrices de cualquier tipo necesitaremos conocer el tipo (dimensión) y número de elementos usados en la función ArraySize(). Debe prestarse una atención especial al indexado en MetaTrader 5 ya que es realizado habitualmente hacia atrás (es decir, el primer elemento contiene datos más recientes que el siguiente). Puede comprobar esto usando la función ArrayIsSeries(). Y MATLAB tiene el siguiente indexado: el primer elemento contiene datos más antiguos que el siguiente, por lo que debemos "invertir" nuestras matrices antes de enviarlas a MATLAB si el flag = AS_SERIES = TRUE. Basándonos en lo anterior, vamos a establecer lo siguiente:
- "Invertimos" las matrices "de forma invisible" para los programas MQL5, excepto las matrices del tipo char y las de dos dimensiones, que las dejamos sin cambios.
- Invertimos "de forma invisible" todas las matrices de MATLAB y asignamos al flag AS_SERIES el valor TRUE, excepto para las matrices de tipo char y las de dos dimensiones, que las dejamos sin cambios.
- En cada matriz de un programa MQL5 creada según la indexación "hacia atrás", el flag AS_SERIES debe ser TRUE, excepto para las matrices de tipo char y las de dos dimensiones, que las dejamos sin cambios.
Pero esta no es la única limitación al trabajar con matrices. Cuando trabajamos con matrices multidimensionales o simplemente matrices, para ser más correctos, especialmente en MATLAB, introducimos una restricción para matrices de no más de 2 dimensiones. Aquí el flag AS_SERIES no puede ser TRUE y por tanto tales matrices no se han "invertido".
No olvidemos que los strings en MQL5 no son matrices de los elementos de tipo char. Por tanto, cuando pasamos strings, aparece un pequeño problema: en MQL5 los strings se codifican usando Unicode y en MATLAB se usa la codificación ANSI. Por tanto, antes de pasar un string este debe convertirse en una matriz de caracteres ANSI usando la función StringToCharArray(). Y viceversa, cuando tenemos una matriz de caracteres en MATLAB, la convertimos usando la función CharArrayToString() (véase Tabla 2). Para evitar la confusión, establecemos que vamos a almacenar todos los strings de los programas MQL5 usando Unicode sin matrices del tipo char.
1.2 Comparación de los tipos de datos en MQL5 y MATLAB
Para reducir la cantidad de funciones y para simplificar el algoritmo de la librería, vamos a reducir la cantidad de tipos por medio de una conversión automática que no debe afectar a la integridad de los datos. La siguiente tabla ilustra la regla de la conversión del tipo de datos desde MQL5 a MATLAB:
MQL5 |
Equivalente a MatLab |
---|---|
char uchar |
Matriz char |
bool |
Matriz lógica |
short ushort int uint |
Matriz int32 |
long ulong |
Matriz int64* |
float doble |
Matriz doble |
string |
Matriz char, usando las funciones StringToCharArray() <=> CharArrayToString() |
* Con este tipo de conversión hay una pérdida de precisión. No la usaremos pero podemos usar dicha conversión en nuestros programas.
Tabla 2. Comparación de los tipos de datos en MQL5 y MATLAB
Ahora estamos familiarizados con los tipos de datos en MQL5 y MATLAB. Sabemos cuáles son las dificultades al pasar los datos y cómo superarlas correctamente. Todavía necesitamos conocer la API del motor de MATLAB y familiarizarnos con el compilador 4 de MATLAB.
2. Referencia de la API del motor de MATLAB, referencia del compilador 4 de MATLAB y referencia de la librería de entrada/salida de C++
Este apartado realiza una introducción a las funciones más importantes de la API del motor de MATLAB, las características del compilador 4 de MATLAB y una serie de útiles funciones de la librería de entrada/salida estándar de C++. Por tanto, comencemos.
2.1. API del motor de MATLAB y funciones MCR
El motor de MATLAB es una interfaz externa que permite a otros programas usar el escritorio de MATLAB. Proporciona un conjunto completamente funcional de todos los paquetes de MATLAB sin ningún tipo de restricciones.
Aunque no se menciona en la documentación sino en términos del programador del sistema, es una máquina virtual como PHP, MySQL, etc., que proporciona una forma relativamente rápida de intercambiar datos entre MetaTrader 4/5 y MATLAB.
Este método de conexión entre programas externos con el paquete MATLAB está recomendado por los programadores. La interfaz está compuesta por seis funciones:
int exitCode = engClose(Engine *pEng) — esta función cierra el escritorio, devuelve el número de usuarios restantes del escritorio de MATLAB, donde:
- Engine *pEng — puntero al descriptor del escritorio.
mxArray *mxVector = mxCreateDoubleMatrix(int m, int n, int ComplexFlag) — esta función crea una variable (matriz) del escritorio de MATLAB, devuelve un puntero a las variables (matriz), donde:
- mxArray *mxVector — puntero a la variable matriz.
- int m — número de filas.
- int n — número de columnas.
- ComplexFlag — un tipo de número complejo, para MetaTrader 4/5 mxREAL.
- mxArray *mxVector — puntero a la variable matriz.
- Engine *pEng — puntero al descriptor del escritorio.
- char *Name — nombre de la variable de tipo char en el escritorio de MATLAB.
- mxArray *mxVector — puntero a la variable matriz.
- mxArray *mxVector — puntero a la variable matriz.
- Engine *pEng — puntero al descriptor del escritorio.
- char *Name — nombre de la variable de tipo char en el escritorio de MATLAB.
- double *p — puntero a la matriz de tipo doble.
- mxArray *mxVector — puntero a la variable matriz.
- Engine *pEng — puntero al descriptor del escritorio.
- char *Command — comando para MATLAB, string de tipo char.
Probablemente habrá notado que la API del motor de MATLAB le permite crear la estructura de mxArray solo para el tipo doble. Pero esta restricción no afecta a sus posibilidades, sino al algoritmo de su librería.
MCR (instancia MCR) — es la librería especial del paquete MATLAB que permite que las aplicaciones/librerías públicas se ejecuten de forma autónoma y son generadas por el entorno MATLAB en cualquier ordenador. Observe que aunque tenga un paquete completo MATLAB aún necesita instalar la librería MCR ejecutando el archivo MCRInstaller.exe ubicado en la carpeta <MATLAB> \Toolbox\compiler\deploy\win32. Por tanto, antes de llamar a cualquier función de librería pública creada por el entorno MATLAB necesitamos llamar a la función de inicialización MCR:
bool = mclInitializeApplication(const char **option, int count) – devuelve TRUE si el inicio de MCR tuvo éxito, y FALSE en caso contrario, donde:
- const char **option — string de opciones, como en mcc - R; habitualmente es NULL
- int count — string con opciones de tamaño, habitualmente 0.
Al finalizar el trabajo con la librería pública debemos llamar:
bool = mclTerminateApplication(void) — devuelve TRUE si MCR se cerró con éxito.
2.2 Compilador 4 de MATLAB
El compilador de MATLAB nos permite crear las siguientes funciones M:
- Aplicaciones autónomas que se ejecutan incluso si no se ha instalado MATLAB.
- Librerías compartidas de C/C++ que pueden usarse sin tener MATLAB en el sistema del usuario.
El compilador soporta la mayoría de los comandos y paquetes de MATLAB pero no todos. La lista completa de restricciones se encuentra en el sitio web de MATLAB. Este método nos permite crear "grupos independientes del software" de MetaTrader 5 y MATLAB pero al contrario que el motor de MATLAB, requiere un programador bien preparado y un gran conocimiento sobre compilación.
El compilador de MATLAB requiere al menos uno de los siguientes compiladores de C/C++:
- Lcc C (normalmente viene con MATLAB). Es solo un compilador de C.
- Borland C++ versiones 5.3, 5.4, 5.5, 5.6.
- Microsoft Visual C/C++ versiones 6.0, 7.0, 7.1.
El compilador 4 de MATLAB, al contrario que sus predecesores, solo genera el código de la interfaz (wrapper), es decir, no transforma las funciones m a binario o código de C/C++ pero crea un archivo especial basado en el archivo de tecnología de componente (CTF) que incluye la integración de varios paquetes necesarios para poder trabajar con las funciones m. El compilador de MATLAB también encripta este archivo con una llave de 1024-bit única (no repetida).
Ahora vamos a considerar el algoritmo del trabajo del compilador 4 de MATLAB ya que ignorar esta materia nos llevaría a cometer muchos errores graves en el momento de la compilación:
- Análisis de dependencias - en esta etapa determina todas las funciones, archivos MEX y archivos P de los que dependen las funciones m.
- Crear el archivo - el archivo CTF se crea y es encriptado y comprimido.
- Generar el código de objeto del wrapper - en esta etapa se crean todos los códigos fuente necesarios para el componente:
- El código de la interfaz de C/C++ para las funciones m especificadas en la línea de comandos (NameFile_main.c).
- Archivo del componente (NameFile_component.dat) que contiene toda la información necesaria para ejecutar el código m (incluyendo las llaves y vías de encriptación almacenadas en el archivo CTF).
- Conversión de C/C++. En esta etapa se compilan los archivos del código fuente de C/C++.
- Enlazando. La etapa final de la elaboración del proyecto.
Ahora, cuando estamos familiarizados con el comportamiento del algoritmo del compilador de MATLAB, debemos aprender más acerca de las llaves para tener un plan de acción detallado al usar el compilador (mcc):
Llave |
Propósito |
---|---|
un nombre de archivo |
Añade el <nombre de archivo> archivo al registro y determina los archivos que se añadirán al archivo CTF |
l |
Macro que genera una librería de funciones |
N |
Libera todas las vías, excepto el conjunto de directorios mínimos |
p <directorio> |
Añade la vía de conversión según el procedimiento. Requiere la llave -N. |
R -nojvm |
Cancela la opción MCR (tiempo de ejecución del componente de MATLAB, véase la ayuda de MATLAB) |
W |
Gestiona la creación de envolventes de funciones |
lib |
Crea las funciones de inicialización y finalización |
main |
Crea la capa POSIX de la función main() |
T |
Especifica la etapa de salida |
codegen |
Crea el código de envolvente para la aplicación autónoma |
compile:exe |
Igual que codegen |
compile:lib |
Crea el código de envolvente para la DLL pública |
link:exe |
Igual que compile:exe más linking |
link:lib |
Igual que compile:exe más linking |
Tabla 3. Llaves del compilador mcc de MatLab (versión 4)
La Tabla 3 contiene las llaves básicas que pueden ser útiles para resolver problemas habituales. Para más ayuda utilice el comando de ayuda de MATLAB help mcc o doc mcc.
Debemos familiarizarnos con el enlazador de MATLAB, a continuación se encuentran las llaves principales (mbuild):
Llave |
Propósito |
---|---|
-setup |
En el modo interactivo es la definición del archivo de opciones del compilador usado por defecto en futuras llamadas de mbuild. |
-g |
Crea el programa con la información de depuración. Anexa DEBUGFLAGS al final del archivo. |
-O |
Optimización del código del objeto |
Tabla 4. Llaves del enlazador mbuild de Matlab (versión 4)
La Tabla 4 enumera las llaves principales. Para más información use los comandos de la ayuda mbuild o doc mbuild.
2.3 Librería de entrada/salida estándar de C++
El uso de la librería de entrada/salida estándar permite copiar correctamente los datos. Su utilización nos permitirá evitar los errores absurdos que surgen durante la fase de diseño del programa (por ejemplo: muchos programadores novatos solo copian el puntero al bloque de memoria en lugar de copiar todo el bloque de memoria). De toda la librería de entrada/salida solo nos interesa una función:
void *pIn = memcpy(void *pIn, void *pOut, int nSizeByte) – esta función copia (clona) la variable/matriz de pOut a pln con nSizeByte de tamaño, donde:
- void *pIn — puntero a la matriz, dónde copiar.
- void *pOut — puntero a la matriz de la que se hace el copiado.
- int nSizeByte — el tamaño de los datos copiados no debe exceder el tamaño de la matriz pln, ya que de lo contrario se producirá un error de acceso a memoria.
3. Práctica
Ya hemos terminado con la teoría y podemos proceder con la interacción entre MetaTrader 5 y MATLAB.
Como probablemente habrá adivinado, esto se hará de dos formas: usando la máquina virtual del motor de MATLAB y usando las librerías generadas por el compilador de MATLAB. En primer lugar, vamos a considerar una forma simple, rápida y versátil de interacción a través del motor de MATLAB.
Esta parte del artículo debe leerse desde de principio a fin, ya que a pesar de las aparentes diferencias entre los métodos de interacción, tienen una filosofía y sintaxis familiar en la construcción del lenguaje y para aprender algo nuevo es más fácil con ejemplos simples.
3.1 Desarrollar una librería universal de la interacción entre MetaTrader 5 y el motor de MATLAB
Este método de interacción no puede ser calificado como elegante y rápido, pero es el más fiable y cubre todo el paquete MATLAB. Por supuesto, debemos mencionar la velocidad del desarrollo del modelo final. La esencia del desarrollo es escribir una librería-envolvente universal para la interacción de MetaTrader 4/5 y el motor de MATLAB. Después, este script/indicador/experto de MetaTrader 4/5 puede gestionar el escritorio virtual de MATLAB. Y todo el algoritmo matemático puede almacenarse en un programa MQL como strings, de forma que podemos usarlo para proteger nuestra propiedad intelectual (para más información véase el artículo "¡Protéjanse, programadores!). También puede almacenarse en archivos separados de funciones m o funciones P en la carpeta <MetaTrader 5>\MQL5\Libraries folder.
Posibles áreas de aplicación de esta interacción:
- Para probar o demostrar "modelos/ideas matemáticas" sin tener que escribir complejos programas (la protección de la propiedad intelectual puede realizarse al igual que en los programas MQL y por medio del paquete MATLAB usando funciones P).
- Para escribir complejos modelos matemáticos usando todas las características de MATLAB.
- Para todos aquellos que no van a distribuir sus scripts/indicadores/expertos.
Vamos con ello. Espero que haya leído los apartados 1.1 Tipos de datos en MATLAB y MQL5, 1.2 Comparación de los tipos de datos de MQL5 y MATLAB, 2.1 La API del motor de MATLAB y las funciones MCR y 2.3 Librería de entrada/salida estándar de C++, ya que no pararemos ni las veremos más. Lea detenidamente los siguientes bloques-esquema que ilustran el algoritmo de la librería futura:
Figura 1. Bloque-esquema del algoritmo de la librería
Como se ve en la Figura 1, la librería está compuesta por tres bloques principales. Veamos su finalidad:
- Bloque de MQL5, preparación preliminar de los datos enviados/recibidos:
-
- Matrices inversas.
- Conversión de tipos.
- Conversión de las codificaciones de strings.
- Bloque de C/C++:
-
- Convierte la matriz en la estructura mxArray.
- Pasa los comandos del motor de MATLAB.
- Bloque del motor de MATLAB - sistema de cálculos.
Ahora vamos a trabajar con algoritmos. Comenzaremos con el bloque MQL5. El lector atento se habrá dado cuenta de que se entrará en la implementación de lo que se escribió en el apartado de tipos de datos en MATLAB y MQL5. Si se lo ha perdido, difícilmente comprenderá por qué es necesario todo esto.
El algoritmo de las funciones mlInput <variable_type> es casi idéntico. Vamos a ver cómo funciona usando la función mlInputDouble() que proporciona la entrada de las variables de tipo doble a la máquina virtual de MATLAB.
Este es el prototipo:
bool mlInputDouble(double &array[],int sizeArray, string NameArray), donde:
-
array — referencia a la variable o matriz de tipo doble.
-
sizeArray — tamaño de la matriz (número de elementos, ¡no de bytes!).
-
NameArray — string que contiene un nombre de variable único para la máquina virtual de MATLAB (el nombre debe cumplir los requisitos de MATLAB).
Algoritmo:
- Convierte el string NameArray en matriz char usando la función StringToCharArray().
-
Verifica el tipo de indexado usando la función ArrayIsSeries(). Si el tipo de indexado es normal pasa el valor a la función mlxInputDouble().
Indexado de la matriz de series de tiempo: "invierte" la matriz y pasa el valor a la función mlxInputDouble().
- Finaliza la función y pasa el valor devuelto a la función mlxInputDouble().
El algoritmo de las funciones mlGet <variable_type> es también casi idéntico. Vamos a ver cómo funciona usando la función mlGetDouble() que devuelve la variable de tipo doble de la máquina virtual de MATLAB.
El prototipo:
int mlGetDouble(double &array[],int sizeArray, string NameArray), donde:
-
array — referencia a la variable o matriz de tipo doble.
-
sizeArray — tamaño de la matriz (número de elementos, ¡no de bytes!).
-
NameArray — string que contiene un nombre de variable único para la máquina virtual de MATLAB.
Algoritmo:
- Convierte el string NameArray en matriz char usando la función StringToCharArray().
- Obtiene el tamaño de la matriz usando la función mlxGetSizeOfName() .
-
- Si el tamaño es mayor que cero asigna a la matriz de recepción el tamaño necesario usando la función ArrayResize(), obtiene los datos de mlxGetDouble(), devuelve el tamaño de la matriz.
- Si el tamaño es cero, devuelve error, es decir, valor nulo.
¡Y eso es todo! Las funciones mlGetInt() y mlGetLogical() producen una conversión de "sombra" de los tipos double ->; int/bool. Para esta finalidad las funciones crean un buffer de memoria temporal en sus cuerpos. Esta es una medida forzada ya que por desgracia la API de MATLAB no permite crear estructuras mxArray para tipos de datos que no sean dobles. Sin embargo, esto no significa que MATLAB opere exclusivamente con tipos dobles.
El bloque C/C++ es más fácil ya que debe proporcionar la conversión de datos desde el tipo doble a la estructura mxArray. Esto se hace usando las funciones mxCreateDoubleMatrix(), mxGetPr() y memcpy(). A continuación, usando la función engPutVariable() pasa los datos a la máquina virtual de MATLAB y para extraer los datos usa la función engGetVariable(). De nuevo, debemos prestar atención a las funciones con prefijos Int y Logical — como se ve en el bloque-esquema no interactúan directamente con MATLAB sino que usan las funciones mlxInputDouble/mlxGetDouble y mlxInputChar(). El algoritmo de su comportamiento es simple: llamada de la función mlxInputDouble/mlxGetDouble - valores entrada/salida como doble (!) y envía el comando MATLAB "sombra" para convertir el tipo de datos a través de la función mlxInputChar() .
El bloque del motor de MATLAB es aún más fácil. Proporciona solo funciones matemáticas y su comportamiento depende de sus comandos y funciones m/p.
Ahora, cuando todos los detalles del proyecto están claros es momento de abordar la elaboración del proyecto.
Cualquier elaboración de este tipo comienza con la creación de la librería principal - en nuestro caso esta es el bloque C/C++. Para esta finalidad creamos un archivo con la extensión DEF en cualquier editor de texto ANSI (notepad, bred, etc.). Es deseable que el nombre de este archivo tenga caracteres latinos sin espacios ni puntuación, ya que de lo contrario "oirá" muchas palabras de halago de su compilador... Este archivo proporciona la permanencia de sus funciones. Si este archivo está ausente, C/C++ creará sus propios "nombres exóticos" para exportar las funciones.
El archivo contiene: LIBRARY — palabra de control, LibMlEngine — nombre de la librería y EXPORTS — segunda palabra de control, a continuación vienen los nombres de las funciones. Como probablemente ya sepa, los nombres de las funciones de exportación no pueden tener espacios ni signos de puntuación. Este es el texto del archivo DIIUnit.def del archivo MATLABEngine.zip:
LIBRARY LibMlEngine
EXPORTS
mlxClose
mlxInputChar
mlxInputDouble
mlxInputInt
mlxInputLogical
mlxGetDouble
mlxGetInt
mlxGetLogical
mlxGetSizeOfName
mlxOpen
Por tanto, tenemos el primer archivo del proyecto. Ahora abrimos Windows Explorer y vamos a la carpeta '<MATLAB>\Extern\include'. Copiamos el archivo engine.h (archivo cabecera de la máquina virtual de MATLAB) en la carpeta donde está nuestro proyecto (si no hiciéramos esto tendríamos que especificar manualmente la ruta al archivo en la etapa de compilación).
Ahora vamos a crear el bloque C/C++. No incluiremos todo el código fuente del programa en el artículo ya que este archivo se encuentra disponible en MATLABEngine.zip como DllUnit.cpp y está bien documentado. Observe cómo es mejor crear funciones usando la convención __stdcall - por ejemplo, los parámetros se pasan a través de la pila y la función vacía la pila. Esta norma es "nativa" para la API de Win32/64.
Vamos a ver ahora cómo declarar una función:
extern "C" __declspec(dllexport) <variable_type> __stdcall Function(<type> <name>)
- extern "C" __declspec(dllexport) — le dice al compilador de C++ que la función es externa.
- <variable_type> — tipo de la variable devuelta que puede ser: void, bool, int, double, tipos combinados (conocidas no solo para DII sino también para llamar al programa) y punteros.
- __stdcall — declaración sobre el paso de parámetros a la función y hacia atrás, es un estándar de la API de Win32/64.
- Funcion — nuestro nombre de la función.
-
<type> <name> — tipo y nombre de la variable de entrada, donde el máximo número de variables es 64.
Este tema se trata en detalle en el artículo cómo intercambiar datos: una DLL para MQL5 en 10 minutos.
Construcción del bloque C/C++: para esto necesitamos incluir una librería estándar de entrada/salida y añadir los siguientes archivos al proyecto (en nuestro compilador): Proyecto->Añadir proyecto):
- DllUnit.def
- En la carpeta <MATLAB>\Extern\lib\<win32/64>\<compiler>\, donde:
<MATLAB> — carpeta principal de MATLAB.
<win32/64> — la carpeta win32 para un SO de 32-bit, o win64 para un SO de 64-bit.
<compiler> — la carpeta de "borland" para Borland C/C++. 5-6, la carpeta de "Microsoft" para Microsoft Visual C++: -
- libeng.lib
- libmx.lib
Puede surgir una pregunta muy habitual como la siguiente: "¡Tengo una versión distinta del compilador o no tengo este compilador en mi lista" (en raras ocasiones no tendremos tales archivos)". Vamos a ver cómo crear manualmente una librería pública. Vamos a ver cómo se hace en Visual C++ y en Borland C++.
- En FAR abrimos la carpeta <MATLAB>\Bin\<win32/64>, donde:
<MATLAB> — carpeta principal de MATLAB.
<win32/64> — la carpeta win32 para un SO de 32-bit, o win64 para un SO de 64-bit. - Para Borland C++ introducimos: implib libeng.lib libeng.dll. Lo mismo para libmx.dll.
- Para Visual C++ introducimos:lib libeng.dll. Lo mismo para libmx.dll.
-
Para otro compilador: cualquier compilador de cualquier lenguaje de programación debe tener esta utilidad - Gestor de librerías, normalmente este es un programa de la consola <compiler _folder>\bin\*lib*.exe.
A propósito, olvidé avisarle, no intente hacer una librería de 64-bit para un compilador de 32-bit. En primer lugar, averigüe si hay soporte para 64-bit en la ayuda del compilador. Si no la hay utilice la DLL de MATLAB de 32-bit o elija otro compilador C/C++. Una vez llegamos a la compilación, tras la cual obtendremos una librería, esta debe ubicarse en la carpeta terminal_folder\MQL5\Libraries.
Vamos a comenzar ahora con el bloque MQL. Ejecute MetaEditor, haga clic en "Nuevo" y proceda como se indica a continuación:
Figura 2. MQL5 Wizard Crear librería
Figura 3. MQL5 Wizard Propiedades generales de la librería
Ahora, una vez que el Wizard de MQL5 ha creado una plantilla procedemos a su edición:
1. Describir la importación de funciones:
//+------------------------------------------------------------------+ //| DECLARATION OF IMPORTED FUNCTIONS | //+------------------------------------------------------------------+ #import "LibMlEngine.dll" void mlxClose(void); //void – means: don't pass any parameters! bool mlxOpen(void); //void – means: don't pass and don't receive any parameters! bool mlxInputChar(char &CharArray[]); //char& CharArray[] – means: pass a reference! bool mlxInputDouble(double &dArray[], int sizeArray, char &CharNameArray[]); bool mlxInputInt(double &dArray[], int sizeArray, char &CharNameArray[]); bool mlxInputLogical(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetDouble(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetInt(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetLogical(double &dArray[], int sizeArray, char &CharNameArray[]); int mlxGetSizeOfName(char &CharNameArray[]); #import
Observe que en MQL5 podemos pasar "punteros" de dos formas:
- void NameArray[] ; // Este método de pasar desde la matriz permite solo leer los datos. Sin embargo, si intentamos usar esta referencia para "editar sus contenidos" obtendremos un error de acceso a memoria (en el mejor de los casos MetaTrader 5 gestionará silenciosamente el error en el SHE-frame pero NO HEMOS ESCRITO un SEH-frame, por lo que podemos incluso obviar la razón del error)
- void& NameArray[] ; // Este método de pasar nos permite leer y editar contenidos de la matriz pero debemos mantener el tamaño de la matriz.
Si la función no acepta o no pasa los parámetros, siempre especificaremos el tipo void.
2. No describimos todas las funciones del bloque de MQL porque el código fuente de MatlabEngine.mq5 se encuentra disponible en MATLABEngine.zip.
Por lo tanto, vamos a considerar los detalles de la declaración y definición de las funciones externas en MQL5:
bool mlInputChar(string array)export { //... body of function }
Como puede verse en el ejemplo, la declaración y definición de la función está mezclada. En este caso, declaramos la función llamada mlInputChar() como externa (export), lo que devuelve un valor del tipo bool y acepta el string de la matriz como parámetro. Ahora compilamos...
Ahora que hemos completado el último bloque de la librería y lo hemos compilado, es el momento de probarlo en condiciones reales.
Para hacer esto, escribimos un script de prueba simple (o lo tomamos de MATLABEngine.zip, archivo: TestMLEngine.mq5).
El código del script es simple y está bien comentado:
#property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com/ru" #property version "1.00" #import "MatlabEngine.ex5" bool mlOpen(void); void mlClose(void); bool mlInputChar(string array); bool mlInputDouble(double &array[], int sizeArray, string NameArray); bool mlInputInt(int &array[], int sizeArray, string NameArray); int mlGetDouble(double &array[], string NameArray); int mlGetInt(int &array[], string NameArray); bool mlInputLogical(bool &array[], int sizeArray, string NameArray); int mlGetLogical(bool &array[], string NameArray); int mlGetSizeOfName(string strName); #import void OnStart() { // Dynamic buffers for MATLAB output double dTestOut[]; int nTestOut[]; bool bTestOut[]; // Variables for MATLAB input double dTestIn[] = { 1, 2, 3, 4}; int nTestIn[] = { 9, 10, 11, 12}; bool bTestIn[] = {true, false, true, false}; int nSize=0; // Variables names and command line string strComm="clc; clear all;"; // command line - clear screen and variables string strA = "A"; // variable A string strB = "B"; // variable B string strC = "C"; // variable C /* ** 1. RUNNING DLL */ if(mlOpen()==true) { printf("MATLAB has been loaded"); } else { printf("Matlab ERROR! Load error."); mlClose(); return; } /* ** 2. PASSING THE COMMAND LINE */ if(mlInputChar(strComm)==true) { printf("Command line has been passed into MATLAB"); } else printf("ERROR! Passing the command line error"); /* ** 3. PASSING VARIABLE OF THE DOUBLE TYPE */ if(mlInputDouble(dTestIn,ArraySize(dTestIn),strA)==true) { printf("Variable of the double type has been passed into MATLAB"); } else printf("ERROR! When passing string of the double type"); /* ** 4. GETTING VARIABLE OF THE DOUBLE TYPE */ if((nSize=mlGetDouble(dTestOut,strA))>0) { int ind=0; printf("Variable A of the double type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("A = %g",dTestOut[ind]); } } else printf("ERROR! Variable of the double type double hasn't ben got"); /* ** 5. PASSING VARIABLE OF THE INT TYPE */ if(mlInputInt(nTestIn,ArraySize(nTestIn),strB)==true) { printf("Variable of the int type has been passed into MATLAB"); } else printf("ERROR! When passing string of the int type"); /* ** 6. GETTING VARIABLE OF THE INT TYPE */ if((nSize=mlGetInt(nTestOut,strB))>0) { int ind=0; printf("Variable B of the int type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("B = %i",nTestOut[ind]); } } else printf("ERROR! Variable of the int type double hasn't ben got"); /* ** 7. PASSING VARIABLE OF THE BOOL TYPE */ if(mlInputLogical(bTestIn,ArraySize(bTestIn),strC)==true) { printf("Variable of the bool type has been passed into MATLAB"); } else printf("ERROR! When passing string of the bool type"); /* ** 8. GETTING VARIABLE OF THE BOOL TYPE */ if((nSize=mlGetLogical(bTestOut,strC))>0) { int ind=0; printf("Variable C of the bool type has been got into MATLAB, with size = %i",nSize); for(ind=0; ind<nSize; ind++) { printf("C = %i",bTestOut[ind]); } } else printf("ERROR! Variable of the bool type double hasn't ben got"); /* ** 9. ENDING WORK */ mlClose(); }
Como vemos en el script, estamos introduciendo valores y a continuación obtenemos valores. Sin embargo, al contrario que en MetaTrader 4 donde necesitamos conocer el tamaño del buffer en la etapa de diseño, en MetaTrader 5 esto no es necesario ya que usamos buffers dinámicos.
Ahora que hemos comprendido finalmente la máquina virtual de MATLAB, podemos comenzar a usar la DLL incluida en el entorno de MATLAB.
3.2 Guías técnicas para construir/usar DLL generadas por el compilador 4 de MATLAB
En el apartado previo hemos aprendido cómo crear una librería para la interacción universal con el paquete MATLAB. Sin embargo, este método tiene una desventaja: requiere el paquete MATLAB por parte del usuario final. Esta restricción crea una serie de dificultades en la distribución del software terminado. Por esta razón, el paquete matemático MATLAB tiene un compilador integrado que permite crear "aplicaciones autónomas" independientes del paquete MATLAB. Vamos a echarle un vistazo.
Por ejemplo, consideremos un indicador simple: media móvil (SMA). Actualicémosla ligeramente añadiendo un filtro de red neuronal (GRNN) que permite suavizar el "ruido blanco" (bursts aleatorios). Llamamos al nuevo indicador NeoSMA y al filtro GRNNFilter.
De esta forma, tenemos dos funciones m de las que queremos crear una DLL que pueda ser llamada desde MetaTrader 5.
Ahora recuerde que MetaTrader 5 busca DLL en las siguientes carpetas:
- <terminal_dir>\MQL5\Libraries
- <terminal_dir>
- Carpeta actual
- Carpeta del sistema <windows_dir>\SYSTEM32;
- <windows_dir>
- Las carpetas enumeradas en la variable de entorno del sistema PATH.
Por tanto, ubicamos en una de esas carpetas dos funciones m (NeoSMA.m y GRNNFilter.m) donde construiremos la DLL. Quiero que preste atención a la ubicación, ya que esto no se realiza por casualidad. El lector atento ya conocerá la característica del compilador de MATLAB: este mantiene las rutas mientras compila (véase "2.2 Compilador 4 de MATLAB).
Antes de que comience a compilar el proyecto debe configurar el compilador. Para hacer esto siga estos pasos:
- En la línea de comandos de MATLAB introduzca: mbuild -setup
- Pulse "y" para confirmar los compiladores C/C++ instalados en su sistema.
- Elija el compilador Lcc-win32 C estándar.
- Pulse "y" para confirmar el compilador seleccionado.
Figura 4. Compilando el proyecto
Ahora ya estamos preparados para comenzar con el proceso de compilación de las funciones m.
Para esto introducimos:
mcc -N -W lib:NeoSMA -T link:lib NeoSMA.m GRNNFilter.m
Explicación de las llaves:
-N — para pasar por alto todas las rutas innecesarias
-W lib:NeoSMA — le dice al compilador que NeoSMA es el nombre de la librería
-T link:lib — le dice al compilador que cree una librería pública con enlazado
NeoSMA.m and GRNNFilter.m — los nombres de las funciones m
Veamos ahora cual es el compilador que ha creado:
- mccExcludedFiles.log — archivo log que contiene las acciones del compilador
- NeoSMA.c — versión C de la librería (contiene el código С de la envolvente)
- NeoSMA.ctf — archivo CTF (véase el apartado 2.2 Compilador 4 de MATLAB)
- NeoSMA.h — archivo cabecera (contiene las declaraciones de las librerías, funciones y constantes)
- NeoSMA.obj — archivo del objeto (archivo fuente que contiene el código máquina y el seudocódigo)
- NeoSMA.exports — nombres exportados de las funciones
- NeoSMA.dll — DLL para enlazado adicional
- NeoSMA.lib — DLL para usar en proyectos C/C++
- NeoSMA_mcc_component_data.c — versión C en el componente (usado para cumplir con el archivo CTF, contiene las rutas, etc.)
- NeoSMA_mcc_component_data.obj — versión objeto del componente (archivo fuente que contiene el código máquina y el pseudocódigo);
Por tanto, vamos a trabajar con la DLL precisamente con su estructura interna. Consta de (solo funciones básicas):
- Función principal de cualquier DLL - BOOL WINAPI DllMain(), que (según la especificación de Microsoft) gestiona los eventos en la DLL: Carga de la DLL en el espacio de dirección del proceso creando un nuevo flujo de datos y borrando el flujo y descargando la DLL de la memoria.
- Funciones de servicio de la inicialización/deinicialización de la DLL: BOOL <NameLib>Initialize(void)/void <NameLib>Terminate(void) — es necesario para iniciar/descargar el entorno de trabajo matemático antes de usar las funciones de la librería y al finalizar su uso.
-
Funciones m exportadas – void mlf<NameMfile>(int <number_of_return_values>, mxArray **<return_values>, mxArray *<input_values>, ...), donde:
- <number_of_return_values> — número de variables devueltas (no confundir con el tamaño de la matriz, etc.).
- mxArray **<return_values> — dirección de la estructura de mxArray donde serán devueltos los resultados del trabajo de la función m.
- mxArray *<input_values> — puntero a la estructura de mxArray de la variable de entrada de la función m.
Como puede ver, las funciones m exportadas contienen direcciones y punteros a la estructura mxArray y no podemos llamar directamente a estas funciones desde MetaTrader 5 ya que no comprenderá este tipo de datos. No describimos la estructura mxArray en MetaTrader 5 ya que los programadores de MATLAB no garantizan que no cambie con el tiempo, incluso para una misma versión del producto, por lo que es necesario escribir un simple adaptador DLL.
Su bloque-esquema se muestra a continuación:
Figura 5. Bloque-esquema del adaptador de la DLL
Es muy similar a la parte derecha de la DLL del motor de MATLAB, por lo que no analizaremos su algoritmo y procederemos directamente con el código. Para esto creamos dos pequeños archivos en nuestro compilador C/C++:
nSMA.cpp (de DllMatlab.zip):
#include <stdio.h> #include <windows.h> /* Include MCR header file and library header file */ #include "mclmcr.h" #include "NEOSMA.h" /*--------------------------------------------------------------------------- ** DLL Global Functions (external) */ extern "C" __declspec(dllexport) bool __stdcall IsStartSMA(void); extern "C" __declspec(dllexport) bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd); /*--------------------------------------------------------------------------- ** Global Variables */ mxArray *TempY; mxArray *TempIn; mxArray *TempN; mxArray *TempAd; bool bIsNeoStart; //--------------------------------------------------------------------------- int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { switch(reason) { case DLL_PROCESS_ATTACH: bIsNeoStart = false; TempY = 0; //Nullify pointers to buffers TempN = 0; TempIn = 0; TempAd = 0; break; case DLL_PROCESS_DETACH: NEOSMATerminate(); //Delete old data before exiting from Dll if(TempY != NULL) mxDestroyArray(TempY); if(TempN != NULL) mxDestroyArray(TempN); if(TempIn != NULL) mxDestroyArray(TempIn); if(TempAd != NULL) mxDestroyArray(TempAd); mclTerminateApplication(); } return 1; } //--------------------------------------------------------------------------- bool __stdcall IsStartSMA(void) { if(bIsNeoStart == false) { if(!mclInitializeApplication(NULL,0) ) { MessageBoxA(NULL, (LPSTR)"Can't start MATLAB MCR!", (LPSTR) "MATLAB DLL: ERROR!", MB_OK|MB_ICONSTOP); return false; }else { bIsNeoStart = NEOSMAInitialize(); }; }; return bIsNeoStart; } //--------------------------------------------------------------------------- bool __stdcall nSMA(double *pY, int nSizeY, double *pIn, int nSizeIn, double dN, double dAd) { /* ** Create buffers */ if(TempN == NULL){ TempN = mxCreateDoubleMatrix(1, 1, mxREAL);} else { mxDestroyArray(TempN); TempN= mxCreateDoubleMatrix(1, 1, mxREAL); }; if(TempIn == NULL){ TempIn = mxCreateDoubleMatrix(1, nSizeIn, mxREAL);} else { mxDestroyArray(TempIn); TempIn= mxCreateDoubleMatrix(1, nSizeIn, mxREAL); }; if(TempAd == NULL){ TempAd = mxCreateDoubleMatrix(1, 1, mxREAL);} else { mxDestroyArray(TempAd); TempAd= mxCreateDoubleMatrix(1, 1, mxREAL); }; /* ** Creating data for processing */ memcpy((char *)mxGetPr(TempIn), (char *) pIn, (nSizeIn)*8); memcpy((char *)mxGetPr(TempN), (char *) &dN, 8); memcpy((char *)mxGetPr(TempAd), (char *) &dAd, 8); /* ** Send and receive a response from the m-function */ if(mlfNeoSMA(1, (mxArray **)TempY, (mxArray *)TempIn, (mxArray *)TempN , (mxArray *)TempAd) == false) return false; /* ** Return calculated vector from the m-function and clear buffers */ memcpy((char *) pY, (char *)mxGetPr(TempY), (nSizeY)*8); mxDestroyArray((mxArray *)TempY); TempY = 0; mxDestroyArray((mxArray *)TempN); TempN = 0; mxDestroyArray((mxArray *)TempIn); TempIn = 0; mxDestroyArray((mxArray *)TempAd); TempAd = 0; return true; }
nSMA.def (de DllMatlab.zip):
LIBRARY nnSMA
EXPORTS
IsStartSMA
nSMA
Construcción del proyecto en nuestro compilador C/C++: para esto necesitamos incluir una librería estándar de entrada/salida y añadir los siguientes archivos al proyecto (en nuestro compilador: Proyecto->Añadir proyecto):
- nSMA.def
- En la carpeta <MATLAB>\Extern\lib\<win32/64>\<compiler>\, donde:
<MATLAB> — carpeta principal de MATLAB.
<win32/64> — la carpeta win32 para un SO de 32-bit, o win64 para un SO de 64-bit.
<compiler> — la carpeta de "borland" para Borland C/C++. 5-6, la carpeta de "Microsoft" para Microsoft Visual C++ (Tengo los archivos para la versión 6): -
- libmx.lib
- mclmcr.lib
- NeoSMA.lib — se crea manualmente (véase 3.1 Desarrollando la librería universal de la interacción de MetaTrader 5 y el motor de MATLAB).
Lo último que quiero contarle en este apartado es en relación a los archivos necesarios para llevar el proyecto a otro ordenador donde no esté instalado MATLAB.
Esta es la lista de archivos y rutas de la máquina de destino:
- MCRInstaller.exe cualquier carpeta (instalador MCR)
- extractCTF.exe cualquier carpeta (instalador MCR)
- MCRRegCOMComponent.exe cualquier carpeta (instalador MCR)
- unzip.exe cualquier carpeta (instalador MCR)
- NeoSMA.dll <terminal_dir>\MQL5\Libraries
- NeoSMA.ctf <terminal_dir>\MQL5\Libraries
- nnSMA.dll <terminal_dir>\MQL5\Libraries
Muchos programadores avanzados se han preguntado si es aconsejable usar un programa instalador (SETUP). Hay muchos de ellos en internet, incluso productos gratuitos.
Ahora tenemos que probar esta DLL en MetaTrader 5. Para hacer esto escribiremos un script simple (TestDllMatlab.mq5 de DllMatlab.zip):
#property copyright "2010, MetaQuotes Software Corp." #property link "nav_soft@mail.ru" #property version "1.00" #import "nnSMA.dll" bool IsStartSMA(void); bool nSMA(double &pY[], int nSizeY, double &pIn[], int nSizeIn, double dN, double dAd); #import datetime Time[]; // dynamic array of time coordinates double Price[]; // dynamic array of price double dNeoSma[]; // dynamic array of price void OnStart() { int ind=0; // run Dll if(IsStartSMA()==true) { //--- create and fill arrays CopyTime(Symbol(),0,0,301,Time); // time array + 1 ArraySetAsSeries(Time,true); // get the time chart CopyOpen(Symbol(),0,0,300,Price); // price array ArraySetAsSeries(Price,true); // get the open prices ArrayResize(dNeoSma,300,0); // reserve space for function response // get data if(nSMA(dNeoSma,300,Price,300,1,2)==false) return; // specify array orientation ArraySetAsSeries(dNeoSma,true); // plot data on chart for(ind=0; ind<ArraySize(dNeoSma);ind++) { DrawPoint(IntegerToString(ind,5,'-'),Time[ind],dNeoSma[ind]); } } } //+------------------------------------------------------------------+ void DrawPoint(string NamePoint,datetime x,double y) { // 100% ready. Traza los datos en el gráfico. Dibujando usando flechas. // Main properties of chart object ObjectCreate(0,NamePoint,OBJ_ARROW,0,0,0); ObjectSetInteger(0, NamePoint, OBJPROP_TIME, x); // time coordinate x ObjectSetDouble(0, NamePoint, OBJPROP_PRICE, y); // price coordinate y // Additional properties of chart object ObjectSetInteger(0, NamePoint, OBJPROP_WIDTH, 0); // line width ObjectSetInteger(0, NamePoint, OBJPROP_ARROWCODE, 173); // arrow type ObjectSetInteger(0, NamePoint, OBJPROP_COLOR, Red); // arrow color } //+------------------------------------------------------------------+
Conclusión
Ahora sabemos cómo crear una librería universal para la interacción de MetaTrader 5 y MATLAB y cómo conectar la DLL incluida en el entorno de MATLAB. Pero todavía hay interfaces de la interacción de MetaTrader 5 y MATLAB que deben ser descritas, aunque esto está fuera del alcance de este artículo. El tema de este artículo se ha tratado en profundidad. He utilizado las formas más efectivas de interacción que no requieren ningún tipo especial de "adaptadores". Aunque es posible hacerlo de otra forma, como a través de la tecnología .NET - Cómo exportar cotizaciones desde MetaTrader 5 a las aplicaciones .NET usando servicios WCF.
Muchos lectores pueden preguntarse ¿qué método usar? La respuesta es simple: ambos, porque durante el diseño/depuración del modelo matemático no se necesita velocidad. Pero necesitaremos toda la capacidad de MATLAB sin "costes de producción especiales" para la programación. El motor de MATLAB será de ayuda aquí, por supuesto. Sin embargo, cuando el modelo matemático es compilado y está listo para usarse, necesitaremos velocidad, multitarea (trabajo de indicadores y/o sistema de transacciones en varios gráficos de precio) - aquí sin duda necesitaremos una DLL construida en el entorno de MATLAB.
Pero todo esto no nos obliga a seguirlo. Cada uno dará su propia respuesta, respondiendo principalmente en términos de los "costes de programación" para la escala del proyecto (número de indicadores y/o usuarios de sistemas de trading). No tiene sentido crear una DLL en el entorno de MATLAB para uno o dos usuarios (es más fácil instalar MATLAB en dos ordenadores).
Muchos lectores familiarizados con MATLAB tendránprobablemente una pregunta; ¿por qué todo esto? ¡MQL5 ya dispone de funciones matemáticas! La respuesta es que el uso de MATLAB permite implementar sin esfuerzo nuestras ideas matemáticas, entre las que podemos encontrar una lista parcial de posibilidades como sigue:
- algoritmo dinámico de lógica confusa en el indicador y/o sistema de trading mecánico
- algoritmo genético dinámico en el sistema de trading mecánico (probador de estrategias dinámico)
- algoritmo de red neuronal dinámico en el indicador y/o sistema de trading mecánico
- indicadores de tres dimensiones
- simulación de sistemas de gestión no lineales
Por tanto, todo depende de usted y no olvide: "las matemáticas siempre han sido la reina de las ciencias" y el paquete MATLAB es su calculadora científica.
Bibliografía
- Ayuda de MATLAB.
- Ayuda de MQL5.
- Jeffrey Richter. Programando aplicaciones para Microsoft Windows.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/44





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso