English Русский 中文 Deutsch 日本語 Português
preview
Desarrollando un EA de trading desde cero (Parte 16): Acceso a los datos en la Web (II)

Desarrollando un EA de trading desde cero (Parte 16): Acceso a los datos en la Web (II)

MetaTrader 5Integración | 4 julio 2022, 16:11
372 0
Daniel Jose
Daniel Jose

1.0 Introducción

En el artículo anterior Desarrollando un EA de trading desde cero (Parte 15): Acceso a datos en la Web (I) he presentado toda la lógica y las ideas detrás de los métodos de uso de la plataforma MetaTrader 5 para acceder a los datos de mercado presentes en la Web, en páginas especializadas que proveen información sobre el mercado.

Allí mostré los detalles de cómo acceder, encontrar y adquirir la información de esos sitios para utilizarla en la plataforma, pero no todo se trata sólo de eso, ya que sólo capturar los datos no tiene mucho sentido, la parte más interesante de hecho es saber cómo tomar estos datos dentro de la plataforma y utilizarlos en un EA; la forma de hacerlo no es tan obvia, o mejor dicho, no es tan simple como para poder hacerlo sin realmente conocer y entender todas las características que están presentes en MetaTrader 5.


2.0 Planificación y implementación

El caso es que si no leyeron y entendieron el artículo anterior, por favor léanlo y traten de entender todos los conceptos allí presentes, porque aquí la cosa será para gente grande, exploraremos una enorme serie de cosas, pasaremos por problemas y llegaremos finalmente a una solución que es a la vez hermosa por no decir brillante, ya que usaremos MetaTrader 5 de una manera muy poco explorada, lo digo porque tuve dificultades para encontrar referencias al uso de algunas características presentes en la plataforma, pero aquí trataré de explicar cómo usar uno de esos recursos.

Así que prepárate y manos a la obra.


2.1 Acceso a los datos de la Web a través del EA

Esta es la parte más interesante que se puede hacer dentro de este sistema, a pesar de ser algo sencillo de realizar, es de lejos lo más peligroso si se planifica mal, peligroso porque puede dejar al EA atascado esperando la devolución de alguna respuesta del servidor, aunque sea por unos instantes.

La lógica aquí se presenta en la imagen siguiente:

Veamos que el EA se comunica directamente con el servidor Web que contiene la información que queremos capturar. A continuación tenemos un ejemplo de código completo que se ejecuta exactamente así.

#property copyright "Daniel Jose"
#property version "1.00"
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

Si te fijas, verás que el código es exactamente el último código que se creó en el artículo anterior, sólo que ese código es ahora parte del EA, y fue adaptado para esto, es decir, si la cosa ya funcionaba allí, entonces funcionará aquí también. Bueno, la diferencia es que aquí en el EA, generé una condición, esta condición implica que el código se ejecutará cada segundo, o sea que el EA hará una petición al servidor Web que le estamos indicando, cada segundo, y esperará la respuesta sólo para luego presentar los datos capturados y volver a realizar otras funciones internas, y el ciclo se repetirá durante toda la vida del EA, el resultado de la ejecución se puede ver a continuación.


Bien, pero a pesar de hacerlo de esta manera, de nuevo no aconsejo tal práctica, por el riesgo de que el EA se quede atascado esperando la respuesta del servidor, aunque sea por unos instantes, esto puede comprometer el sistema de trading de la plataforma y el propio EA, pero si de hecho estás interesado sólo en estudiar el método, este sistema te bastará para aprender mucho.

Pero si tienes un servidor local que hará el enrutamiento entre la información de la Web y la plataforma, puede ser que este método sea suficiente, ya que si el sistema hace una petición, lo máximo que ocurrirá es que el servidor local aún no tenga información y responda rápidamente, esto te ahorrará los siguientes pasos que tenemos que dar.

Bueno, ahora vamos a ver otra forma de realizar este tipo de tareas al menos un poco más seguras, ya que utilizaremos el sistema de thread propio de MetaTrader 5 para conseguir al menos un mínimo de seguridad y evitar que el EA esté sujeto a las condiciones del servidor Web remoto, pudiendo bloquearse durante unos instantes a la espera de que el servidor remoto responda a su petición, pero al mismo tiempo daremos condiciones para que el EA sepa lo que está pasando, pudiendo recoger información de la Web.


2.2 Creación de un canal de comunicación

Una forma mejor y a la vez sencilla de conseguir la captura de datos en la Web y utilizar dichos datos en un EA es a través de un canal, pero aunque funciona, no es lo más adecuado en algunos casos, ya que existen limitaciones en el uso de dichos canales, no obstante al menos el EA podrá acceder a la información recogida en la Web sin el riesgo de esperar a que el servidor remoto responda.

Mira que he dicho más arriba que la forma más sencilla de resolver el problema sería enrutar los datos, lo que significa que crearías un servidor local que descargara los datos por ti y los pusiera a disposición de la plataforma MetaTrader 5, pero esto requiere un cierto grado de conocimiento y capacidad de procesamiento, además de complicar de forma inútil la gran mayoría de los casos, sin embargo podemos utilizar las funcionalidades que proporciona MetaTrader 5 para crear un canal muy similar, pero mucho más sencillo que hacer el enrutamiento a través de un servidor local.

La figura siguiente muestra cómo promoveremos dicho canal.

Se crea mediante el uso de un objeto, ten en cuenta que el EA buscará dentro del objeto la información que el script publicó allí, para entender cómo funciona realmente necesitamos 3 códigos, y se pueden ver a continuación, en su totalidad, uno será el EA, otro será un encabezado, que contiene el objeto, y finalmente un script.

#property copyright "Daniel Jose"
#property description "Testing Inner Channel"
#property version "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        Print(GetInfoInnerChannel());
}
//+------------------------------------------------------------------+

El siguiente código es la cabecera que se va a utilizar. Observa que el objeto se declara aquí para que sea compartido entre EA y el script.

//+------------------------------------------------------------------+
//|                                          Canal Intra Process.mqh |
//|                                                      Daniel Jose |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_NameObjectChannel   "Inner Channel Info WEB"
//+------------------------------------------------------------------+
void CreateInnerChannel(void)
{
        long id;
        
        ObjectCreate(id = ChartID(), def_NameObjectChannel, OBJ_LABEL, 0, 0, 0);
        ObjectSetInteger(id, def_NameObjectChannel, OBJPROP_COLOR, clrNONE);
}
//+------------------------------------------------------------------+
void RemoveInnerChannel(void)
{
        ObjectDelete(ChartID(), def_NameObjectChannel);
}
//+------------------------------------------------------------------+
inline void SetInfoInnerChannel(string szArg)
{
        ObjectSetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT, szArg);
}
//+------------------------------------------------------------------+
inline string GetInfoInnerChannel(void)
{
        return ObjectGetString(ChartID(), def_NameObjectChannel, OBJPROP_TEXT);
}
//+------------------------------------------------------------------+

Y finalmente tenemos el script que usaremos, este sustituirá la creación de un servidor local, de hecho hace el trabajo del servidor.

#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        CreateInnerChannel();
        while (!IsStopped())
        {
                SetInfoInnerChannel(GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D));
                Sleep(200);
        }
        RemoveInnerChannel();
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); (!_StopFlag) && (c0 < c1); c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); (!_StopFlag) && (c0 < c1); c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; (!_StopFlag) && (charResultPage[counter + iInfo] == 0x20); counter++);
        for (;(!_StopFlag) && (charResultPage[counter + iInfo] != cLimit); counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return (_StopFlag ? "" : szInfo);
}
//+------------------------------------------------------------------+

Veamos lo siguiente: hay un objeto que es visto por el EA y creado por el script, el hecho de hacerlo así es porque el EA y el script pueden convivir en el mismo gráfico, entonces este objeto será nuestro canal de comunicación entre el EA y el Script. El EA será el cliente y el script será el servidor, mientras que el objeto será el canal de comunicación entre ellos. De esta manera el script capturará los valores en el servidor Web remoto y pondrá el valor encontrado en el objeto. El EA verá de vez en cuando qué valor hay en el objeto, si es que el objeto existe, ya que si el script no se está ejecutando el objeto no debería estar disponible, pero los momentos en los que el EA mire el valor en el objeto no perturbarán en absoluto la ejecución del mismo, si el script se bloquea porque está esperando la respuesta del servidor remoto, para el EA esto es insignificante, seguirá su trabajo independientemente de lo que esté haciendo el script.

Pero aunque esta solución es hermosa no es perfecta, hay un problema con esta solución, y el problema es el script.

Para entenderlo, veamos el siguiente vídeo, pero presta atención a cada uno de los detalles del mismo.



Todo funciona de maravilla, y es algo esperable, ya que este tipo de solución es muy utilizada en programación cuando estamos desarrollando programas de tipo cliente-servidor, en los que no queremos que uno bloquee al otro, o sea utilizamos un canal para realizar la comunicación entre los procesos, muchas veces cuando se está en el mismo entorno, este canal se creará con el uso de la memoria, creando una región aislada, pero compartida y visible tanto para el cliente como para el servidor. El servidor pone los datos allí y el cliente va a la misma región y recoge los datos disponibles, es decir, uno es independiente del otro, pero ambos estarán relacionados.

La idea aquí es utilizar este mismo principio, pero la forma en que funciona el script genera un problema, cuando cambiamos el timeframe, el script se cierra, incluso si utilizamos un bucle infinito es cerrado por MetaTrader 5, pues bien cuando esto sucede tendríamos que reiniciar el script poniéndolo de nuevo en el gráfico, pero si el cambio de timeframe es constante, esto se convertirá en un problema, además de ser un tormento estar cada hora poniendo de nuevo el script en el gráfico.

Esto puede ocurrir además de que se puede acabar olvidando de mirar si el script está en el gráfico o no, y por lo tanto se acabará utilizando una información errónea, ya que tal y como está codificado el EA no tenemos seguridad de que se nos avise si el script no está en el gráfico, esto se podría solucionar mejorando el código del EA para poder testear si el script está o no en el gráfico, esta tarea no es difícil de implementar, un simple test de cuál fue la hora de la última publicación del script en el objeto ya soluciona el problema.

Pero tenemos una forma de crear una solución mucho más adecuada, al menos en algunos casos, a decir verdad es una solución casi perfecta, y usaremos este mismo concepto presentado anteriormente, solo que en lugar del script usaremos un servicio.


2.3 Creación de un servicio

Esta es una solución extrema, pero como el script tiene el problema de cerrarse en cada cambio de horario, tenemos que usar otro método, si bien al resolver un problema terminaremos teniendo otro, es bueno que sepas cuales son los recursos posibles y como usar cada uno, pero lo principal es conocer las limitaciones que presenta cada una de las soluciones y así tratar de encontrar un camino intermedio, que nos permita resolver el problema de la mejor manera posible.

La programación es así, al tratar de resolver un problema generar uno nuevo.

La intención aquí es crear algo como la imagen de abajo:

Aunque parezca sencillo, los recursos implicados son muy poco explorados en general, por lo que intentaré profundizar en los detalles implicados para ayudar a quien quiera saber más sobre el trabajo con estos recursos.


2.3.1 Acceso a las variables globales

Esta parte está tan poco explorada que al principio incluso me planteé crear una dll sólo para dar soporte a esta característica, pero buscando en la documentación de MQL5 encontré una referencia a ella. Bien, el tema es que necesitamos acceder o crear un punto común entre el servicio y el EA, en el caso cuando usamos un script, este punto era el objeto, pero cuando usamos un servicio no podemos hacer lo mismo, la solución sería usar una variable externa, pero al intentar hacer esto el funcionamiento no fue el esperado, para más detalles lee variables externas, allí se explica como proceder.

Así que la idea casi falleció, y volví a pensar en usar una dll, pero quería explorar MetaTrader 5 y MQL5, así que mirando el terminal encontré algo, que se puede ver en la imagen de abajo:

         

Esto es lo que buscaba, aquí añadí una variable para poder comprobar como se podía configurar la cosa, y podemos usar solo valores double, puedes pensar que esto es un problema, aunque sí en varios casos por limitarnos, será más que suficiente cuando queramos transmitir mensajes cortos, que es nuestro caso, debes pensar que el tipo double es en realidad una cadena corta de 8 caracteres, entonces da para pasar valores o mensajes cortos entre programas.

Bueno la primera parte del problema está resuelta, MetaTrader 5 nos proporciona los medios para hacer un canal sin necesidad de una dll sólo para esto, pero ahora tenemos otro problema: ¿Cómo acceder a estas variables a través del programa? ¿Es posible crearlas dentro del programa, ya sea EA, script, indicador o servicio? ¿O estamos obligados a utilizar sólo las declaradas en el terminal?

Estas preguntas son muy importantes para que realmente podamos utilizar el recurso, si no es posible utilizarlo a través de programas, tendríamos que pasar por el camino de las dlls, pero sí es posible, hacerlo a través de programas, para más detalles ver el siguiente tema: Variables globales del terminal.


2.3.2 Utilización de una variable de terminal para el intercambio de información

Bien, ahora que tenemos lo básico, necesitamos crear algo sencillo para poder probar y entender cómo funcionará en la práctica el proceso de uso de las variables de terminal.

Para ello se crearon los siguientes códigos, todos ellos completos a continuación. El primero es el archivo de cabecera:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#define def_GlobalNameChannel   "InnerChannel"
//+------------------------------------------------------------------+

Aquí simplemente definimos un nombre para la variable global de terminal común entre los dos procesos que se ejecutarán en el terminal gráfico.

El siguiente código se muestra a continuación, que es el servicio que se ejecutará.

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        double count = 0;
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                GlobalVariableSet(def_GlobalNameChannel, count);
                count += 1.0;
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+

Su funcionamiento es muy sencillo, comprobará si la variable ya está definida, esto lo hace la función GlobalVariableCheck, si la variable aún no existe, será creada temporalmente por la función GlobalVariableTemp y luego recibirá un valor por la función . En otras palabras, estamos probando, creando y registrando información, el servicio está funcionando como un servidor, así como lo hizo el script, sólo que aún no estamos accediendo a un sitio Web, vamos a tomarlo con calma, tenemos que entender cómo funciona el sistema.

El siguiente paso es crear el programa cliente, que en este caso será EA, se puede ver a continuación:

#property copyright "Daniel Jose"
#property description "Testing internal channel\nvia terminal global variable"
#property version "1.03"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
int OnInit()
{
        EventSetTimer(1);
        
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick()
{
}
//+------------------------------------------------------------------+
void OnTimer()
{
        double value;
        if (GlobalVariableCheck(def_GlobalNameChannel))
        {
                GlobalVariableGet(def_GlobalNameChannel, value);
                Print(value);           
        }
}
//+------------------------------------------------------------------+

Aquí también el código es muy simple, cada segundo más o menos, el EA comprobará si la variable existe, si lo hace, leerá el valor usando GlobalVariableGet y luego imprimirá este valor en la terminal.

Bueno, tal vez muchos no sepan cómo hacerlo, así que vamos a tomarlo con calma, primero vamos a poner el servicio a funcionar, esto se hace de la siguiente manera:

Pero también puede ocurrir otro tipo de escenario, en el que el servicio se haya detenido y lo vuelvas a poner en marcha, en cuyo caso procederás como sigue:

Después de hacer esto, comprobamos las variables del terminal y obtenemos el siguiente resultado:

Observa que el sistema aparentemente funciona, pero ahora tenemos que poner el EA en el gráfico, tener la lectura de los valores y, así, confirmar la comunicación a través del canal. Así que después de poner el EA en el gráfico obtenemos el siguiente resultado:

Y ya está, el sistema funciona como queremos, quizás no te estés dando cuenta, pero estamos teniendo el siguiente modelo que se puede ver en la imagen de abajo, que es un formato típico de cliente-servidor, y eso es exactamente lo que queremos hacer y estamos buscando la forma de implementarlo, ya que tenemos las ventajas que he mencionado antes sobre este modelo.

Ahora sólo nos falta añadir el sistema de lectura y captura de valores de la Web en el servicio, y ya tendremos el modelo final para las pruebas, esta parte es bastante sencilla, porque lo que haremos es coger el código que estamos utilizando desde el principio y añadirlo al servicio. Así que para nuestra prueba sólo tendremos que modificar el archivo del servidor, para leer el valor del sitio Web y publicar este valor para que el cliente lo lea, por lo que el nuevo código del servicio se ve a continuación, en su totalidad.

#property service
#property copyright "Daniel Jose"
#property version   "1.00"
//+------------------------------------------------------------------+
#include <Inner Channel.mqh>
//+------------------------------------------------------------------+
void OnStart()
{
        string szRet;
        
        while (!IsStopped())
        {
                if (!GlobalVariableCheck(def_GlobalNameChannel)) GlobalVariableTemp(def_GlobalNameChannel);
                szRet = GetDataURL("https://tradingeconomics.com/stocks", 100, "<!doctype html>", 2, "INDU:IND", 172783, 173474, 0x0D);
                GlobalVariableSet(def_GlobalNameChannel, StringToDouble(szRet));
                Sleep(1000);
        }
}
//+------------------------------------------------------------------+
string GetDataURL(const string url, const int timeout, const string szTest, int iTest, const string szFind, int iPos, int iInfo, char cLimit)
{
        string  headers, szInfo = "";
        char    post[], charResultPage[];
        int     counter;
   
        if (WebRequest("GET", url, NULL, NULL, timeout, post, 0, charResultPage, headers) == -1) return "Bad";
        for (int c0 = 0, c1 = StringLen(szTest); c0 < c1; c0++) if (szTest[c0] != charResultPage[iTest + c0]) return "Failed";
        for (int c0 = 0, c1 = StringLen(szFind); c0 < c1; c0++) if (szFind[c0] != charResultPage[iPos + c0]) return "Error";
        for (counter = 0; charResultPage[counter + iInfo] == 0x20; counter++);
        for (;charResultPage[counter + iInfo] != cLimit; counter++) szInfo += CharToString(charResultPage[counter + iInfo]);
        
        return szInfo;
}
//+------------------------------------------------------------------+

Ahora tenemos un sistema que funciona como se muestra en la siguiente figura:

Es decir, ya está casi completo, y cuando lo ejecutamos obtenemos los siguientes resultados, pero ahora el cambio de marco temporal ya no será un problema.



Conclusión

Aquí hemos explorado algunas características que aún están poco exploradas en la plataforma MetaTrader 5, una de ellas son los canales de comunicación, pero aún no estamos sacando todo el provecho que esta característica nos puede dar realmente, podemos ir más allá de lo mostrado aquí, y esto lo veremos en el próximo artículo. Pero de una forma u otra, todo lo visto hasta ahora en esta serie nos muestra lo mucho que podemos hacer dentro de la plataforma MetaTrader 5, sólo hay que elegir el camino y seguirlo hasta conseguir los resultados que buscamos, pero hay que tener conocimiento de las limitaciones, ventajas y riesgos que conlleva cada uno de los posibles caminos.


Traducción del portugués realizada por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/pt/articles/10442

Archivos adjuntos |
Script_e_EA.zip (3.03 KB)
Serviso_e_EA.zip (2.39 KB)
Desarrollando un EA comercial desde cero (Parte 17): Acceso a los datos en la web (III) Desarrollando un EA comercial desde cero (Parte 17): Acceso a los datos en la web (III)
En este artículo continuaremos a aprender cómo obtener datos de la web para utilizarlos en un EA. Así que pongamos manos a la obra, o más bien a empezar a codificar un sistema alternativo.
Desarrollando un EA comercial desde cero (Parte 15): Acceso a los datos en la web (I) Desarrollando un EA comercial desde cero (Parte 15): Acceso a los datos en la web (I)
Cómo acceder a los datos en la web dentro de MetaTrader 5. En la web tenemos varios sitios y lugares en los que una gran y vasta cantidad de información está disponible y accesible para aquellos que saben dónde buscar y cómo utilizar mejor esta información.
Desarrollando un EA comercial desde cero (Parte 18): Un nuevo sistema de órdenes (I) Desarrollando un EA comercial desde cero (Parte 18): Un nuevo sistema de órdenes (I)
Esta es la primera parte del nuevo sistema de ordenes. Desde que este EA comenzó a tener su desarrollo documentado en artículos, ha sufrido varios cambios y mejoras, si bien ha mantenido el mismo modelo de sistema de órdenes en el gráfico.
DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control «Panel», parámetro AutoSize DoEasy. Elementos de control (Parte 5): Objeto básico WinForms, control «Panel», parámetro AutoSize
En este artículo, crearemos un objeto básico para todos los objetos de la biblioteca WinForms y comenzaremos a implementar la propiedad AutoSize del objeto WinForms "Panel", es decir, el cambio automático del tamaño para que se ajuste a su contenido interno.