Interacción entre MetaTrader 5 y MATLAB

Andrey Emelyanov | 23 enero, 2014

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:

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:

Engine *pEng = engOpen(NULL) — esta función llama al escritorio de MATLAB, el parámetro es siempre NULL, devuelve un puntero al descriptor del escritorio.

int exitCode = engClose(Engine *pEng) — esta función cierra el escritorio, devuelve el número de usuarios restantes del escritorio de MATLAB, donde:

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:

void = mxDestroyArray(mxArray *mxVector) — esta función destruye la matriz de MATLAB, es necesaria para liberar memoria, donde:
int = engPutVariable(Engine *pEng, char *Name, mxArray *mxVector) — esta función envía la variable al escritorio. Debemos no solo crear las variables del tipo mxArray sino también enviarlas a MATLAB, donde:
mxArray *mxVector = engGetVariable(Engine *pEng, char *Name) — esta función obtiene la variable del escritorio, es la inversa de la función anterior. Solo son aceptadas las variables del tipo mxArray, donde:
double *p = mxGetPr(mxArray *mxVector) — esta función lleva el puntero a la matriz de valores y es usada para copiar datos junto con memcpy() (véase 2.3 Librería de entrada/salida estándar de C++), donde:
int = engEvalString(Engine *pEng, char *Command) — esta función envía comandos al escritorio de MATLAB, donde:

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:

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:  

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++:

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:

  1. Análisis de dependencias - en esta etapa determina todas las funciones, archivos MEX y archivos P de los que dependen las funciones m.  
  2. Crear el archivo - el archivo CTF se crea y es encriptado y comprimido.  
  3. 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).  
  4. Conversión de C/C++. En esta etapa se compilan los archivos del código fuente de C/C++.
  5. 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:


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:

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

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:

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:

Algoritmo:

  1. Convierte el string NameArray en matriz char usando la función StringToCharArray().
  2. 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().
  3. 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:

Algoritmo:

  1. Convierte el string NameArray en matriz char usando la función StringToCharArray().   
  2. 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>)

  1. extern "C" __declspec(dllexport) — le dice al compilador de C++ que la función es externa.  
  2. <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.
  3.  __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.  
  4. Funcion — nuestro nombre de la función.  
  5. <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):

  1. DllUnit.def
  2. 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++.

  1. 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.  
  2. Para Borland C++ introducimos: implib libeng.lib libeng.dll. Lo mismo para libmx.dll.
  3. Para Visual C++ introducimos:lib libeng.dll. Lo mismo para libmx.dll.
  4. 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 2. MQL5 Wizard Crear librería

Figura 3. MQL5 Wizard Propiedades generales de la 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:

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:   

  1. En la línea de comandos de MATLAB introduzca: mbuild -setup
  2. Pulse "y" para confirmar los compiladores C/C++ instalados en su sistema.
  3. Elija el compilador Lcc-win32 C estándar.
  4. Pulse "y" para confirmar el compilador seleccionado.

Figura 4. Compilando el proyecto

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:

Por tanto, vamos a trabajar con la DLL precisamente con su estructura interna. Consta de (solo funciones básicas):

  1. 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.  
  2. 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.
  3. 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

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):

  1. nSMA.def
  2. 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
  3. 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:

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:  

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

  1. Ayuda de MATLAB.
  2. Ayuda de MQL5.
  3. Jeffrey Richter. Programando aplicaciones para Microsoft Windows.