Simulink: una guía para desarrolladores de asesores expertos

Denis Kirichenko | 11 marzo, 2014


Introducción

Hay varios artículos que describen las inmensas posibilidades de Matlab. Para ser más precisos, la forma en que este software es capaz de ampliar las herramientas que utiliza el programador para desarrollar un asesor experto. En este artículo intentaré ilustrar el funcionamiento de este potente paquete de Matlab llamado Simulink.

Me gustaría ofrecer una forma alternativa de desarrollar sistemas de trading automatizados para operadores. El cambio a este método se debió a la complejidad del problema al que se enfrentan los operadores: la creación, verificación y prueba de sistemas de trading automatizados. No soy un programador profesional. Y por ello, el principio "ir de lo simple a lo complejo" es muy importante para mí cuando trabajo en el desarrollo de sistemas de trading. ¿Qué es exactamente simple para mí? En primer lugar, es la visualización del proceso de creación del sistema y la lógica de su funcionamiento. También es un mínimo de código escrito manualmente. Estas expectativas encajan muy bien con las capacidades del paquete Simulink®, un conocido producto MATLAB, líder mundial en instrumentos de visualización de cálculos matemáticos.

En este artículo intentaré crear y probar el sistema de trading basado en un paquete de Matlab, y a continuación escribiré un asesor experto para MetaTrader 5. Además, todos los datos históricos para el backtesting se usarán a partir de MetaTrader 5.

Para evitar la confusión terminológica llamaré a los sistemas de trading que funcionan en Simulink con una palabra genérica MTS, y a los que funcionan en MQL5 los llamaré simplemente asesor experto.


1. Los fundamentos de Simulink y Stateflow

Antes de proceder a realizar acciones concretas es necesario hacer una introducción sobre algunas cuestiones teóricas.

Con la ayuda del paquete Simulink®, que es parte de MATLAB, el usuario puede modelar, simular y analizar sistemas dinámicos. Además, es posible preguntar sobre la naturaleza del sistema para simularlo y observar entonces qué ocurre.

Con Simulink, el usuario puede construir un modelo desde cero o modificar un modelo existente. El paquete permite el desarrollo de sistemas lineales y no lineales creados sobre la base de comportamientos discretos, continuos e híbridos.

Las principales propiedades del paquete se muestran en el sitio de los desarrolladores:

Por tanto, vamos a comenzar inmediatamente con la revisión del entorno de Simulink. Se inicializa desde una ventana ya abierta de Matlab de una de las dos formas siguientes:

  1. usando el comando de Simulink en la ventana de comandos;
  2. usando el icono de Simulink en la barra de herramientas.

Figura 1. Inicialización de Simulink

Figura 1. Inicialización de Simulink

Cuando se ejecuta el comando, aparece la ventana de navegación por las librerías (navegador de librerías de Simulink). 

Figura 2. Navegador de librerías

Figura 2. Navegador de librerías


La ventana del navegador contiene el árbol de los componentes de las librerías de Simulink. Para ver una parte específica de la librería, simplemente la seleccionamos con el ratón y aparecerá un conjunto de componentes de iconos de la parte activa de la librería en la parte derecha de la ventana del navegador de librerías de Simulink. La Figura 2 muestra la sección principal de la librería de Simulink.

Usando el menú del navegador o los botones de su barra de herramientas, podemos abrir una ventana para crear un nuevo modelo o para subir uno existente. Debo señalar que todo el trabajo con Simulink tiene lugar mientras está abierto un sistema MATLAB en el que es posible monitorizar la ejecución de las operaciones siempre que su salida sea proporcionada por el programa de modelado.


Figura 3. La ventana en blanco de Simulink

Figura 3. La ventana en blanco de Simulink


En primer lugar vamos a cambiar algunos parámetros de nuestro modelo. Abrimos Simulación -->Parámetros de configuración. Esta ventana tiene un número de pestañas con muchos de los parámetros. Nos interesa la pestaña por defecto Solucionador, donde podemos establecer los parámetros del solucionador del sistema de modelado de Simulink.

En el momento de la simulación, el tiempo de modelado es establecido por el momento del inicio: el momento de inicio (habitualmente 0), y el momento de la parada, stop time.

Para nuestra tarea, vamos a asignar al momento de inicio el valor 1. Vamos a dejar el momento de parada como está.

En las opciones del solucionador también he cambiado el tipo de paso fijo, el propio solucionador y el paso (tamaño del paso fijo) a 1.

Figura 4. Ventana de parámetros de configuración


El entorno de Simulink se completa con éxito gracias al subsistema Stateflow, un paquete de modelado por evento basado en la teoría de los autómatas de estado finito. Esto nos permite representar el trabajo del sistema en base a una cadena de reglas que especifican los eventos y acciones en respuesta a esos eventos.

La interfaz gráfica del usuario del paquete Stateflow tiene los siguientes componentes:

El diagrama de bloque (gráfico) usado con más frecuencia, situado en la sección Stateflow. Veámoslo a continuación:

Vamos a mover el bloque de la librería y a hacer doble clic en él para abrir el diagrama. Aparecerá una ventana en blanco de un editor de gráficos SF. Puede usarse para crear gráficos SF y su depuración, y así poder obtener las funciones necesarias.

La Barra de Herramientas está situada arriba a la derecha, en el lado izquierdo. Hay 9 botones:

  1. Estado;
  2. Unión del historial;
  3. Transición por defecto;
  4. Unión conectiva;
  5. Tabla de la verdad;
  6. Función;
  7. Función MATLAB incorporada;
  8. Caja;
  9. Llamada a la función de Simulink.

Por desgracia, es imposible considerar cada elemento en detalle por separado en el contexto de este artículo. Por tanto, voy a limitarme a una breve descripción de aquellos elementos que vamos a necesitar en nuestro modelo. Puede encontrar información más detallada en la sección de ayuda de Matlab o en el sitio web del desarrollador.

Figura 5. La vista del gráfico SF en el editor

Figura 5. La vista del gráfico SF en el editor

El objeto clave de los gráficos SF en el Estado. Se muestra como un rectángulo con las equinas redondeadas.

Puede ser exclusivo o paralelo. Cada estado puede ser padre y tener herederos. Los estados pueden ser activos o no activos y pueden realizar ciertos procedimientos.

La transición se muestra como una línea curva, con flecha, y conecta los estados y otros objetos. Puede realizarse una transición haciendo clic con el botón izquierdo del ratón en el objeto de origen y dirigiendo el cursor al objeto de destino. La transición puede tener sus propias condiciones que se graban en los paréntesis. El procedimiento de transición se indica entre paréntesis y se ejecutará si se cumple la condición. El procedimiento ejecutado durante la confirmación del objeto de destino se marca con una barra oblicua.

El nodo alternativo (unión conectiva) tiene la forma de un círculo, y permite la transición a través de los diferentes caminos, cada uno de los cuales se define por una condición específica. En tal caso, se selecciona la transición que corresponde a la condición especificada.

La función es representada como un gráfico de flujo con las declaraciones del lenguaje de procedimiento de Stateflow. Un gráfico de flujo refleja la estructura lógica del uso de transiciones y nodos alternativos.

Un evento es otro objeto importante de Stateflow que pertenece al grupo de objetos no gráficos. Este objeto puede lanzar los procedimientos del gráfico SF.

El procedimiento (acción) también es un objeto no gráfico. Puede llamar a la función, asignar un evento específico, transición, etc.

El dato en el modelo SF se representa por valores numéricos. El dato no se representa como objeto gráfico. Pueden crearse a cualquier nivel de la jerarquía del modelo y tener propiedades.


2. Descripción de la estrategia de trading

Ahora un breve inciso sobre el trading. Para nuestros propósitos de trading, el asesor experto erá muy simple, por no decir primitivo.

El sistema de trading automatizado abrirá posiciones en base a la señal, básicamente después de cruzar los movimientos exponenciales con periodo de 21 y 55 (números de Fibonacci) promediados en los precios de cierre. Si la EMA 21 cruza la EMA 55 desde abajo, se abre una posición larga, en caso contrario se abre una corta.

Para el filtrado del ruido, se abrirá la posición en la barra k-ésima para el precio de apertura de la barra después de que ocurra el cruce 21/55. Vamos a operar con EURUSD H1. Solo se abrirá una posición. Solo se cerrará al llegar al nivel Take Profit o Stop Loss.

Me gustaría señalar que durante el desarrollo del sistema de trading automatizado y el backtesting del historial, se han admitido ciertas simplificaciones del conjunto de las transacciones.

Por ejemplo, el sistema no comprobará la ejecución por el broker de una señal. Además, añadiremos las restricciones de trading al núcleo del sistema en MQL5.


3. Modelado de una estrategia de trading en Simulink

Para comenzar, necesitamos subir los datos de precios históricos al entorno de Matlab. Vamos a hacerlo usando un script de MetaTrader 5 donde los guardaremos (testClose.mq5).

En Matlab, estos datos (Open, High, Low, Close, Spread) también se cargarán usando un script simple (priceTxt.m).

Usando movavg (una función estándar de Matlab) crearemos matrices de medias móviles exponenciales:

[ema21, ema55] = movavg(close, 21, 55, 'e');

Así como una matriz auxiliar de los índices de la barra:

num=1:length(close);

Vamos a crear las siguientes variables:

K=3; sl=0.0065; tp=0.0295;

Vamos a comenzar con el proceso de modelado.

Creamos una ventana en blanco de Simulink y la llamamos mts cuando la guardemos. Todas las acciones siguientes se han copiado en un formato de vídeo. Si algo no está muy claro, o no está claro en absoluto, puede ver mis acciones en el vídeo.

Al guardar el modelo, el sistema puede mostrar el siguiente error:

??? El archivo "C:\\Simulink\\mts.mdl" contiene caracteres que son incompatibles con la codificación de caracteres actual windows-1251. Para evitar este error, seguimos una de las dos alternativas siguientes:
1) Usamos la función slCharacterEncoding para cambiar la codificación de caracteres actual a una de las siguientes: Shift_JIS, windows-1252, ISO-8859-1.
2) Eliminamos los caracteres no soportados. El primer caracter no soportado está en la línea 23, byte offset 15.

Para eliminar este, simplemente necesitamos cerrar todas las ventanas de los modelos y cambiar la codificación usando los siguientes comandos:

bdclose all
set_param(0, 'CharacterEncoding', 'windows-1252');

Vamos a especificar la fuente de información de nuestro modelo.

La función de esta fuente de información serán los datos históricos de MetaTrader 5 que contienen los precios de apertura, máximos, mínimos y de cierre. Además, tendremos en cuenta el Spread, si bien se ha convertido en flotante hace relativamente poco tiempo. Finalmente, grabamos el momento de la apertura en la barra. Con fines de modelado, se interpretarán algunas matrices de datos iniciales como una señal, este es un vector de valores de una función de tiempo en puntos discretos en el tiempo.

Vamos a crear el subsistema "FromWorkspace" para obtener los datos del espacio de trabajo de Matlab. Seleccionamos la sección Puertos y Subsistemas en el navegador de librerías de Simulink. Arrastramos el bloque "Subsystem" a la ventana de modelo de Simulink utilizando el ratón. Lo renombramos como "FromWorkspace" haciendo clic en "Subsystem". A continuación, entramos en él haciendo doble clic con el botón izquierdo del ratón sobre el bloque para crear las variables de entrada y salida y las constantes del sistema.

Para crear las fuentes de señal en el navegador de la librería, elegimos el Signal Processing Blockset y las fuentes (Signal Processing Sources). Usando el ratón, arrastramos el bloque "Signal from WorkSpace" a la ventana del subsistema del modelo FromWorspace. Como el modelo tendrá 4 señales de entrada, simplemente duplicamos el bloque y creamos 3 copias más en él. Vamos a especificar ahora qué variables serán procesadas por el bloque. Para hacer esto, hacemos clic dos veces sobre el bloque e introducimos el nombre de la variable en las propiedades. Estas variables serán: open, ema21, ema55, num. Daremos a los bloques los nombres siguientes: open signal, ema21 signal, ema55 signal, num signal.

Ahora, de la sección "Bloques usados frecuentemente" de Simulink, añadiremos un bloque para crear un canal (creador del bus). Abrimos el bloque y cambiamos el número de entradas a 4. Conectamos los bloques de señales open, ema21, ema55 y num con las entradas del bloque creador del bus (Bus Creator).

Además, tendremos 5 constantes de entrada más. El bloque "Constant" se añade de la sección "Bloques usados frecuentemente". Como valor (valor Constant ) especificamos los nombres de las variables: spread, high, low, tp, sl:

Llamaremos a los bloques de la siguiente forma: spread array, high array, low array, Take Profit, Stop Loss.

Seleccionamos el bloque del puerto de salida (Out1) en la sección de Simulink "Ports & Subsystems" (Puertos y subsistemas) y lo movemos a la ventana del subsistema. Hacemos 5 copias del puerto de salida. El primero lo conectaremos con el bloque creador del bus (Bus Creator) y los demás, alternativamente, con las matrices de bloques high, low, Take Profit, y Stop Loss.

Vamos a renombrar el primer puerto a price y los demás por el nombre de la variable de salida.

Para crear una señal de trading vamos a insertar el bloque de adición (Add) de la sección de Simulink "Mathematical Operations" (Operaciones matemáticas). Lo llamaremos diferencial emas. Dentro del bloque vamos a cambiar la lista de signos c + + a + -. Usando la combinación de teclas Ctrl + K, giramos el bloque 90º en el sentido de las agujas del reloj. Conectamos el bloque de señal ema21 a la entrada "+" y la señal ema55 con el "-". 

A continuación, insertamos el bloque Delay de la sección Signal Processing Blockset (Bloques de procesado de señal) de Signal Operations (operaciones de señal). Lo llamaremos K Delay. En el campo Delay (ejemplos) de este bloque introducimos el nombre de la variable K. Lo conectamos con el bloque anterior.

Los bloques diferenciales emas y K Delay formatean el frente (diferencia de nivel) de la señal de control para los cálculos solo para la etapa de modelado en la que ha habido un cambio. El subsistema que crearemos un poco más tarde se activará si al menos un elemento tiene un cambio en su nivel de señal.

A continuación, de la sección de Simulink "Bloques usados frecuentemente" añadiremos un multiplexador with y un bloque (Mux). De igual forma, giramos el bloque 90º en el sentido de las agujas del reloj. Dividiremos en dos la línea de señal del bloque delay y las conectaremos con los múltiples.

De la sección Stateflow, insertamos un bloque Chart. Introducimos el gráfico. Añadimos 2 eventos de llegada (Buy y Sell) y dos eventos de salida (OpenBuy y OpenSell). El valor de disparo (Trigger) para el evento Buy lo estableceremos a Falling (activación del subsistema por un frente negativo) y para los eventos Sell lo estableceremos a Rising (activación del subsistema por un frente positivo). El valor de disparo (Trigger) para los eventos OpenBuy y OpenSell lo establecernos en la posición de la llamada a Function (Calling), (la activación del subsistema vendrá determinada por la lógica del funcionamiento de la función S dada).

Vamos a crear una transición por defecto con 3 nodos alternativos. El primer nodo lo conectaremos mediante una transición al segundo nodo, estableciendo las condiciones y procedimientos para ellos en Buy {OpenBuy;}, y para el tercero, estableciendo el procedimiento en Sell {OpenSell;}. Conectamos la entrada del gráfico con el múltiple y las dos salidas con otro múltiple, que puede copiarse del primero. El último bloque se conectará al puerto de salida que copiaremos de uno análogo y lo llamaremos Buy/Sell.

¡Y casi se me olvida! Para que el modelo funcione correctamente necesitamos crear un objeto de canal virtual que ubicaremos en el espacio de trabajo de Matlab. Para hacer esto introducimos el Bus Editor a través del menú Herramientas. En el editor seleccionamos el elemento Add Bus y lo llamamos InputBus.

Insertamos los elementos según los nombres de las variables de entrada: open, ema21, ema55 y num. Abrimos el Bus Creator y marcamos la casilla de verificación junto a las propiedades Specify a través del objeto bus (establece las propiedades a través del objeto bus). En otras palabras, hemos conectado nuestro bloque con el objeto de canal virtual que hemos creado. El canal virtual significa que las señales solo se combinan gráficamente sin afectar la distribución de memoria.

Guardamos los cambios en la ventana del subsistema. Esto concluye nuestro trabajo con el subsistema FromWorkspace.


Ahora es el momento de crear el "Black Box". Será un bloque, basado en las señales de llegada, que procesará la información y tomará decisiones de trading. Por supuesto, ha de ser creado por nosotros y no por un programa de ordenador. Después de todo, solo nosotros podemos decidir bajo qué condiciones debe realizar el sistema una transacción. Además, el bloque tendrá que mostrar la información sobre los contratos realizados en forma de señales.

El bloque que se requiere se llama Chart y se encuentra en la sección Stateflow. Ya nos hemos familiarizado con él, ¿verdad? Usando "arrastrar y soltar" lo movemos a nuestra ventana modelo.

Figura 6. Bloques del subsistema de entrada y el gráfico de StateFlow

Figura 6. Bloques del subsistema de entrada y el gráfico de StateFlow


Abrimos el gráfico e introducimos nuestros datos en él. En primer lugar, vamos a crear un objeto de canal tal y como hemos hecho en el subsistema FromWorkspace. Pero a diferencia del primero, que nos suministró las señales del espacio de trabajo, este nos devolverá el resultado obtenido. Y, por tanto, lo llamaremos el objeto OutputBus. Sus elementos serán: barOpen, OpenPrice, TakeProfit, StopLoss, ClosePrice, barClose, Comment, PositionDir, posN y AccountBalance.

Ahora comenzaremos a construirlo. En la ventana del gráfico mostraremos la transición por defecto (# 1).

Para las condiciones y procedimientos debemos indicar:

[Input.num>=56 && Input.num>Output.barClose] {Output.barOpen=Input.num;i = Input.num-1;Output.posN++;}

Esta condición significa que los datos serán procesados si el número de barras de entrada es, al menos, 56 y si la barra de entrada es mayor que la barra de cierre de la posición previa. A continuación, a la barra de apertura (Output.barOpen) se le asigna el número de la barra de entrada por una variable índice de i. El índice (comenzando a partir de 0) y el número de posiciones abiertas se incrementan en 1.

La segunda transición se ejecuta solo si la posición abierta no es la primera. De lo contrario, se ejecuta la tercera transición, que asignará a la variable del saldo de la cuenta (Output.AccountBalance) el valor 100.000.

La cuarta transición se ejecuta si el gráfico fue inicializado por el evento OpenBuy. En tal caso, la posición se dirigirá a la compra (Output.PositionDir = 1) y el precio de apertura será igual al precio de la barra de apertura, teniendo en cuenta el margen diferencial (Output.OpenPrice = Input.open + spread [i] * 1e-5). También se especificarán los valores de las señales de salida StopLoss y TakeProfit.

Si ocurre un evento OpenSell, el flujo seguirá a continuación de la quinta transición y establecerá sus valores para las señales de salida.

La sexta transición se lleva a cabo si la posición es larga, de lo contrario el flujo seguirá a continuación de la transición séptima.

La octava transición comprueba si el precio máximo de la barra ha alcanzado el nivel de Take Profit, o si el precio mínimo de la barra ha alcanzado el nivel de Stop Loss. De lo contrario, el valor de la variable índice i se incrementa en uno (novena transición).

La décima transición verifica las condiciones que surgen en Stop Loss: el mínimo del precio de la barra ha cruzado el nivel de Stop Loss. Si esto se confirma, el flujo seguirá a continuación de la onceava transición y luego a continuación de la doceava, donde los valores de las diferencias de precio entre las posiciones de cierre y apertura, y se define el saldo actual de la cuenta y el índice de la barra de cierre.

Si no se confirma la décima transición, se cierra la posición en Take Profit (decimotercera transición). Y luego, a partir de la decimocuarta, el flujo seguirá a continuación de la transición decimosegunda.

Los procedimientos y condiciones para las transiciones de una posición corta son los opuestos.

Finalmente, hemos creado nuevas variables en el gráfico. Para integrarlas automáticamente en nuestro modelo necesitamos ejecutar el modelo directamente en la ventana del gráfico, haciendo clic en el botón "Start Simulation" (Comenzar la simulación). Tiene un aspecto similar al botón "Play" en los reproductores de música. En este punto, se ejecuta el Stateflow Symbol Wizard (objetos maestros SF), y este sugerirá que guardemos los objetos creados. Presionamos el botón SelectAll y hacemos clic en el botón Create. Se han creado los objetos. Vamos ahora a abrir el navegador del modelo. A la izquierda, hacemos clic en nuestro gráfico en la jerarquía del modelo. Vamos a clasificar los objetos por el tipo de datos (DataType).

Añadimos más datos usando los comandos de menú "Add" y "Data". Llamaremos Input a la primera variable. Cambiamos el valor de Scopes a Input y de Type a Bus: <bus object name>. Y a continuación introducimos el nombre del canal creado anteriormente, InputBus, justo en este campo. De esta forma, nuestra variable Input tendrá el tipo de InputBus. Vamos a establecer el valor de Port a uno.

Completamos la misma operación con la variable Output. Solo este debe tener el tipo Output Scope y Output Bus.

Vamos a cambiar el scope para las variables high, low, sl, tp y spread al valor de "Input". Respectivamente estableceremos los números de puerto en el siguiente orden: 3, 4, 6, 5, 2.

También vamos a cambiar el scope de la variable Lots a Constant. En la pestaña "Value Attributes" (atributos del valor) vamos a introducir los eventos 1, OpenBuy y OpenSell para Input en el evento "Initial" (a la derecha).  En los eventos cambiamos el valor de disparo para la "llamada de la función".

Creamos una variable interna len con un scope (alcance) Constant. En la pestaña "Value Attributes", en el campo "Initial value", introduciremos una longitud de función m (cierre). De esta forma será igual a la longitud de la matriz de cierre que se encuentra en el espacio de trabajo de Matlab.

Para las variables high y low introduciremos un valor de [len 1] en el campo Size (tamaño). De esta forma, en la memoria hemos reservado los tamaños de matriz de high y low como valor de [len 1].

También vamos a indicar para la variable K en la pestaña "Value Attributes", en el campo "Initial value" (a la derecha) la variable actual de K, tomada del espacio de trabajo.

Como resultado, tenemos un subsistema Chart, con 7 puertos de entrada y uno de salida. Vamos a posicionar el bloque de tal forma que el puerto de los eventos de entrada () esté en la parte inferior. Renombraremos el bloque "Position handling". En el propio gráfico también mostraremos el nombre del bloque. Combinamos los bloques del subsistema FromWorkspace y "Position handling" a través de los puertos adecuados. Y cambiamos el color de los bloques.

Debe señalarse que el subsistema "Position handling" solo funcionará si es "despertado" por eventos de llegada OpenBuy u OpenSell. De esta forma, optimizamos la operación del subsistema para evitar los cálculos innecesarios.

Figura 7. Subsistemas FromWorkspace y Position handling


Ahora tenemos que crear un subsistema para imprimir los resultados del procesamiento en el espacio de trabajo de Matlab y combinarlo con el subsistema "Position handling". Será la tarea más fácil.

Vamos a crear el subsistema "FromWorkspace" para obtener los resultados en el espacio de trabajo. Repetimos los pasos seguidos al crear el subsistema "FromWorkspace". En el navegador de la librería seleccionamos la sección Simulink Ports & Subsystems (Puertos y subsistemas de Simulink). Usando el ratón, arrastramos el bloque "Subsystem" en la ventana modelo de Simulink. Lo renombramos como "ToWorkspace" haciendo clic en "Subsystem". Combinamos el bloque con el subsistema "Position handling".

Para crear las variables, accedemos haciendo doble clic en el bloque con el botón izquierdo del ratón.

Como el subsistema recibirá datos del objeto OutputBus, que es un bus no virtual, necesitamos seleccionar las señales de este canal. Para hacer esto, seleccionamos la sección "Bloques usados frecuentemente" de Simulink, en el navegador de la librería y añadimos un "Bus Selector". El bloque tendrá 1 señal de entrada y 2 de salida, mientras que necesitamos tener 10 de estas señales.

Vamos a conectar el bloque al puerto de entrada. Pulsamos el botón "Start simulation" (Iniciar simulación) (este es nuestro botón "Play"). El compilador comenzará a construir el modelo. No se construirá con éxito pero creará señales de entrada para el bloque de selección de bus. Si entramos en el bloque veremos aparecer las señales necesarias en el lado izquierdo de la ventana, que son transmitidas a través de OutputBus. Es necesario seleccionarlas todas usando el botón "Select" y moverlas al lado derecho "Selected signals" (señales seleccionadas).

Figura 8. Parámetros del bloque Bus Selector

Figura 8. Parámetros del bloque Bus Selector


Vamos a referirnos de nuevo a la sección "Bloques usados frecuentemente" del navegador de librerías de Simulink y a añadir el bloque múltiple Mux. Indica el número de entradas iguales a 10.

A continuación accedemos a la sección "Sinks" del navegador de librerías de Simulink y movemos el bloque ToWorkspace a la ventana del subsistema. Ahí indicaremos el nuevo nombre de la variable "AccountBalance" y cambiaremos el formato de salida (formato Save) desde "Structure" a "Array". Combinamos el bloque con el múltiple. Borramos el puerto de salida ya que no lo necesitaremos más. Y personalizamos el color de los bloques. Guardamos la ventana. El subsistema está listo.

Antes de construir el modelo, debemos verificar la presencia de las variables en el espacio de trabajo. Deben estar presentes las siguientes variables. InputBus, K, OutputBus, close, ema21, ema55, high, low, num, open, sl, spread y tp.

Vamos a establecer el valor de Stop Time como parámetro para definir num (end). Lo que significa que el vector procesado tendrá la longitud establecida por el último elemento de la matriz num.

Antes de empezar a construir el modelo, necesitamos elegir un compilador usando el siguiente comando:

mex-setup

Por favor, elija su compilador para construir archivos de interfaz externa (MEX):
¿Quiere que mex localice los compiladores instalados [s]/n? s

Seleccione un compilador:

[1] Lcc-win32 C 2.4.1 in C:\PROGRA~2\MATLAB\R2010a\sys\lcc
[2] Microsoft Visual C++ 2008 SP1 in C:\Program Files (x86)\Microsoft Visual Studio 9.0
[0] None

Compilador: 2

Como puede ver, he seleccionado el compilador SP1 de Microsoft Visual C ++ 2008.

Vamos a empezar a construir. Presionamos el botón "Start simulation" (Iniciar la simulación). Hay un error. Stateflow Interface Error (Error de interfaz de Stateflow): Incompatibilidad de ancho de puerto. La entrada "spread" (#139) espera un escalar. La señal es el vector de una dimensión con 59.739 elementos.

La variable "spread" no debe tener el tipo doble sino heredar su tipo de la señal de Simulink.

En el navegador del modelo, para esta variable, especificamos "Inherit: Igual que Simulink", y en el campo Size, y especificamos "-1". Guardamos los cambios.

Vamos a ejecutar el modelo de nuevo. Ahora funciona el compilador. Mostrará algunos avisos de menor importancia. Y en menos de 40 segundos el modelo procesará los datos de casi 60.000 barras. La transacción se realiza desde '2001 .01.01 00:00 ' a '2010 .08.16 11:00'. La cantidad total de posiciones abiertas es 461. Puede observar cómo funciona el modelo en el siguiente clip.



4. Implementación de la estrategia en MQL5

De esta forma, nuestro sistema de trading automatizado se compila en Simulink. Ahora necesitamos transferir esta idea de trading al entorno de MQL5. Tenemos que trabajar con los bloques de Simulink a través de los cuales hemos expresado la lógica de nuestro asesor experto de trading. La tarea actual es transferir el sistema de trading lógico al asesor experto de MQL5.

Sin embargo, debe señalarse que algunos bloques no necesariamente tienen que estar de alguna forma definidos en el código de MQL5, ya que sus funciones pueden estar ocultas. Intentaré comentar, con el mayor nivel de detalle, qué línea se corresponde con cada bloque en el código. Algunas veces esta relación puede ser indirecta. Y algunas veces puede reflejar una conexión de interfaz de bloques u objetos.

Antes de empezar esta sección, permítame llamar su atención respecto al artículo "Guía paso a paso para escribir asesores expertos en MQL5 para principiantes". Este artículo proporciona una sencilla descripción de la principales ideas y reglas básicas a la hora de escribir un asesor experto en MQL5. Pero no voy a hacer hincapié en ellas ahora. Usaré algunas líneas de código MQL5 de ahí.


4.1 Subsistema "FromWorkspace"

Por ejemplo, tenemos un bloque "open signal" en el subsistema "FromWorkspace". En Simulink, este es necesario para obtener el precio de la barra de apertura durante el backtesting y para abrir una posición a este precio en caso de recibirse una señal de trading. Este bloque no está presente, obviamente, en el código MQL5, ya que el asesor experto solicita la información sobre el precio inmediatamente después de recibir la señal de trading.

En el asesor experto necesitaremos procesar los datos recibidos de las medias móviles. Por tanto, crearemos para ellos matrices dinámicas y las correspondientes variables auxiliares, tales como controladores.

int ma1Handle;  // Moving Average 1 indicator handle: block "ema21 signal"
int ma2Handle;  // indicator handle Moving Average 2: block "ema55 signal"

ma1Handle=iMA(_Symbol,_Period,MA1_Period,0,MODE_EMA,PRICE_CLOSE); // get handle of Moving Average 1 indicator 
ma2Handle=iMA(_Symbol,_Period,MA2_Period,0,MODE_EMA,PRICE_CLOSE); // get handle of Moving Average 2 indicator

double ma1Val[]; // dynamic array for storing the values of Moving Average 1 for every bar: block "ema21 signal"
double ma2Val[]; // dynamic array for storing the values of Moving Average 2 for every bar: block "ema55 signal"

ArraySetAsSeries(ma1Val,true);// array of indicator values MA 1: block "ema21 signal"
ArraySetAsSeries(ma2Val,true);// array of indicator values MA 2: block "ema55 signal"

Todas las demás líneas que afectan de alguna forma el movimiento de ema21 y ema55 pueden considerarse como auxiliares.

Take Profit y Stop Loss se definen como variables de entrada:

input int TakeProfit=135;   // Take Profit: Take Profit in the FromWorkspace subsystem
input int StopLoss=60;      // Stop Loss:  Stop Loss in the FromWorkspace subsystem
Teniendo en cuenta que hay 5 dígitos significativos para EURUSD, el valor de TakeProfit y StopLoss deberá actualizarse de la siguiente forma:
int sl,tp;
sl = StopLoss;
tp = TakeProfit;
if(_Digits==5)
 {
  sl = sl*10;
  tp = tp*10;
 }

Las matrices "spread", "high" y "low" se usan como valores, ya que son responsables de suministrar los datos históricos en forma de matriz de datos de precio relevantes, para identificar las condiciones de trading.

No se representan de forma explícita en el código. Sin embargo, puede argumentarse que la matriz "spread", por ejemplo, es necesaria para formar una corriente de precios ask. Y las otras dos son necesarias para determinar las condiciones para cerrar una posición que no se especifican en el código, ya que se excluyen automáticamente en MetaTrader 5 al llegar a un cierto nivel de precio.

El bloque "num signal" es auxiliar y no se muestra en el código del asesor experto.

El bloque "emas differential" comprueba las condiciones para abrir posiciones cortas o largas encontrando las diferencias. "K Delay" crea un retraso para las matrices que son promedio respecto al valor de K.

El evento Buy o Sell se crea y es un evento de entrada para el subsistema de apertura de posición.

En el código, todo se expresa de la siguiente forma:

// event Buy (activation by the negative front)
bool Buy=((ma2Val[1+K]-ma1Val[1+K])>=0 && (ma2Val[K]-ma1Val[K])<0) ||
         ((ma2Val[1+K]-ma1Val[1+K])>0 && (ma2Val[K]-ma1Val[K])==0);

// event Sell (activation by the positive front)
bool Sell=((ma2Val[1+K]-ma1Val[1+K])<=0 && (ma2Val[K]-ma1Val[K])>0)||
         ((ma2Val[1+K]-ma1Val[1+K])<0 && (ma2Val[K]-ma1Val[K])==0);
El subsistema de apertura de posición crea por sí mismo los eventos "OpenBuy" y "OpenSell", que son procesados en el subsistema "Position handling", usando las condiciones y procedimientos.

 

4.2 Subsistema "Position handling"

El subsistema comienza a trabajar procesando los eventos OpenBuy y OpenSell.

Para la primera transición del subsistema, una de las condiciones es la presencia de no menos de 56 barras, lo que se indica en el código mediante la comprobación de tales condiciones:

if(Bars(_Symbol,_Period)<56) // 1st transition of the «Position handling»subsystem : condition [Input.num>=56]
      {
        Alert("Not enough bars!");
        return(-1);
      }

La segunda condición para la transición: el número de barras de apertura debe ser mayor que el de las barras de cierre (Input.num; Output.barClose), es decir, la posición se ha cerrado.

En el código, todo se expresa de la siguiente forma:

//--- 1st transition of the «Position handling» subsystem: condition [Input.num>Output.barClose]

bool IsBought = false;  // bought
bool IsSold = false;    // sold
if(PositionSelect(_Symbol)==true) // there is an opened position
 {
   if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_BUY)
     {
      IsBought=true;  // long
     }
   else if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL)
     {
      IsSold=true;    // short
     }
  }
// check for opened position
if(IsTraded(IsBought,IsSold))
 {
   return;
 }

//+------------------------------------------------------------------+
//| Function of the check for opened position                        |
//+------------------------------------------------------------------+
bool IsTraded(bool IsBought,bool IsSold)
  {
   if(IsSold || IsBought)
     {
      Alert("Transaction is complete");
      return(true);
     }
   else
      return(false);
  }

La cuarta transición se encarga de abrir una posición larga.

Se representa de la siguiente forma:

// 4th transition procedures of the «Position handling» subsystem: open long position
 mrequest.action = TRADE_ACTION_DEAL;                                  // market buy
 mrequest.price = NormalizeDouble(latest_price.ask,_Digits);           // latest ask price
 mrequest.sl = NormalizeDouble(latest_price.bid - STP*_Point,_Digits);  // place Stop Loss
 mrequest.tp = NormalizeDouble(latest_price.bid + TKP*_Point,_Digits);  // place Take Profit
 mrequest.symbol = _Symbol;                                           // symbol
 mrequest.volume = Lot;                                              // total lots
 mrequest.magic = EA_Magic;                                          // Magic Number
 mrequest.type = ORDER_TYPE_BUY;                                       // order to buy
 mrequest.type_filling = ORDER_FILLING_FOK;                            // the specified volume and for a price, 
                                                                               // equal or better, than specified
 mrequest.deviation=100;                                             // slippage
 OrderSend(mrequest,mresult);
 if(mresult.retcode==10009 || mresult.retcode==10008) // request completed or order placed
    {
     Alert("A buy order has been placed, ticket #:",mresult.order);
    }
 else
    {
     Alert("A buy order has not been placed; error:",GetLastError());
     return;
    }

La quinta transición se encarga de abrir una posición corta.

Se representa de la siguiente forma:

// 5th transition procedures of the «Position handling» subsystem: open a short position
 mrequest.action = TRADE_ACTION_DEAL;                                  // market sell
 mrequest.price = NormalizeDouble(latest_price.bid,_Digits);           // latest bid price
 mrequest.sl = NormalizeDouble(latest_price.ask + STP*_Point,_Digits);  // place a Stop Loss
 mrequest.tp = NormalizeDouble(latest_price.ask - TKP*_Point,_Digits);  // place a Take Profit
 mrequest.symbol = _Symbol;                                          // symbol
 mrequest.volume = Lot;                                             // lots
 mrequest.magic = EA_Magic;                                         // Magic Number
 mrequest.type= ORDER_TYPE_SELL;                                      // sell order
 mrequest.type_filling = ORDER_FILLING_FOK;                           // in the specified volume and for a price, 
                                                                              // equal or better, than specified in the order
 mrequest.deviation=100;                                             // slippage
 OrderSend(mrequest,mresult);
 if(mresult.retcode==10009 || mresult.retcode==10008) // request is complete or the order is placed
    {
     Alert("A sell order placed, ticket #:",mresult.order);
    }
 else
    {
     Alert("A sell order is not placed; error:",GetLastError());
     return;
    }

Otras transiciones en las subcategorías no se encuentran en el asesor experto, ya que los procedimientos adecuados (activación de los stops o alcanzar un nivel Take Profit) se llevan a cabo automáticamente en MQL5.

El subsistema "ToWorkspace" no se encuentra en el código MQL5 ya que su tarea es plantear la salida a los espacios de trabajo de Matlab.


Conclusiones

Utilizando una idea de trading simple como ejemplo, he creado el sistema de trading automatizado en Simulink, en el que he llevado a cabo un backtesting sobre datos históricos. Al principio me fastidió la siguiente pregunta: "¿Hay algún punto en todo esto en que sea posible implementar rápidamente un sistema de trading mediante código MQL5?"

Por supuesto, puede hacerlo sin la visualización del proceso de creación del sistema y la lógica de su funcionamiento. Pero es más frecuente que esto solo lo realicen programadores expertos o personas con talento. Cuando el sistema de trading se amplía con nuevas condiciones y funciones, la tarea del operador será, claramente, la presencia del diagrama de bloque y su funcionamiento.

Me gustaría señalar que no intenté enfrentar las capacidades del lenguaje Simulink con las del lenguaje MQL5. Tan solo ilustré cómo se puede crear un sistema de trading automatizado usando un diseño de bloque. Puede que en el futuro, los desarrolladores creen un constructor de estrategias visual que facilite el proceso de escribir asesores expertos.