Desarrollando las interfaces gráficas para los Asesores Expertos e indicadores a base de .Net Framework и C#

Vasiliy Sokolov | 27 mayo, 2019

Introducción

Desde el octubre de 2018, MQL5 comenzó a ofrecer el soporte nativo de la integración con las bibliotecas de Net Framwork. El soporte nativo significa que ahora los tipos, métodos y las clases ubicados en la biblioteca de .Net están accesibles directamente desde un programa MQL5 sin la declaración previa de las funciones de llamada y sus parámetros, así como, sin la conversión compleja de los tipos de dos lenguajes uno a otro. Realmente, eso puede ser considerado como un determinado avance, ya que ahora una gigante base de código de .Net Framework y la potencia del lenguaje C# está disponible prácticamente para todos los usuarios de MQL5.

Las capacidades de Net Framework no se limitan sólo por la biblioteca en sí. Gracias al entorno de desarrollo VisualStudio integrado tipo shareware (condicionalmente gratuito), resulta mucho más fácil crear muchas cosas. Por ejemplo, se puede usarlo en modo «arrastrar y soltar» (drag-n-drop) para crear una aplicación windows completamente funcional, con un formulario y elementos que van a comportarse de manera usual, como cualquier otra aplicación gráfica de windows. Eso es lo que faltaba tanto en MQL.

Claro que, a lo largo de los años de la existencia de este lenguaje, aparecieron muchas versiones de la biblioteca facilitando considerablemente el desarrollo de una aplicación gráfica dentro del programa MQL. No obstante, todas estas bibliotecas, por más buenas que sean, representan un conjunto del código, cuyo principio de trabajo hay que entender y saber integrarlo con el código de sus EAs e indicadores. En otras palabras, es poco probable que los usuarios no familiarizados con la programación podían usar estas bibliotecas en la práctica. La desproporción entre la simplicidad de la creación de los formularios en Visual Studio y la complejidad de la configuración de las bibliotecas gráficas en MQL permanecería hasta ahora, pero gracias a la nueva integración con las bibliotecas de .Net Framework, la creación simple de los formularios se hace realidad. 

Este artículo está dedicado al diseño de las interfaces gráficas de usuario para los EAs e indicadores escritos en MQL5. Aquí, bajo las interfaces gráficas se entienden los formularios windows estándar que contienen un cierto conjunto de elementos gráficos estándar, cada uno de los cuales estará estrechamente interrelacionado con la lógica comercial del propio EA.

Para diseñar una aplicación gráfica, necesitaremos integrar el programa MQL con las bibliotecas de .Net. Este artículo también describe en detalles cómo hacer eso y cómo funciona. Por eso, será interesante no sólo para aquéllos que quieren crear algún formulario gráfico para su programa mql, sino también para aquéllos que se interesan del tema de la integración con una base de códigos ajena escrita en .Net.

El hincapié se hace en la sencillez del enfoque propuesto. La principal idea consiste en hacer una interracción con el código escrito en C# lo más simple posible. Además, la interracción debe ser organizada de tal forma que el código escrito en C# sea creado automáticamente, sin la intervención del usuario. Gracias a los recursos desarrollados de C# y a las capacidades avanzadas de Visual Studio, eso es posible.

Así, el lector de este artículo no necesitará ningunos conocimientos de C#. La idea principal consistirá en que el usuario coloque (en modo del constructor) los controles gráficos, como botones o etiquetas de texto tal como le parezca oportuno, y después de eso, atribuya la lógica correspondiente a cada elemento en el lenguaje MQL. Todas las acciones necesarias para integrar el panel con un programa MQL van a realizarse «entre bastidores», en modo automático. 


Esquema de interracción con interfaces gráficas de .Net, principios generales

.Net es el nombre patentado de la plataforma de lenguaje común desarrollada por Microsoft en 2002, como una alternativa a la plataforma Java que era popular en aquél entonces (es más, lo sigue siendo). La base de la plataforma es llamado entorno común de ejecución para lenguajes (Common Language Runtime o CLR por siglas en inglés). A diferencia de un programa clásico que se compila directamente en el código de máquina y puede ser ejecutado en el ordenador directamente, el programa escrito para .Net se ejecuta en una máquina virtual CLR. Así, .Net es un tipo de ambiente a través del cual un programa escrito en el lenguaje de alto nivel se ejecuta en la máquina de usuario.

El lenguaje de programación C# es el principal lenguaje de programación en .Net. Cuando hablan de C#, sobrentienden .Net, y viceversa, por tanto, .Net se asocia claramente con C#. Simplificando, se puede decir que .Net es un ambiente de ejecución para programas escritos normalmente en C#. Nuestro artículo no será una excepción. Todo el código propuesto en el artículo será escrito en este lenguaje.

Después de haber escrito el programa para la plataforma .Net, se compila en código de byte intermedio del lenguaje de bajo nivel CIL (Common Intermediate Language) que se ejecuta por la máquina virtual CLR. El propio código se empaqueta en las entidades estándar de los programas windows: los módulos de ejecución exe o las bibliotecas dinámicas dll. El código compilado para la máquina virtual Net tiene una estructura de alto nivel, sus propiedades son fáciles de explorar, se puede entender qué tipos de datos contiene. Las últimas versiones del compilador MQL utilizan esta magnifica particularidad. En la fase de compilación, el compilador carga la biblioteca dinámica Net y lee los métodos estáticos públicos definidos en ella. Aparte de los métodos estáticos públicos, el compilador MQL entiende los tipos básicos de los datos del lenguaje de programación C#. Estos tipos de datos incluyen:

  • Todos los tipos de datos enteros: long/ulong, int/uint, byte, short/ushort;
  • Números reales con punto flotante float/double;
  • Tipos de datos de caracteres char (a diferencia de MQL, donde char y uchar significan el tipo de bytes С#, este tipo de datos se usa se usa para determinar un carácter);
  • Tipos de cadena string;
  • Estructuras simples que contienen tipos básicos especificados arriba como sus campos.

Además de los tipos mencionados, MQL ve los arrays C#. Sin embargo, en este momento es imposible obtener el acceso estándar a los elementos del array a través del indexador '[]' en el programa MQL. Se puede decir con seguridad que, en el futuro, el soporte de los tipos va a ampliarse. Sin embargo, las capacidades actuales son bastante suficientes para organizar una interacción completa.

En nuestro proyecto, vamos a crear los formularios usando la tecnología Windows Forms. Es un conjunto bastante simple de APIs que permite dibujar rápidamente, y lo más importante de forma simple, una interfaz gráfica incluso para un usuario no preparado. Su particularidad consiste en un enfoque orientado a eventos. Eso significa que cuando el usuario hace clic en un botón o introduce un texto en la ventada de entrada, entonces se genera un evento correspondiente. Después de procesar este evento, el programa escrito en C# determina que cierto elemento gráfico del formulario ha sido modificado por el usuario. El trabajo con los eventos es un proceso bastante complicado para el usuario no familiarizado con C#. Por eso, es necesario escribir un código especial intermedio que va a procesar los eventos ocurridos en el formulario y transmitirlos al programa MQL iniciado en el terminal MetaTrader 5.

De esta manera, en nuestro proyecto habrá tres objetos independientes que van a interractuar entre sí:

Los tres objetos van a interactuar entre sí a través de un sistema de mensajes. Hay un sistema de mensajes entre el programa escrito en MQL y el controlador, y otro entre el controlador y la ventana de usuario.



Fig. 1. Esquema general de interacción entre el programa MQL y la aplicación gráfica C#

El esquema está representado en forma muy general y por ahora no revela las especificaciones entre las partes descritas de nuestra futura aplicación gráfica. Sin embargo, basándose en el esquema propuesto, nos queda claro que nuestro sistema será altamente distribuido: cada módulo será independiente, sin requerir intervenciones en su código si algún otro módulo ha sufrido modificaciones. En las siguientes secciones, se describe en detalle el propio proceso de interacción entre estas partes y los medios con los cuales se implementa esta separación.


Instalación y configuración de Visual Studio

Ahora, cuando ya tenemos preparado el esquema general, ha llegado el momento para proceder directamente a la implementación de nuestro proyecto. Para eso, necesitamos una versión funcional de Visual Studio. Si ya tiene instalado este programa, puede saltar esta sección. No obstante, esta sección será útil para los principiantes. 

Visual Studio representa un ambiente profesional del desarrollo en los campos más diversos de la programación. Está presentado en varias ediciones. Nos va a interesar la edición Community. Esta versión es del tipo shareware (condicionalmente gratuito). Pasados 30 días de su uso, hay que registrarla gratuitamente pasando un procedimiento estándar de verificación a través de uno de los servicios de Microsoft. Mostraremos los pasos básicos para bajar, instalar y registrar la plataforma para que los usuarios principiantes puedan empezar a usar su funcionalidad lo más pronto posible. 

Pues, aquí tenemos la guía paso a paso para instalar Visual Studio A continuación, mostramos las capturas de pantalla para la versión internacional del instalador en inglés. En su caso, la apariencia puede ser diferente dependiendo de los ajustes regionales de su ordenador. 

Lo primero que hay que hacer es pasar al sitio web oficial de Visual Studio visualstudio.microsoft.com y seleccionar la distribución apropiada. Recuerdo que necesitamos elegir la versión Community:

 

Fig. 2. Elección de la distribución de VisualStudio.


Después de la selección, comienza el proceso de la descarga del instalador de Visual Studio. Si la web solicita que se registre, ignore este paso. Lo hacemos más tarde.

Después de iniciar el instalador, aparecerá una ventana para ejecutar una cierta configuración de su instalador. Hay que confirmar pulsando el botón Сontinue:

Fig. 3. Confirmación de continuar la instalación

Luego, comienza el proceso de la descarga de archivos necesarios para la instalación. Eso puede llevar algún tiempo, dependiendo de la capacidad de tráfico de su canal de comunicación. Una vez finalizado el proceso, aparece la ventana de configuración de la instalación. Elija la opción ".Net desktop development": 


Fig. 4. Seleccionar componentes

Después de elegir la opción, pulse el botón Install. Se comienza el proceso de la instalación que puede durar algún tiempo:

Fig. 5. Proceso de instalación

Una vez terminada la instalación, Visual Studio se inicia automáticamente. Si eso no ha pasado, inícielo manualmente. Al iniciar Visual Studio por primera vez, debe entrar en su cuenta de usuario o crear una nueva. Si todavía no tiene ninguna cuenta, es mejor crearla ahora pulsando "Create One":


Fig. 6. Crear una cuenta de usuario nueva


Después de seguir el enlace "Create one", comienza el proceso del registro de una cuenta de correo nueva que estará vinculada en adelante a todos los servicios de Microsoft. Tiene que registrarse haciendo las acciones propuestas de forma consecutiva. Como el proceso del registro es estándar, no vamos a entrar en detalles.

Si por algún motivo no necesita registrarse, salte este paso siguiendo el enlace "Not now, maybe later". No obstante, no olvide que dentro de 30 días Visual Studio exigirá el registro, de lo contrario, terminará su trabajo.


Creación del primer formulario. Inicio rápido.

Después de registrarse y entrar en su cuenta de usuario, Visual Studio será iniciado. Vamos a intentar crear nuestro primer formulario visual y conectarlo con MetaTrader. Al terminar de leer esta sección, verá qué fácil hacer eso.

Bien, es necesario crear un proyecto nuevo. Para eso, seleccionamos en el menú  File -> New -> Project. Se abrirá una ventana de diálogo para seleccionar el tipo del proyecto:

Fig. 7.

Hay que seleccionar el tipo "Windows Form App (.Net Framework)". Introduzca el nombre del proyecto en el campo Name. Renombramos el nombre estándar facilitado por defecto y llamamos nuestro proyecto GuiMT. Luego, pulsamos el botón «OK». Después de eso, en Visual Studio se mostrará automáticamente el constructor visual con un formulario creado automáticamente.

 

Fig. 8. Creación del formulario gráfico en la ventana de Visual Studio.


La ventana Solution Explorer contiene la estructura de nuestro proyecto creado. Nótese que el nombre Form1.cs es un archivo que contiene el código que crea la representación gráfica del formulario que vemos en la ventana del constructor gráfico Form1.cs[Disign]. Recuerde este nombre, lo vamos a necesitar.

El constructor visual permite cambiar el tamaño del formulario a través del ratón. Además, se puede colocar los elementos personalizados en el formulario. Por ahora, eso es suficiente para los primeros experimentos. Abrimos la pestaña Toolbox, en las pestañas laterales a la izquierda de la ventana principal, en la sección All Windows Form seleccionamos el elemento Button:

Fig. 9. Seleccionar botón

Lo arrastramos a la superficie principal de nuestro formulario Form1 usando el ratón.

Fig. 10 El primer formulario

También puede cambiar el tamaño del botón. Puede experimentar con los tamaños de la ventana principal y la ubicación de este botón. Ahora, cuando tenemos un botón en nuestro formulario, vamos a considerar que nuestra primera aplicación está hecha. Vamos a compilarla. Hay varias maneras de hacerlo, pero ahora simplemente la iniciamos en el modo de depuración, para eso pulsamos el botón Start:

Fig. 11. Botón para iniciar la aplicación en el modo de la aplicación.  

Después de pulsar este botón, se realiza la compilación de la aplicación y su ejecución automática. Una vez iniciada la aplicación, se puede pararla, por ejemplo, pulsando la cruz del cierre de la ventana, o bien parando la depuración en Visual Studio mediante el botón Stop:

Fig. 11. Botón para parar la depuración

Pues, nuestra primera aplicación está hecha. La última cosa que tenemos que hacer es descubrir la ruta absoluta hacia el programa a ejecutar que acabamos de crear. La manera más fácil es simplemente mirar la ruta en el campo Project Folder de la ventana Properties, mientras que el proyecto GuiMT tiene que estar seleccionado en Solution Explorer :

Fig. 12. Ruta absoluta hacia el proyecto de la aplicación en Project Folder

La ruta en esta ventana se refiere al proyecto. El ensamblado (assembly en inglés) de nuestro programa va a ubicarse en una de las subcarpetas, dependiendo del modo de compilación. En nuestro caso, será .\bin\debug\<Nombre_del_proyecto.exe>. Así, la ruta completa hacia nuestro programa será la siguiente: C:\Users\<Nombre_del_usuario>\source\repos\GuiMT\GuiMT\bin\debug\GuiMT.exe. Después de aclarar la ruta, hay que anotarla o memorizar, por que más tarde tendremos que insertarla en nuestro código en MQL.


Obtención de la última versión GuiController.dll. Trabajo con GitHub

La biblioteca GuiController.dll se encuentra en los archivos adjuntados a este artículo. Hay que colocar esta librería a \MQL5\Libraries. Sin embargo, a menudo ocurre que la biblioteca sigue actualizándose y desarrollándose, mientras que al artículo se adjunta un archivo ya obsoleto. Para evitar esta y muchas otras situaciones semejantes, es mejor usar los sistemas del control de las versiones, cuando el código nuevo automáticamente queda disponible para los receptores. Nuestro proyecto no es una excepción. Para obtener de forma garantizada la última versión de GuiController, usamos el repositorio abierto de códigos abiertos GitHub.com. El código fuente de este controlador se guarda en este repositorio, basta con descargar su proyecto y compilar el controlador en la biblioteca dinámica. Si por algún motivo Usted no puede o no quiere usar este sistema, salte esta sección. En vez de eso, compile el archivo GuiController.dll a la carpeta MQL5\Libraries. 

Bien, si Usted tiene abierta la solución actual, ciérrelo usando el comando File -> Solution. Ahora vamos a la pestaña Team Explorer y hacemos clic en el enlace Clone. En el campo amarillo introduzca la dirección por la que se ubica el proyecto:

https://github.com/PublicMqlProjects/MtGuiController

En el siguiente campo, se indica la ruta local para guardar el proyecto descargado. Se selecciona automáticamente según el nombre del proyecto descargado, por eso, no lo modificamos. En la imagen de abajo, se muestran los valores que deben ser introducidos en Team Explorer:

Fig. 13. Conexión al repositorio remoto del código fuente

Ahora, cuando todo está listo, pulsamos el botón Clone. Dentro de un rato aparecerá el proyecto con la última versión de MtGuiController por la dirección especificada. Abralo usando el comando en el menú File -> Open -> Project/Solution. Una vez cargado y abierto el proyecto, es necesario compilarlo, para eso, pulse la tecla "F6" o seleccione en el menú el comando Build -> Build Solution. Busque el archivo compilado MtGuiController.dll en la carpeta MtGuiController\bin\debug y cópielo en la carpeta de las bibliotecas MetaTrader 5:  MQL5\Libraries.

Si por algún motivo no consigue obtener la última versión a través de github, no se desespere. En este caso, copie el controlador desde el archivo adjunto al artículo.


Integración de la primera aplicación con MetaTrader 5

Ahora que tenemos la primera aplicación y el controlador que va a transmitir las señales de la ventana gráfica en MetaTrader, nos queda ejecutar la parte final: es decir, escribir el programa en MQL en forma del Asesor Experto que va a recibir los eventos de parte de nuestra ventana a través del controlador. Crearemos el nuevo EA en MetaEditor con el nombre GuiMtController con el siguiente contenido:

//+------------------------------------------------------------------+
//|                                              GuiMtController.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- destroy timer
   GuiController::HideForm(assembly, "Form1");
   EventKillTimer();   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---
}
//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
  {
//---
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }
  }
//+------------------------------------------------------------------+

Recordaré que para compilar este código, la biblioteca MtGuiController.dll debe ubicarse en la carpeta MQL5\Libraries. Además, la ruta absoluta especificada en la cadena

string assembly = "С:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

debe ser corregida por la ruta de la ubicación real de su programa con la ventana.
Si todo está hecho correctamente el EA se compila, y después de su ejecución en el fondo de la ventana principal aparecerá nuestra ventana:

Fig. 14. El EA con la aplicación gráfica integrada en C#.

Nótese que si pulsa el botón button1, el EA mostrará en la pestaña Experts la frase "Click on element button1", señalizando sobre el evento del clic del ratón. 


Interacción del programa MQL con GuiController, modelo de evento

Para comprender cómo funciona nuestro programa, vamos a analizar detalladamente la lista anterior del código MQL.

Pues bien, lo primero que vemos es la directiva import y la cadena assembly:

#import  "MtGuiController.dll"
string assembly = "C:\\Users\\Bazil\\source\\repos\\GuiMT\\GuiMT\\bin\\Debug\\GuiMT.exe";

La primera cadena dice al compilador que van a usarse las llamadas a los métodos estáticos abiertos de la clase que se ubican en в MtGuiController.dll. Obsérvese que no es necesario especificar a qué métodos vamos a acceder en este ensamblado. De este trabajo se encargará el compilador en modo automático.

La segunda cadena contiene la ruta hacia el formulario que vamos a jestionar. Esta dirección debe corresponder a la ubicación real de su formulario.

Luego va el código estándar del procedimiento de la inicialización del EA OnInit:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- create timer
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, "Form1");
//---
   return(INIT_SUCCEEDED);
  }

Ahí se realiza la instalación de un temporizador de alta frecuencia y la primera llamada a uno de los métodos de nuestra clase. Describiremos la función del temporizador más tarde, ahora nos centraremos en la llamada ShowForm:

GuiController::ShowForm(assembly, "Form1");

En C# las funciones no pueden existir separadamente de las clases. De esta manera, cada función (método) tiene su clase donde está determinada. En MtGuiController.dll se determina la única clase GuiController. Incluye los métodos estáticos a través de los cuales se puede gestionar una u otra ventana. No hay otras clases en MtGuiController.dll, entonces toda la gestión se realiza de forma centralizada a través de esta clase. Es muy cómodo porque el usuario trabaja con la única interfaz de interacción y no busca la información necesario en un conjunto de definiciones diferentes.

Lo primero que se hace en el bloque de la inicialización es la llamada al método ShowForm. Como no es difícil de averiguar por su nombre, se encarga de iniciar el proceso de la visualización de los formularios. El primer parámetro del método establece la ruta absoluta hacia el archivo del formulario, el segundo define el nombre del formulario. Es que se puede definir varios formularios en el mismo archivo. Por eso, es necesario especificar qué formulario exactamente queremos iniciar en el archivo. En este caso, el nombre del formulario es el nombre de la clase del formulario que ha sido definido por Visual Studio por defecto para el formulario creado por nosotros. Si abrimos nuestro proyecto creado anteriormente en Visual Studio y abrimos el archivo Form1.Designer.cs en modo de visualización del código, veremos el nombre de la clase que necesitamos:

partial class Form1
{
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
        ...
}

Posteriormente, habrá que dar los nombres más razonable para las clase. En Visual Studio, es bastante fácil de hacerlo simplemente renombrando la clase y todas las referencia a ella. En este caso, también habrá que cambiar el segundo parámetro del método ShowForm.

La próxima función a tratar será OnTimer. Ella se invoca cada cinco segundos según las configuraciones del temporizador. Dentro de ella se encuentra el código más interesante de nuestro proyecto. El cuerpo de la función consiste del ciclo for que repasa los números de secuencia de los eventos:

for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == ClickOnElement)
         printf("Click on element " + el_name);
   }

Desde el punto de vista del controlador, un evento es cualquier acción realizada por el usuario en el formulario. Por ejemplo, cuando el usuario hace clic en un botón o introduce un texto en la ventada de entrada, el controlador recibe un evento correspondiente y lo coloca en la lista de los eventos. El número de los eventos en la lista se transmite por el método estático GuiController::EventsTotal() que puede ser llamado por nuestro programa MQL.

En Windows Forms, existe una enorme cantidad de eventos. Cada elemento (formulario, botón o ventana de texto) contiene varias decenas de eventos. No todos los eventos pueden ser procesados y no todos son necesarios. Por eso, GuiController procesa sólo los eventos más importantes. En la versión actual hay sólo tres eventos procesados. Son los siguientes eventos:

  • evento del clic en el botón;
  • evento del fin de la entrada del texto;
  • evento del scroll horizontal.

Posteriormente, se planea ampliar esta lista, pero para los propósitos de este artículo, la lista propuesta es suficiente.

Así, después de que se ocurra un evento soportado por nuestro GuiController, será procesado y añadido a la lista de los eventos. El procesamiento del evento consiste en la creación de los datos, al recibir los cuales, el programa MQL podría determinar relativamente fácil el tipo del evento y sus parámetros. Precisamente por eso el formato de datos de cada evento tiene una estructura muy semejante con el modelo de evento de la función OnChartEvent. Gracias a esta similaridad, el usuario que trabaja con GuiController no necesita estudiar el formato del nuevo modelo de evento. Naturalmente, el enfoque presentado tiene sus dificultades, por ejemplo, los eventos complicados como el scroll son extremamente difíciles de encajar en el formato propuesto, pero estos problemas se solucionan fácilmente a través de los recursos del lenguaje C# y su modelo de programación orientada a objetos bien desarrollado. Ahora, el modelo propuesto será suficiente para resolver estas tareas.

Cada vez que aparece un nuevo evento, sus datos se hacen disponibles a través de los tipos de referencia usando el método estático GuiController::GetEvent. Este método tiene el siguiente prototipo:

public static void GetEvent(int event_n, ref string el_name, ref int id, ref long lparam, ref double dparam, ref string sparam)

Describimos sus parámetros por orden: 

  • event-n — número de secuencia del evento a ser recibido. Gracias a la posibilidad de especificar el número de secuencia del evento es fácil de controlar nuevos eventos independientemente de su cantidad;
  • el_name — nombre del elemento que ha generado este evento;
  • id — tipo del evento.
  • lparam — valor entero que este evento tiene;
  • dparam — valor real que este evento tiene;
  • sparam — valor string que este evento tiene;

Como se puede apreciar, el modelo de evento GuiController parece mucho al modelo de evento OnChartEvent. Cualquier evento en GuiController siempre tiene el número de secuencia y la fuente (nombre del elemento) que ha generado este evento. Los demás parámetros son opcionales. Así, algunos eventos, como presionar un botón, no tienen parámetros adicionales (lparam, dparam, sparam), mientras que otros los contienen. Por ejemplo, el evento del fin del texto en el parámetro sparam contiene el texto que ha sido introducido en el campo por el usuario.

A continuación, se muestra una tabla que incluye los eventos y sus parámetros soportados actualmente:

Nombre del evento Identificador (id)  Parámetros
Exception  0 sparam - contiene un mensaje que causa las excepciones
ClickOnElement  1 -
TextChange  2 sparam - nuevo texto introducido por el usuario 
ScrollChange  3

lparam - nivel anterior del scroll

dparam - nivel actual del scroll

Ahora, cuando ya dominamos el modelo de evento en GuiController, podemos entender el código presentado dentro del ciclo for. Cadena:

GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);

Obtiene el evento del índice i. Si el tipo del evento corresponde al clic en el botón, el nombre del botón y mensaje sobre su pulsación se muestra en la consola del terminal.

if(id == ClickOnElement)
   printf("Click on element " + el_name);

Obsérvese que id se compara con la constante ClickOnElement, la que no está definida en ninguna parte del código MQL del programa. Realmente, esta constante forma parte de la enumeración definida en el propio GuiController en C#^

/// <summary>
/// Type of gui event
/// </summary>
public enum GuiEventType
{
    Exception,
    ClickOnElement,
    TextChange,
    ScrollChange
}

Como se puede ver, el compilador comprende las enumeraciones externas definidas en las bibliotecas Net y sabe trabajar con ellas. 

Otra vez observamos cómo se reciben los mensajes. Para eso, se usa el temporizador. Aunque en realidad, se puede usar cualquier otra función que se invoca periódicamente, por ejemplo, OnTick. No obstante, la periodicidad es muy difícil de controlar. No se puede decir con seguridad cuánto tiempo va a pasar entre dos llamadas consecutivas a OnTick.

Además, es imposible garantizar la periodicidad de la llamada incluso a OnTimer. Por ejemplo, en el Simulador de Estrategias, la frecuencia de la llamada a OnTimer se diferencia mucho de la que puede ser definida para esta función en modo de trabajo real. Todos estos efectos llevan al hecho de que entre dos llamadas a las funciones el usuario puede generar algunos eventos seguidamente. Por ejemplo, él puede pulsar el botón dos o tres veces antes de que el programa MQL tenga tiempo a reaccionar a la primera pulsación.

Precisamente, para estos propósitos, ha sido organizada la cola de los eventos. Cada evento entra en la lista, y luego espera a que sus parámetros serán extraídos por el programa MQL. Mientras que el programa memoriza el último número del evento usando la definición de la variable estática en la función, y la próxima vez que se inicie, recibe los eventos recién llegados. Precisamente por eso, el ciclo for tiene una signatura no estándar:

//-- El ciclo memoriza el último índice del evento y comienza a trabajar a partir de él durante el siguiente inicio de la función
for(static int i = 0; i < GuiController::EventsTotal(); i++)

Se puede recibir los eventos usando el método GuiController::GetEvent, o bien, enviarlos a través de GuiController::SendEvent. El segundo método se usa cuando necesitamos enviar algunos datos en la ventana y modificar su contenido. Tiene el mismo prototipo que GetEvent. La única diferencia es que no contiene el número de secuencia del evento, ya que en este caso, este número no tiene sentido. No vamos a detenernos en los detalles, pero mostraremos el trabajo con él en un ejemplo que será mostrado en la parte final del artículo.

El último método que nos queda por analizar es GuiController::HideForm.  Su signatura es semejante a ShowForm, y la acción es opuesta: este método oculta la ventana, y para eso, es necesario especificar su ubicación y el nombre.

Como puede ver, el código MQL que se encarga de visualizar el formulario y de analizar sus eventos será muy compacto y simple. En realidad, el código describe tres acciones simples.

  1. mostrar la ventana al iniciar el programa;
  2. recibir nuevos eventos de parte de la ventana;
  3. ocultar la ventana al salir del programa.

Sería difícil inventar un esquema más simple. Además, obsérvese bien el código del formulario que hemos creado. Aunque el formulario de la ventana contiene este mismo código, nosotros mismos no hemos escrito ni una cadena en C#. Todo el trabajo ha sido hecho por las herramientas de generación automática del código en Visual Studio y por el propio GuiController. Precisamente así se refleja el poder de la tecnología Net, ya que el objetivo final de los ambientes potentes es la sencillez.


“Debajo del capó de GuiController”

Si no domina muy bien C#, puede saltar esta sección. Será interesante para los que quieren entender cómo funciona GuiController y cómo se realiza el acceso a las aplicaciones Net individuales y aislados.

GuiController representa una clase compartida que se compone de dos partes: una parte parte estática y una parte de la instancia. La parte estática de la clase contiene los métodos estáticos abiertos para interactuar con MetaTrader. Esta parte de la clase implementa una interfaz entre MetaTrader 5 y el propio controlador. La segunda parte es de la instancia, y significa que los datos y los métodos de esta parte existen sólo al nivel de la instancia. Su tarea consiste en interactuar con los ensamblados Net independientes en los cuales se encuentran las ventanas gráficas. La ventana gráfica Windows Forms es una clase heredada de la clase base Form. De esta manera, Usted puede trabajar con cada ventana personalizada a un nivel más alto y abstracto de la clase Form.

Dentro de los conjuntos Net (como DLL o EXE) se encuentran los tipos Net que son abiertos por su naturaleza. Es bastante fácil acceder a ellos, a sus propiedades e incluso a los métodos. Se puede hacer eso usando un mecanismo que se llama en Net como reflexión o reflejo. Gracias a este mecanismo, cada archivo tipo DLL o EXE creado en Net, puede ser examinado en cuanto a la presencia del elemento que necesitamos. Precisamente, de eso se encarga la clase GuiController. Cuando la ruta absoluta hacia el ensamblado en Net se pasa en su método ShowForm, el controlador carga este ensamblado a través de un mecanismo especial, y después de eso encuentra la ventana gráfica que necesitamos visualizar. Mostraremos el código del método GetGuiController que ejecuta este trabajo:

/// <summary>
/// Create GuiController for windows form
/// </summary>
/// <param name="assembly_path">Path to assembly</param>
/// <param name="form_name">Windows Form's name</param>
/// <returns></returns>
private static GuiController GetGuiController(string assembly_path, string form_name)
{
    //-- Cargar el ensamblado especificado
    Assembly assembly = Assembly.LoadFile(assembly_path);
    //-- Encontrar el formulario especificado en él
    Form form = FindForm(assembly, form_name);
    //-- Asignar el controlador de control al formulario encontrado
    GuiController controller = new GuiController(assembly, form, m_global_events);
    //-- Devolver el controlador de control al método de llamada
    return controller;
}

Este procedimiento se asemeja a así llamado el grabber de recursos, es decir, un programa especial que permite extraer el contenido media como iconos e imágenes desde el código binario del programa.

La búsqueda del formulario se realiza a través de la reflexión. El método FindForm obtiene todos los tipos definidos en el ensamblado pasado. Entre estos tipos, él busca aquéllos cuyo tipo base corresponde al tipo Form. Si el nombre de este tipo encontrado también corresponde al del tipo necesario, se crea una instancia de este tipo que, a su vez, se devuelve como formulario:

/// <summary>
/// Find needed form
/// </summary>
/// <param name="assembly">Assembly</param>
/// <returns></returns>
private static Form FindForm(Assembly assembly, string form_name)
{
    Type[] types = assembly.GetTypes();
    foreach (Type type in types)
    {
        //assembly.CreateInstance()
        if (type.BaseType == typeof(Form) && type.Name == form_name)
        {
            object obj_form = type.Assembly.CreateInstance(type.FullName);
            return (Form)obj_form;
        }
    }
    throw new Exception("Form with name " + form_name + " in assembly " + assembly.FullName + "  not find");
}

El momento más emocionante es la creación de la propia aplicación y su ejecución. Es que, en realidad, a partir del conjunto externo de datos binarios, el programa real «toma la vida» y empieza a trabajar como un programa independiente.

Una vez creada la instancia, se le asigna un «controlador». El controlador es una parte de instancia de la clase GuiController que monitorea el formulario que le ha sido pasado. El controlador es responsable de monitorear y enviar los eventos para el formulario.

El inicio del formulario y su eliminación se realizan en un flujo paralelo. Gracias a eso, el flujo actual no se bloquea a la espera de que la operación actual se concluya. Imaginemos que hemos iniciado la ventana en el flujo actual. Puesto que la ventana continua funcionando, el proceso externo que la ha llamado se bloque a la espera del cierre de la ventana. El inicio de la ventana en un flojo separado soluciona este problema.

Los métodos correspondientes del controlador se encargan del inicio y eliminación de la ventana.

/// <summary>
/// El formulario personalizado llamado desde MetaTrader debe ejecutarse en modo asincónico,
/// para garantizar la respuesta de la interfaz.
/// </summary>
public static void ShowForm(string assembly_path, string form_name)
{
    try
    {
        GuiController controller = GetGuiController(assembly_path, form_name);
        string full_path = assembly_path + "/" + form_name;
        m_controllers.Add(full_path, controller);
        controller.RunForm();
    }
    catch(Exception e)
    {
        SendExceptionEvent(e);
    }
}
        
/// <summary>
/// Después de que el EA termine su trabajo con el formulario, hay que completar el proceso de su ejecución.
/// </summary>
public static void HideForm(string assembly_path, string form_name)
{
    try
    {
        string full_path = assembly_path + "/" + form_name;
        if (!m_controllers.ContainsKey(full_path))
            return;
        GuiController controller = m_controllers[full_path];
        controller.DisposeForm();
    }
    catch(Exception ex)
    {
        SendExceptionEvent(ex);
    }
}

La última cosa que tenemos que considerar respecto al controlador es el trabajo con los eventos. Cuando se usa la reflexión para crear un nuevo formulario, éste se pasa al método que se suscribe a sus eventos (mejor dicho, sólo a aquéllos que pueden ser procesados por el controler). Para estos propósitos se crea una correspondencia <elemento - lista de manipuladores de eventos>, donde cada manipulador de eventos está suscrito al evento que necesita: 

/// <summary>
/// Subscribe on supported events
/// </summary>
/// <param name="form">Windows form</param>
private void SubscribeOnElements(Form form)
{
    Dictionary<Type, List<HandlerControl>> types_and_events = new Dictionary<Type, List<HandlerControl>>();
    types_and_events.Add(typeof(VScrollBar), new List<HandlerControl>() { vscrol => ((VScrollBar)vscrol).Scroll += OnScroll });
    types_and_events.Add(typeof(Button), new List<HandlerControl>()  { button => ((Button)button).Click += OnClick });
    types_and_events.Add(typeof(Label), new List<HandlerControl>());
    types_and_events.Add(typeof(TextBox), new List<HandlerControl>() { text_box => text_box.LostFocus += OnLostFocus, text_box => text_box.KeyDown += OnKeyDown });
    foreach (Control control in form.Controls)
    {
        if (types_and_events.ContainsKey(control.GetType()))
        {
            types_and_events[control.GetType()].ForEach(el => el.Invoke(control));
            m_controls.Add(control.Name, control);
        }
    }
}

Cada formulario tiene una lista abierta de elementos que contiene. Repasando la lista de elementos, el método encuentra aquéllos que pueden ser soportados por el controlador, pudiendo suscribirse a los eventos necesarios. Si algún elemento del formulario no se soporta por el controlador, simplemente será ignorado. Los eventos asociados a él no serán integrados en el programa MQL, y el programa no podrá interactuar con este elemento.


Panel comercial a base de las interfaces gráficas

Ahora que hemos considerado todas las partes de nuestro sistema, ha llegado el momento para crear algo realmente útil. Haremos un análogo del panel comercial estándar que se encuentra en la esquina superior izquierda del gráfico:

Fig. 15 El panel comercial integrado en MetaTrader 5

Claro que nuestro panel va a incluir los elementos gráficos estándar de la ventana Windows, por tanto, va a tener un diseño más simple, pero la funcionalidad incorporada será la misma.

Se puede actuar de diferentes maneras, por ejemplo, empezar a crear este panel desde cero. No obstante, la descripción del trabajo del constructor visual será excesiva en este artículo. Por eso, simplemente cargamos el proyecto que contiene este panel en Windows. Hay dos maneras de hacer eso: copiar el proyecto desde el archivo y abrirlo en Windows, o descargarlo desde el repositorio remoto Git siguiendo este enlace: 

https://github.com/PublicMqlProjects/TradePanelForm

En este caso, el trabajo con git será el mismo como ha sido descrito en la sección correspondiente, por eso, no vamos a repetirlo.

Una vez cargado y abierto el proyecto, verá el siguiente formulario:

Fig. 16. Ventana TradePanel en el constructor visual Visual Studio

El proyecto contiene un modelo del panel comercial. En proyectos reales como este, necesitaremos acceder constantemente a los elementos ubicados en este formulario, así como enviarles los eventos. Para eso, habrá que acceder a cada elemento por su nombre. Por eso, los nombres de los elementos tienen que ser bien pensados y fáciles de recordar. Vamos a ver cómo se llaman los elementos que serán usados. Para ver el nombre de cada elemento, hay que encontrar le propiedad Name en la ventana Properties, seleccionando previamente este elemento. Por ejemplo, el botón con la etiqueta Buy tendrá el nombre ButtonBuy:

Fig. 17. Nombre del elemento en la ventana Properties.

Hay que distinguir el texto que figura en el elemento y el nombre del propio elemento. Son valores diferentes aunque tienen el sentido parecido.

Listemos los elementos que se encuentran en nuestro panel comercial:

Aunque los elementos utilizados son pocos, al combinarlos, obtenemos una interfaz bastante avanzada. Obsérvese que, igual como en el ejemplo anterior, nuestra solución no contiene ni una sola línea del código en C#. Todas las propiedades necesarias de los elementos se muestran en la ventana Properties, y la ubicación y el tamaño de los elementos se establecen a través de la técnica drag-n-drop, es decir, usando el ratón.


Integración de la ventana gráfica con el código del EA

Ahora, cuando nuestra ventana ya está preparada, hay que integrarla con el EA. Escribiremos la lógica comercial en el lenguaje de programación MQL, que va a interactuar con los elementos de nuestra interfaz. A continuación, se muestra el código completo del EA:

//+------------------------------------------------------------------+
//|                                                   TradePanel.mq5 |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#import  "MtGuiController.dll"
#include <Trade\Trade.mqh>
string assembly = "c:\\Users\\Bazil\\source\\repos\\TradePanel\\TradePanel\\bin\\Debug\\TradePanel.dll";
string FormName = "TradePanelForm";
double current_volume = 0.0;

//-- Trade module for executing orders
CTrade Trade;  
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//--- create timer, show window and set volume
   EventSetMillisecondTimer(200);
   GuiController::ShowForm(assembly, FormName);
   current_volume = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Dispose form
   EventKillTimer();
   GuiController::HideForm(assembly, FormName);
//---
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//--- refresh ask/bid   
   double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
   double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
   GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
   GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));
//---
}

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//--- get new events by timer
   for(static int i = 0; i < GuiController::EventsTotal(); i++)
   {
      int id;
      string el_name;
      long lparam;
      double dparam;
      string sparam;
      GuiController::GetEvent(i, el_name, id, lparam, dparam, sparam);
      if(id == TextChange && el_name == "CurrentVolume")
         TrySetNewVolume(sparam);
      else if(id == ScrollChange && el_name == "IncrementVol")
         OnIncrementVolume(lparam, dparam, sparam);
      else if(id == ClickOnElement)
         TryTradeOnClick(el_name);
   }
//---
}
//+------------------------------------------------------------------+
//| Validate volume                                                  |
//+------------------------------------------------------------------+
double ValidateVolume(double n_vol)
{
   double min_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);
   double max_vol = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MAX);
   //-- check min limit 
   if(n_vol < min_vol)
      return min_vol;
   //-- check max limit
   if(n_vol > max_vol)
      return max_vol;
   //-- normalize volume
   double vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double steps = MathRound(n_vol / vol_step);
   double corr_vol = NormalizeDouble(vol_step * steps, 2);
   return corr_vol;
}
//+------------------------------------------------------------------+
//| Set new current volume from a given text                         |
//+------------------------------------------------------------------+
bool TrySetNewVolume(string nstr_vol)
{
   double n_vol = StringToDouble(nstr_vol);
   current_volume = ValidateVolume(n_vol);
   string corr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, corr_vol);
   return true;
}
//+------------------------------------------------------------------+
//| Execute trade orders                                             |
//+------------------------------------------------------------------+
bool TryTradeOnClick(string el_name)
{
   if(el_name == "ButtonBuy")
      return Trade.Buy(current_volume);
   if(el_name == "ButtonSell")
      return Trade.Sell(current_volume);
   return false;
}
//+------------------------------------------------------------------+
//| Increment or decrement current volume                            |
//+------------------------------------------------------------------+
void OnIncrementVolume(long lparam, double dparam, string sparam)
{
   double vol_step = 0.0;
   //-- detect increment press
   if(dparam > lparam)
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect decrement press
   else if(dparam < lparam)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect increment press again
   else if(lparam == 0)
      vol_step = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   //-- detect decrement press again
   else
      vol_step = (-1.0) * SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_STEP);
   double n_vol = current_volume + vol_step;
   current_volume = ValidateVolume(n_vol);
   string nstr_vol = DoubleToString(current_volume, 2);
   GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, nstr_vol);
}
//+------------------------------------------------------------------+

El código presentado ejecuta la carga principal de trabajo de nuestro formulario. No obstante, cabe mencionar que toda esta funcionalidad está escrita en MQL5, dentro de las funciones-manipuladores de eventos estándar. Analizaremos el código presentado en más detalles.

Lo primero que se hace en la función OnInit es el establecimiento del temporizador con una precisión de 200 milisegundos. Luego, se muestra la ventana a través del método ShowForm:

GuiController::ShowForm(assembly, FormName);

Aquí, assembly (ensamblado) es la ruta hacia el ensamblado donde se encuentra nuestra ventana; FormName es el nombre de la clase de nuestro formulario.

Inmediatamente después del inicio de la ventana, establecemos el volumen mínimo en el campo de texto "CurrentVolume":

GuiController::SendEvent("CurrentVolume", TextChange, 0, 0.0, DoubleToString(current_volume, 2));

El volumen mínimo se calcula a base del entorno comercial actual a través de la función SymbolInfoDouble.

Al cerrar el EA, la ventana del formulario se cierra. Eso se hace en la función OnDeinit, usando el método GuiController::HideForm; 

La función OnTick reacciona al cambio del precio actual Ask/Bid. De esta manera, cuando se obtienen los precios actuales en esta función y se pasan en las etiquetas de texto correspondientes del formulario, el panel va a mostrar todos los cambios del precio actual.

//-- Obtener el precio Ask
double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
//-- Obtener Bid
double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
//-- Cambiar el texto en la etiqueta AskLabel por el precio actual Ask convertido en la cadena:
GuiController::SendEvent("AskLabel", TextChange, 0, 0.0, DoubleToString(ask, Digits()));
//-- Cambiar el texto en la etiqueta BidLabel por el precio actual Bid convertido en la cadena:
GuiController::SendEvent("BidLabel", TextChange, 0, 0.0, DoubleToString(bid, Digits()));

En la función OnTimer, se realiza el monitoreo de tres acciones que el usuario puede ejecutar con el formulario, a saber:

  • Introducir el nuevo volumen en la etiqueta de texto CurrentVolume;
  • Presionar el botón para aumentar o disminuir el paso del volumen realizado en forma del Scroll;
  • Hacer clic en el botón Buy o Sell, enviando la solicitud para realizar una transacción.

Dependiendo de la acción que ha sido realizada por el usuario, habrá un determinado conjunto de instrucciones. Todavía no hemos analizado el evento del clic de los botones del scroll que deben aumentar o disminuir el volumen actual por un paso mínimo permitido, por eso, vamos a analizar este asunto más detalladamente.

El evento del scroll en el modelo de evento actual se compone de dos parámetros: lparam y dparam. El primer parámetro contiene un valor condicional que caracteriza el desplazamiento del carro respecto al nivel cero antes de que el usuario haga clic en los botones de scroll. El segundo parámetro contiene el mismo valor, pero ya después de la pulsación. El scroll tiene un determinado rango del trabajo, por ejemplo, de 0 a 100. Por tanto, si el valor de lparam es igual a 30 y el valor de dparam es igual a 50, eso significa que el scroll vertical ha sido desplazado hacia abajo de 30 a 50% (el scroll horizontal se desplaza a la derecha por el mismo valor). En nuestro panel, no es necesario determinar exactamente dónde se encuentra el scroll. Sólo necesitamos saber qué botón ha sido pulsado. Para eso, hay que analizar el valor anterior y actual. Precisamente, de eso se encarga la función OnIncrementVolume. Una vez determinado el tipo de la pulsación del scroll, aumenta o disminuye el volumen actual por el paso mínimo del volumen, que se averigua a través de la función de sistema SystemInfoDouble.

Puede establecer un nuevo volumen comercial no sólo a través de las flechas del scroll. Además, se puede introducirlo directamente en la etiqueta de texto. Cuando el usuario introduce un nuevo carácter, windows forms genera un evento correspondiente. Sin embargo, es importante analizar la secuencia final, en vez de cada carácter por separado. Por eso, GuiController reacciona al clic de la tecla «Enter» o al cambio del foco de la etiqueta de texto. Es que precisamente estos eventos se consideran como el fin de la introducción del texto. Cuando ocurre uno de ellos, el texto formado se transfiere en la cola de eventos, que nuestro EA lee sucesivamente. Al llegar al evento del cambio del texto en la etiqueta, el programa MQL analiza su nuevo valor y establece un nuevo volumen según el especificado. El análisis se realiza a través de la función ValidateVolume. Ella controla los siguientes parámetros del volumen introducido:

  • El volumen debe estar entre el mínimo y el máximo permitido;
  • El valor del volumen debe ser múltiplo de su paso. Así, si el volumen es 0.01 del lote, y el usuario introduce 1.0234, será ajustado hasta 1.02;

Obsérvese que se puede controlar estos parámetros sólo a través del entorno comercial actual. De esta manera, todo el control de los valores introducidos por el usuario se realiza por el programa MQL, y no por el formulario creado por el usuario. 

Iniciaremos el panel comercial en el gráfico e intentaremos ejecutar unas transacciones con él:


Fig. 18. Funcionamiento del panel en tiempo real. 

Como puede ver, el panel comercial funciona y ejecuta perfectamente sus funciones.


Funcionamiento de interfaces gráficas en el Simulador de Estrategias

El Simulador de Estrategias en MetaTrader 5 tiene una serie de particularidades que deben ser consideradas por el desarrollador de interfaces gráficas en el lenguaje de programación MQL. La principal particularidad consiste en el hecho de que la función del procesamiento de los eventos gráficos OnChartEvent no se invoca en absoluto. Esta particularidad parece lógica, por que el formulario gráfico supone el trabajo con el usuario en tiempo real. Sin embargo, hay un tipo de paneles que sería muy interesante implementar precisamente en el Simulador. Se trata de llamados players comerciales a través de los cuales los usuarios podrían testear sus estrategias comerciales en modo manual. Por ejemplo, el Simulador de Estrategias genera los precios de mercado actuales en el modo de aceleración, mientras que el usuario pulsa los botones de compra o venta, simulando así sus acciones comerciales en el historial. El TradePanel creado por nosotros pertenece precisamente a este tipo de paneles. A pesar de su sencillez, puede ser un buen player comercial simple, con una funcionalidad necesaria. 

Pero vamos a pensar en cómo nuestro panel va a funcionar en el Simulador de Estrategias de MetaTrader 5. La ventana gráfica del panel TradePanel existe en forma de un ensamblado .Net independiente. Por tanto, no depende de ninguna manera del entorno actual de MetaTrader 5, ni del propio terminal. Hablando estrictamente, se puede ejecutarlo desde cualquier programa, y los ensamblados ubicados en el contenedor exe pueden ser iniciados por el usuario.

De esta manera, nuestro programa no tiene que llamar a OnChartEvent. Es más, para actualizar los datos en la ventana y obtener nuevas órdenes de los usuarios se puede usar cualquier función-manipulador de eventos que se ejecuta regularmente en el Simulador. OnTick y OnTimer pertenecen a estas funciones. Nuestro panel opera a través de ellas. Así, nuestro panel también va a funcionar bien en el Simulador, pues ha sido desarrollado para el funcionamiento en tiempo real. No hay que hacer modificaciones para eso. Comprobaremos esta afirmación ejecutando nuestro panel en el Simulador, y realizaremos algunas transacciones en él:


Fig. 19. Funcionamiento del panel en modo de simulación en el Simulador de EStrategias.

Resulta que el desarrollo de las interfaces gráficas a través de C# nos da un plus inesperado al trabajar en el Simulador de Estrategias. Para una aplicación Windows Forms, el Simulador de Estrategias no impone ningunas restricciones. Las particularidades de trabajo del modelo de evento no afectan el panel ni las maneras de trabajar con él. Tampoco es necesario adaptar el programa para trabajar en el Simulador. 


Conclusión

En este artículo, ha sido propuesto un enfoque para que Usted pueda crear fácil y rápidamente su propio formulario visual. Este enfoque divide la aplicación gráfica en tres partes independientes: programa MQL, adaptador GuiController y el panel visual. Las tres partes de la aplicación no dependen una de otra. El programa MQL trabaja en el entorno comercial MetaTrader y realiza las funciones comerciales y analíticas a base de los parámetros que recibe desde el panel a través de GuiController. El propio GuiController es un programa independiente que no requiere modificaciones al modificar el formulario o sus elementos. Finalmente, el panel gráfico se crea por el usuario, a través de las herramientas visuales avanzadas de Visual Studio. Debido a eso, para crear un formulario bastante complejo, tal vez, no sea necesario el conocimiento del lenguaje de programación C#.

Los formularios creados no dependen de ninguna manera del programa que los ejecuta. Puede ser el propio MetaTrader 5 o su Simulador de Estrategias, es decir, en ambos casos, la ventana va a funciona de acuerdo con la lógica incorporada en ella. Aparte de eso, la ventana tampoco depende de la función en la que ha sido invocada. Gracias a eso, funcionan igualmente bien tanto en MetaTrader 5, como en su Simulador de Estrategias, no hay diferencia si un EA o un indicador trabaja con la ventana. En todos los casos, el comportamiento de la ventana será el mismo.

Debido a las particularidades mencionadas, el enfoque propuesto seguramente encontrará sus seguidores. En primer lugar, será popular entre aquéllos que necesitan hacer un formularios semiautomático: motor o player comercial, panel de datos u otro panel visual en forma de una interfaz gráfica estándar. El enfoque también será de agrado a los que no dominan muy bien la programación. Para crear su formulario, será suficiente conocer MQL5 en términos generales. 

El enfoque propuesto, como cualquier otro, tiene sus desventajas. La principal de ellas es que no se puede trabajar en el Market, ya que la llamada a las DLL de terceros está prohibida. Segundo, la ejecución de las DLL o EXE desconocidos puede ser peligrosa, porque estos módulos pueden contener funciones dañinas. No obstante, este problema se soluciona con la transparencia del proyecto. El usuario sabe que el programa creado por él a priori no contiene otros elementos salvo los establecidos por él mismo, y GuiController es un proyecto público con el código fuente abierto. Otra desventaja consiste en que la interacción entre programas es un proceso bastante complicado. Tal interacción puede causar un bloqueo o un cierre inesperado del programa. Mucho depende del desarrollador que va a diseñar la interfaz y la interacción con ella. Este sistema se estropea con más facilidad que un sistema sólido, escrito en MQL5 puro.

Actualmente, el proyecto se encuentra en la fase del desarrollo. Muchos usuarios no han encontrado los controles necesarios en este artículo, mientras que las posibilidades actuales de la interacción con las ventanas gráficas, digamos francamente, son muy limitadas por ahora. Todo eso es cierto. No obstante, la tarea principal del artículo ha sido cumplida. Hemos demostrado que crear windows forms e interactuar con ellos es más fácil de lo que parece. Si este material resulta ser útil para la comunidad MQL, seguiremos desarrollando el tema.