English Русский 中文 Deutsch 日本語 Português
preview
Cómo construir un EA que opere automáticamente (Parte 10): Automatización (II)

Cómo construir un EA que opere automáticamente (Parte 10): Automatización (II)

MetaTrader 5Trading | 18 abril 2023, 09:55
439 1
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Cómo construir un EA que opere automáticamente (Parte 09): Automatización (I), mostré cómo crear un sistema breakeven y trailing stop, que utiliza dos modos diferentes. Uno qué emplea la línea de stop en posiciones de tipo OCO, y otro que utilizá una orden pendiente para servir como punto de stop. Como expliqué allí, estos métodos tienen pros y contras.

Incluso utilizando un sistema de automatización bastante simple y proporcionando instrucciones detalladas sobre cómo generar cálculos, ese EA no está realmente automatizado. Aunque ya tiene el primer nivel de automatización, todavía se opera manualmente o, para ser más precisos, semimanualmente. La parte responsable de mover la línea o la orden stop la realiza el propio EA con base en la configuración definida por el usuario.

En este artículo, veremos cómo añadir otro nivel de automatización al EA para que pueda permanecer en el gráfico en el que se utiliza todo el tiempo. No te preocupes, esto no significa que enviará órdenes o abrirá posiciones continuamente durante 24 horas. De hecho, he mostrado cómo crear una forma de controlar el volumen de operaciones del sistema para evitar que el EA haga overtrading.

El tipo de automatización que mostraré aquí es algo que muchas plataformas ofrecen como una forma de permitir que el operador no necesite estar comerciando todo el tiempo. Tú estableces un tiempo en el que puedes enviar órdenes o abrir posiciones, y dentro de ese tiempo establecido básica y teóricamente puedes utilizar la plataforma.

Sin embargo, es importante señalar que aunque el uso de la plataforma durante esas horas sea teóricamente posible, puedes desactivar el sistema en cualquier momento. Cada persona sabe cuándo es necesario tomarse un descanso, pero, a diferencia de un operador humano, un EA no actúa de esa manera.

Debo hacer hincapié en que siempre debes revisar cualquier adición o eliminación del EA para que pueda trabajar dentro de una metodología determinada. Sin embargo, nunca debes dejar que el EA opere sin supervisión. Este es un consejo que debes tener siempre presente: NUNCA DEJES QUE UN EA OPERE SIN SUPERVISIÓN.

Pero, ¿cómo creamos el control de horarios en la práctica?

Hay varias formas y maneras de hacerlo. La implementación de este tipo de control depende mucho más de cómo el programador quiera insertarlo en el código del EA que de la programación que se deba realizar. Como la intención de estos artículos es mostrar una forma de crear un EA automático de la forma más sencilla posible, crearemos un código que se pueda eliminar fácilmente. Al mismo tiempo, puedes utilizarlo en un EA manual, si quieres controlar tu propio método de trading. Pero esto es algo a nivel personal de cada uno. 


Planificación

Lo que hace interesante al sistema de desarrollo es que a cada persona se le ocurren diferentes formas y medios de promover un determinado concepto, o incluso la forma en que funcionará la cosa en sí. Incluso cuando los resultados son bastante similares o cercanos entre sí, la forma de implementación puede variar.

Sin embargo, aquí en MQL5, echo de menos una forma de programación orientada a objetos presente en C++, la llamada herencia múltiple. Si se aplica mal esta metodología, es posible tener serios problemas al utilizar la herencia múltiple, ya que es necesario tener cierto cuidado durante la programación para hacer un uso correcto de la misma. Incluso sin esta característica de C++, todavía es posible generar algunos tipos de código, manteniendo las cosas dentro del sistema de herencia y aplicando la ocultación del código.

Para entender la diferencia entre usar herencia múltiple y no usarla, observa las imágenes de abajo. Observa que la clase C_ControlOfTime es el nombre de la clase que usaremos para controlar la franja horaria permitida para que EA se ejecute.

Figura 01

Figura 01 - Modelado mediante herencia múltiple

Figura 02

Figura 02 - Modelado sin herencia múltiple

Nota que la diferencia entre la figura 01 y la figura 02 es que en la primera figura, la clase C_Manager recibirá los métodos implementados en ambas clases C_Orders y C_ControlOfTime a través de herencia, lo que hace que la clase C_Manager crezca rápidamente. Como no podemos hacer eso en MQL5, necesitamos usar otro enfoque, el cual se muestra en la figura 02, en la que la clase C_ControlOfTime heredara de la clase C_Orders.

Pero, ¿por qué no hacer lo contrario? La razón es no quiero que el EA tenga acceso a la clase C_Orders directamente. Sin embargo, sí necesitará acceso a lo implementado en la clase C_ControlOfTime. Lo bueno de programar es que muchas veces podemos seguir caminos diferentes y, al final, tener algo que funciona muy parecido a algo que otro programador puede haber creado.

Lo que muestro es sólo una de las muchas posibilidades para obtener el mismo resultado. Lo que realmente importa es el resultado. Cómo lo consigas importa poco, siempre y cuando hagas las cosas de forma que no comprometas la integridad del código que estás creando. Siéntete libre de crear tu propia técnica y forma de hacer las cosas, porque la programación nos permite hacerlo.


Veamos los últimos detalles antes de la implementación

Una vez definida la idea de utilizar el modelado de clases visto en la figura 02, pasamos a la segunda etapa de la planificación, para crear la clase de control de franjas horarias.

Ahora necesitamos definir cómo se hará la definición del horario, es decir, de qué manera un operador puede definir fácilmente este horario. Una forma sería utilizar un fichero que contenga los datos de las franjas horarias plausibles de ser adoptadas por el EA.

Sin embargo, el uso de un fichero es una cuestión bastante controvertida para este tipo de cosas. Al utilizar un fichero, podemos acabar dando más libertad al operador para definir varias franjas horarias dentro del mismo día, lo que puede ser razonable en algunos casos, pero puede acabar complicando enormemente una tarea sencilla para la mayoría de los operadores, que es hacer que el EA se ejecute sólo dentro de una única franja horaria.

Por otro lado, la implementación de las definiciones de horarios dentro del archivo puede acabar complicando una tarea sencilla para la mayoría de los operadores. Esto se debe a que en algunos casos el EA permanecerá en ejecución sólo dentro de una única franja horaria.

Sin embargo, esto es más común de lo que parece la mayoría de las veces. Así que podemos hacer algo mejor que nos permita mantenernos en un término medio. La plataforma MetaTrader 5 nos permite guardar y cargar la configuración que queramos en archivos. Así que todo lo que tenemos que hacer es crear una configuración para una franja horaria determinada, por ejemplo una configuración para ser utilizada por la mañana y otra para la tarde. Cuando el EA esté bloqueado por el sistema de control de franjas horarias, ya sea porque el operador se va a tomar un descanso, éste puede subir directamente a la plataforma MetaTrader 5 un fichero de configuración que será mantenido por la propia plataforma. Esto nos ahorra el trabajo de crear ficheros de configuración sólo para este fin.

Figura 03

Figura 03 - Área de configuración del EA

En la figura 03, puedes ver el sistema de configuración del EA. Una vez configurado, se puede guardar esta configuración utilizando el botón < GUARDAR >. Cuando quieras cargar alguna configuración guardada, utilizarás el botón < ABRIR >. De esta forma, se puede tener un sistema en el que necesitaremos implementar mucho menos código. Al mismo tiempo, tendremos una mayor fiabilidad en el mismo. Ya que parte del trabajo lo realizará el propio MetaTrader 5, ahorrándonos enormemente el trabajo de pruebas para asegurarnos de que todo funcionará correctamente.

Un último detalle a definir es en relación a las órdenes o posiciones fuera de la franja horaria. ¿Qué hacer con ellas? Bien, el EA no podrá enviar pedidos para abrir una posición o colocar una orden fuera de la franja. Sin embargo, puede manipular una orden o cerrar una posición si éstas ya están en el servidor fuera de la franja. Pero si lo deseas, también puedes bloquearlo, modificar o mantener esta política. Pero esta elección te la dejo a ti, según tu propia política de operación.


El nacimiento de la clase C_ControlOfTime

Lo primero que hay que hacer, dentro del fichero de cabecera C_ControlOfTime.mqh, será crear el código que se ve a continuación:

#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_ControlOfTime : protected C_Orders
{
        private :
                struct st_00
                {
                        datetime Init,
                                 End;
                }m_InfoCtrl[SATURDAY + 1];
//+------------------------------------------------------------------+
        public  :

//... Procedimientos de la clase ...

};

Fíjate en que estamos añadiendo el fichero de cabecera C_Orders.mqh con el propósito de acceder a la clase C_Orders. De esta forma, la clase C_ControlOfTime heredará de la clase C_Orders, utilizando el método protegido. Ya he explicado las consecuencias de utilizar este tipo de herencia en otro artículo de esta misma serie, que es éste  Cómo construir un EA que opere automáticamente (Parte 05): disparadores manuales (II).

Ahora, dentro de la cláusula privada de la clase control, añadimos una estructura que se utilizará como array con 7 elementos. Pero, ¿por qué no definir 7 en lugar de utilizar una declaración compleja? El hecho es que el valor SATURDAY esta definido internamente en el lenguaje MQL5 como una enumeración, ENUM_DAY_OF_WEEK, lo que hace mas claro el uso de los días de la semana para acceder al array.

Esto se llama aumento a nivel de lenguaje, ya que para los que leen el código, una palabra es más expresiva que un valor numérico. Dentro de esta estructura, sólo tenemos dos elementos: uno indica el punto de inicio de las operaciones del EA y el otro indica el punto en el que el EA ya no puede operar, ambos de tipo datetime.

Con el array definido, podemos pasar a nuestro primer código dentro de la clase: el constructor de la clase, que se puede ver a continuación:

                C_ControlOfTime(const ulong magic)
                        :C_Orders(magic)
                        {
                                ResetLastError();
                                for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++) ZeroMemory(m_InfoCtrl[c0]);
                        }

Para mucha gente, el siguiente código puede parecer extraño, pero no es más que una forma de programación un poco más avanzada de lo habitual. El hecho es que la programación que se considera de alto nivel no tiene nada que ver con mi experiencia en programación, como he mencionado antes:

«Es más fácil entender código que utiliza un lenguaje que se asemeja al lenguaje natural, que código que utiliza valores numéricos»

Así que el bucle en este constructor casi parece explicar lo que está haciendo, y eso es lo que define si un código es de alto nivel o no.

Creo que no tendrás problemas para entender este constructor, ya que en momentos anteriores expliqué cómo funciona un código en el constructor. Si tienes dudas, te sugiero que veas los artículos anteriores de esta misma serie. Lo único que difiere aquí es el bucle, donde definimos una variable que comenzará con el valor "Domingo" (SUNDAY) y terminará en "Sábado" (SATURDAY), pero por lo demás no hay nada muy complicado.

El detalle es que este bucle funciona correctamente sólo porque en la enumeración se establece que "domingo" sea el primer día de la semana y "sábado" el último. Sin embargo, si "lunes" (MONDAY) se estableciera como el primer día, este bucle fallaría y generaría un error al ejecutar el código en la plataforma MetaTrader 5. Por lo tanto, es importante tener cuidado al utilizar código de alto nivel, ya que si la configuración es incorrecta, el código puede generar varios fallos de ejecución (RUN-TIME).

Una vez hecho esto, podemos pasar al siguiente procedimiento de nuestra clase:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

Este merece un poco más de explicación, ya que no es tan sencillo para los que están aprendiendo o tienen muy poca experiencia. Lo sé porque yo también he estado en su lugar, querido lector. Así que, para explicar con más detalle y claridad el código anterior, vamos a dividirlo en partes más pequeñas.

if (_LastError != ERR_SUCCESS) return;
if ((index > SATURDAY) || (index < SUNDAY)) return;

Siempre que sea posible, deberías verificar el sistema en busca de errores, ya que este código es extremadamente sensible y propenso a errores, dada la naturaleza de lo que estamos tratando. Comenzamos comprobando si hubo algún fallo antes de llamar a esta rutina en particular. Si hubo un fallo, el procedimiento terminará inmediatamente.

También es necesaria una segunda comprobación para evitar fallos por azar. Si por alguna razón realizamos una llamada cuyo día de la semana, a juicio del procesador, no es más que un número, y este número no se encuentra en el rango comprendido entre el sábado (SATURDAY) y el domingo (SUNDAY), no continuaremos ejecutando ninguna línea más dentro de la función.

Una vez que este primer paso ha sido aceptado perfectamente y el código ha pasado esas primeras pruebas, vamos a traducir el contenido pasado por el autor de llamada:

if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
{
        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
}

Aquí tenemos un código muy interesante de observar y entender, sobre todo para aquellos que están empezando a aprender a programar. Aquí usamos la función StringSplit para "romper" la información que recibimos del autor de llamada en dos partes. El carácter que indica dónde se romperá la información es el signo menos (-) y lo que queremos y esperamos son dos partes de información. Si llega un número diferente, se considerará un error, como se puede ver en los ejemplos siguientes:

12:34 - - 18:34 <-- Esto es un error
12:32  -  18:34 <-- Información correcta
12:32     18:34 <-- Esto es un error
       -  18:34 <-- Esto es un error
12:34  -        <-- Esto es un error

El contenido interno de la información no le importa a la función StringSplit, ya que sólo "romperá" la información basándose en el delimitador elegido. Esta rutina es extremadamente útil en varias ocasiones y es importante estudiarla con calma, ya que ayuda a separar la información dentro de una cadena.

Después de obtener las dos piezas de información, utilizamos la función StringToTime para traducirlas a un código en formato de fecha.

Aquí hay un detalle importante: como sólo se trata de valores de horas y minutos y no nos preocupamos de introducir la fecha concreta, es posible que tú introduzcas una fecha, pero será ignorada por el sistema. Sólo necesitas informar la hora deseada. La función StringToTime añadirá automáticamente la fecha actual por nosotros. Esto no es un problema en sí mismo, a menos que lo veamos más adelante.

Sin embargo, si no deseas que se añada la fecha actual, utilizamos la factorización para eliminar esta información. Es importante recordar que si se deseas mantener la fecha añadida por la función StringToTime, basta con eliminar esta factorización indicada.

Después de convertir los valores, realizamos una primera prueba que es muy importante para evitar futuros problemas. Comprobamos si la hora de inicio es menor o igual que la hora final especificada. Si esta prueba tiene éxito, no tendremos que preocuparnos por este problema en el futuro. Sin embargo, si la prueba falla, informaremos al usuario de que los valores no son apropiados.

Además, realizamos otra prueba para tratar el error de ejecución que se produce cuando no se introduce ninguna fecha. Este tipo de error es muy molesto, pero lo gestionamos adecuadamente. Si se detecta, eliminamos la indicación de que se ha producido el error, puesto que ya sabemos de antemano que no se va a introducir ninguna fecha y que esto generaría este error.

Nota importante: Durante las pruebas que realices con el EA, si observas que se produce un error de tiempo de ejecución y que éste no vulnera la integridad del EA hasta el punto de hacerlo inestable o inseguro, añade una prueba similar a ésta para minimizar la generación de este tipo de mensajes de error. Algunos errores de tiempo de ejecución pueden ignorarse porque son de baja gravedad y no comprometen el trabajo del EA. Sin embargo, otros deben ser manejados de forma diferente, porque hacen que el EA deje de continuar en el gráfico. Esto se llama robustez obligada, porque eres consciente de que el fallo puede ocurrir, pero aún así sabes que no compromete el sistema.

Después de convertir los valores, tendremos el siguiente fragmento de código:

if ((_LastError != ERR_SUCCESS) || (!bLocal))
{
        Print("Error in the declaration of the time of day: ", EnumToString(index));
        ExpertRemove();
}

Observa algo importante aquí: si el sistema genera algún error grave durante la conversión de los valores, esta variable constante tendrá un valor distinto de ERR_SUCCESS. Esto indica que no podemos confiar en los datos que fueron utilizados o introducidos por el usuario. La misma condición se aplica si esta variable tiene un valor falso, lo que indica que algo en el código falló en algún momento. En cualquier caso, informaremos al operador mediante un mensaje impreso en el terminal y solicitaremos el cierre del EA en la plataforma MT5.

De esta forma, creo que ha quedado claro el funcionamiento de la función, ya que se ha explicado detalladamente. Sin embargo, aún no ha terminado. Tenemos otra cuestión que explorar:

virtual const bool CtrlTimeIsPassed(void) final
                        {
                                datetime dt;
                                MqlDateTime mdt;
                                
                                TimeToStruct(TimeLocal(), mdt);
                                dt = (mdt.hour * 3600) + (mdt.min * 60);
                                return ((m_InfoCtrl[mdt.day_of_week].Init <= dt) && (m_InfoCtrl[mdt.day_of_week].End >= dt));
                        }

Es importante no aceptar algo como cierto antes de experimentar con posibles variaciones que puedan tener los mismos resultados, pero de una forma considerablemente más sencilla. Esto puede parecer desconcertante, pero es un enfoque valioso para afrontar los retos y encontrar soluciones más eficaces.

Antes de entrar en materia, entendamos la función anterior. Ésta captura la hora y la fecha locales y convierte estos valores en una estructura para poder descomponer los datos. Después, se crea un valor que sólo contiene la hora y los minutos, que son los valores utilizados en la rutina anterior. Este es el problema mencionado anteriormente: si no se elimina el valor de la fecha en la rutina de conversión, hay que añadirlo aquí. Con los valores en la mano, se comprueba si están dentro del rango definido para el día. Si lo están, el resultado es verdadero; en caso contrario, el resultado es falso.

Ahora, surge otra pregunta: ¿por qué hacer todo este trabajo para comprobar si la hora local está dentro de un rango de valores? ¿No sería más sencillo capturar la hora local y comprobarla directamente? Efectivamente, sería más sencillo. Pero hay un problema: el día de la semana.

Si estuviéramos definiendo el rango de uso de EA basándonos sólo en el día actual, bastaría con capturar sólo la hora local. Sin embargo, estamos estableciendo valores para los siete días de la semana. La forma más sencilla de saber en qué día de la semana estamos es utilizar exactamente el trabajo que se hizo en la rutina anterior.

Así que todo depende de cómo se está implementando el sistema. Si la implementación es sencilla, las rutinas y procedimientos necesarios serán considerablemente más simples. Si el sistema se está creando para un uso más amplio, las rutinas y procedimientos serán considerablemente más complicados.

Por eso es importante pensar y analizar antes de empezar a codificar. De lo contrario, podrías acabar en un callejón sin salida, en el que un código recién construido hace que los códigos antiguos sean incapaces de satisfacer las demandas. Tendrás que cambiar los códigos antiguos y, antes de que te des cuenta, estarás confundido por la cantidad de cambios necesarios. Puede que tengas que descartarlo todo y empezar de cero.

Antes de cambiar de tema, me gustaría hacer hincapié en un detalle importante de la rutina anterior. Si quieres que el EA esté encendido las 24 horas del día, incluso con la plataforma funcionando todo el tiempo, puedes tener miedo de que el EA, al final del día, no sepa cuándo empezar a operar de nuevo. Pero esto no sucederá, porque en cada llamada de la función, ésta reevalúa toda la situación y utiliza el día actual de la semana para verificar si el EA puede o no realizar alguna operación.

Por ejemplo, supongamos que le dices al EA que puede operar el lunes de 04:15 a 22:50 y el martes de 3:15 a 20:45. Puedes simplemente dejar que opere de lunes a martes. Tan pronto como el día pase de lunes a martes, empezará a verificar automáticamente qué franja horaria está permitida para operar el martes. Por eso se decidió utilizar el modo semana en lugar de un ajuste basado en el día actual.

Puede que algunos detalles de esta clase hayan pasado desapercibidos, pero he dejado hablar de ello ahora para no complicar demasiado la explicación del funcionamiento de las rutinas. Pero analicemos con calma un detalle muy importante a la hora de tratar la herencia de funciones. Si observas, verás que tanto la rutina SetInfoCtrl como la rutina CtrlTimeIsPassed tienen unas declaraciones muy extrañas. ¿Por qué tienen estas declaraciones? ¿Cuál es el sentido o la utilidad de hacerlas? Veámoslas más de cerca a continuación:

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
//+------------------------------------------------------------------+
virtual const bool CtrlTimeIsPassed(void) final
//+------------------------------------------------------------------+

Cada palabra de las declaraciones anteriores tiene una razón y una necesidad específicas, no se pone nada sólo para embellecer el código -aunque algunos programadores lo hacen a menudo, pero ésa es otra historia-.

Lo que realmente importa en estas declaraciones es la palabra reservada "final". Es crucial para la existencia de la palabra "virtual" en la declaración. Cuando creas una clase, puedes trabajar en ella de muchas maneras, anulando métodos, cambiando la forma en que una función de la clase padre se ejecutará en una clase hija, creando nuevas formas basadas en un trabajo más primitivo, etc. Al añadir la palabra "final" en la declaración de una función o procedimiento en una clase, le estás diciendo al compilador que una clase hija no puede modificar de ninguna manera - ni siquiera anulando - la función o procedimiento heredado que está escrito en la clase padre y al que se le ha dado la palabra "final" en la declaración.

Es importante recalcarlo de nuevo: al utilizar la palabra "final" en la declaración, se le está diciendo al compilador que una clase hija no puede modificar de ninguna manera -ni siquiera sobrescribiendo- la función o procedimiento heredado que esté escrito en la clase padre y haya recibido la palabra "final" en la declaración.

De esta forma, garantizamos que si cualquier clase que herede esta clase aquí, con sus métodos y variables, intenta modificar estos métodos, este intento se considerará un error y el programa no compilará. La palabra "virtual" sirve exactamente para promover la posibilidad de cambio, pero la palabra "final" impide tal cambio. Al final, la palabra final es la que dicta la regla. Por lo tanto, siempre que quiera asegurarse de que un procedimiento o función no sufre modificaciones indebidas dentro de una clase hija, detenga esta posibilidad de cambio añadiendo una declaración como la que se muestra arriba.

Esto le evitará muchos quebraderos de cabeza si trabaja con muchas clases y un nivel profundo de herencia.


Creación de la clase C_ControlOfTime en la clase C_Manager y su uso en el EA

Ahora, por fin, podemos enlazar la clase C_ControlOfTime con la clase C_Manager para que EA pueda tener un nuevo tipo de parámetro de trabajo. Para ello, hemos realizado el siguiente cambio en el código de la clase C_Manager:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
#include "C_ControlOfTime.mqh"
//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
class C_Manager : public C_ControlOfTime

Se han eliminado las líneas subrayadas del código original y se han añadido nuevas líneas en su lugar. Es importante tener en cuenta que la clase C_Manager hereda la clase C_ControlOfTime de forma pública. Simplemente estamos incorporando todo lo que hay en la clase C_ControlOfTime a la clase C_Manager. Esto aumenta las capacidades de la clase C_Manager sin aumentar su código. Si ya no queremos las capacidades añadidas al heredar la clase C_ControlOfTime, todo lo que tenemos que hacer es eliminar esa herencia y cualquier punto de referencia que esté dentro de la clase C_Manager. Así de sencillo.

De esta forma, no modificamos el nivel de fiabilidad de la clase C_Manager, que seguirá funcionando con la máxima seguridad, estabilidad y robustez como si nada. Si la clase C_ControlOfTime empieza a causar inestabilidades en la clase C_Manager, basta con eliminar la clase C_ControlOfTime y la clase C_Manager volverá a ser estable.

¿Ves por qué me complace tanto crear todo en forma de clase, en lugar de funciones dispersas? Las cosas se hacen más grandes y mejores rápidamente, siempre con el mayor nivel posible de estabilidad y fiabilidad que el lenguaje puede proporcionar.

Ahora que los constructores necesitan ser referenciados de alguna manera, veamos a continuación cómo será el nuevo constructor de la clase:

//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_ControlOfTime(magic),
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;

// ... Resto del código interno del constructor ....

                        }
//+------------------------------------------------------------------+

Al igual que con la declaración, aquí ocurre lo mismo. Se ha eliminado la línea subrayada y se ha añadido una nueva línea para pasar los datos al constructor de la clase C_ControlOfTime. Es este constructor el que referenciará al constructor de la clase C_Ordenes, para que éste reciba el número mágico necesario para enviar las órdenes.

Para concluir este tema, los verdaderos puntos de uso del control del tiempo se encuentran en las siguientes funciones:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_TicketPending > 0) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!CtrlTimeIsPassed()) return;
                                if ((m_StaticLeverage >= def_MAX_LEVERAGE) || (m_bAccountHedging && (m_Position.Ticket > 0))) return;
                                tmp = C_Orders::ToMarket(type, (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceStop), (def_ORDER_FINISH ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Ninguna otra función dentro de EA o de la clase C_Manager está utilizando realmente una franja horaria. Pero si lo deseas, puedes añadir este control añadiendo exactamente la línea resaltada en las funciones que necesitan ser controladas, como el disparador de trailing stop. Si no desea utilizar el control en algunas de esas funciones, sólo tiene que eliminar la línea que se ha añadido. ¿Te gusta la forma en que se planificó el código? Pero todo esto no funciona sin otro punto, que se ve en el código del EA.

En el código EA, los únicos cambios realmente necesarios se pueden ver a continuación:

#include <Generic Auto Trader\C_Manager.mqh>
#include <Generic Auto Trader\C_Mouse.mqh>
//+------------------------------------------------------------------+
C_Manager *manager;
C_Mouse  *mouse;
//+------------------------------------------------------------------+
input int       user01   = 1;                   //Leverage Factor
input double    user02   = 100;                 //Take Profit ( FINANCE )
input double    user03   = 75;                  //Stop Loss ( FINANCE )
input bool      user04   = true;                //Day Trade ?
input color     user05  = clrBlack;             //Price Line Color
input color     user06  = clrForestGreen;       //Take Line Color 
input color     user07  = clrFireBrick;         //Stop Line Color
input double    user08  = 35;                   //BreakEven ( FINANCE )
//+------------------------------------------------------------------+
input string    user90  = "00:00 - 00:00";      //Sunday
input string    user91  = "09:05 - 17:35";      //Monday
input string    user92  = "10:05 - 16:50";      //Tuesday
input string    user93  = "09:45 - 13:38";      //Wednesday
input string    user94  = "11:07 - 15:00";      //Thursday
input string    user95  = "12:55 - 16:25";      //Friday
input string    user96  = "00:00 - 00:00";      //Saturday
//+------------------------------------------------------------------+
#define def_MAGIC_NUMBER 987654321
//+------------------------------------------------------------------+
int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)
        {
                switch (c0)
                {
                        case SUNDAY     : szInfo = user90; break;
                        case MONDAY     : szInfo = user91; break;
                        case TUESDAY    : szInfo = user92; break;
                        case WEDNESDAY  : szInfo = user93; break;
                        case THURSDAY   : szInfo = user94; break;
                        case FRIDAY     : szInfo = user95; break;
                        case SATURDAY   : szInfo = user96; break;
                }
                (*manager).SetInfoCtrl(c0, szInfo);
        }
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}

Sólo fue necesario añadir los puntos donde habrá interacción con el usuario del código. Este bucle capturará los datos y los volcará al sistema para que puedan ser utilizados por la clase C_Manager, controlando así el tiempo de funcionamiento del EA. En esta parte del código, tenemos un comportamiento muy sencillo de entender y que no requiere mayor explicación.


Conclusión

En este artículo, he mostrado cómo agregar un control para permitir que el EA opere dentro de una franja horaria específica. Aunque el sistema es bastante sencillo, es necesaria una última explicación.

Si introduces una hora superior a 24 horas en el sistema de interacción, se corregirá a la hora más cercana a 24 horas. Por ejemplo, si deseas que el EA opere hasta las 22:59 (si trabajas en el mercado Forex), debes introducir exactamente este valor. Si escribes 25:59, el sistema de conversión lo cambiará a 23:59. Aunque este error tipográfico es inusual, puede ocurrir.

No se ha añadido ninguna verificación extra para analizarlo porque es poco frecuente, pero quería comentarlo aquí y proponer una prueba para verificar esta condición, que se puede ver a continuación. Pero no te preocupes, el código adjunto ya contiene estos cambios.

virtual void SetInfoCtrl(const ENUM_DAY_OF_WEEK index, const string szArg) final
                        {
                                string szRes[], sz1[];
                                bool bLocal;
                                
                                if (_LastError != ERR_SUCCESS) return;
                                if ((index > SATURDAY) || (index < SUNDAY)) return;
                                if (bLocal = (StringSplit(szArg, '-', szRes) == 2))
                                {
                                        m_InfoCtrl[index].Init = (StringToTime(szRes[0]) % 86400);
                                        m_InfoCtrl[index].End = (StringToTime(szRes[1]) % 86400);
                                        bLocal = (m_InfoCtrl[index].Init <= m_InfoCtrl[index].End);
                                        for (char c0 = 0; (c0 <= 1) && (bLocal); c0++)
                                                if (bLocal = (StringSplit(szRes[0], ':', sz1) == 2))
                                                        bLocal = (StringToInteger(sz1[0]) <= 23) && (StringToInteger(sz1[1]) <= 59);
                                        if (_LastError == ERR_WRONG_STRING_DATE) ResetLastError();
                                }
                                if ((_LastError != ERR_SUCCESS) || (!bLocal))
                                {
                                        Print("Error in the declaration of the time of day: ", EnumToString(index));
                                        ExpertRemove();
                                }
                        }

Ha sido necesario añadir una nueva variable y el código resaltado, que descompone los datos de la hora para comprobar si la hora introducida está por debajo del valor máximo posible dentro de un intervalo de 24 horas.


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

Archivos adjuntos |
Santiago Fallas
Santiago Fallas | 4 may. 2023 en 23:14
Ok muchas gracias
Cómo construir un EA que opere automáticamente (Parte 11): Automatización (III) Cómo construir un EA que opere automáticamente (Parte 11): Automatización (III)
Un sistema automatizado sin seguridad no tendrá éxito. Sin embargo, la seguridad no se consigue sin entender bien algunas cosas. En este artículo, comprenderemos por qué es tan difícil lograr la máxima seguridad en los sistemas automatizados.
Algoritmos de optimización de la población: Algoritmo de murciélago (Bat algorithm - BA) Algoritmos de optimización de la población: Algoritmo de murciélago (Bat algorithm - BA)
Hoy analizaremos el algoritmo de murciélago (Bat algorithm - BA), que posee una sorprendente convergencia en funciones suaves.
Experimentos con redes neuronales (Parte 3): Uso práctico Experimentos con redes neuronales (Parte 3): Uso práctico
Las redes neuronales lo son todo. Vamos a comprobar en la práctica si esto es así. MetaTrader 5 como herramienta autosuficiente para el uso de redes neuronales en el trading. Una explicación sencilla.
Cómo construir un EA que opere automáticamente (Parte 09): Automatización (I) Cómo construir un EA que opere automáticamente (Parte 09): Automatización (I)
Aunque la creación de un Expert Advisor automático no es una tarea muy complicada, sin los conocimientos adecuados, se puede acabar cometiendo muchos errores. En este artículo, vamos a ver cómo construir el primer nivel de automatización, que es crear el disparador para activar breakeven y trailing stop.