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

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

MetaTrader 5Trading | 20 abril 2023, 09:35
909 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Cómo construir un EA que opere automáticamente (Parte 11): Automatización (III), expliqué cómo es posible crear un sistema sólido capaz de minimizar los fallos y lagunas que pueden afectar a cualquier programa.

A veces veo a la gente decir que la probabilidad de que algo ocurra es de una entre 500.000, pero aunque sea un porcentaje bajo, sigue existiendo la posibilidad de que ocurra. Si eres consciente de ello, ¿por qué no crear formas de minimizar el daño o los efectos secundarios en caso de que ocurra? ¿Por qué ignorar la posibilidad de que ocurra un suceso y no corregirlo o prevenirlo de alguna manera simplemente porque la probabilidad es baja?

Si has estado siguiendo esta secuencia de artículos sobre cómo automatizar un EA, te habrás dado cuenta de que crear un EA para uso manual es relativamente rápido y sencillo. Sin embargo, para un EA 100% automatizado, la situación no es tan fácil. En ningún momento sugerí que tendríamos o estaríamos creando un sistema 100% infalible que pudiera ser utilizado sin supervisión. De hecho, creo que ha quedado claro que muchos se engañan a sí mismos pensando que pueden encender un EA automatizado y dejarlo funcionando sin entender lo que se está ejecutando.

Cuando hablamos de un EA 100% automatizado, la cosa se pone bastante seria y complicada, sobre todo por los errores de ejecución y la necesidad de trabajar en tiempo real. Estas dos cosas juntas, más el hecho de que el sistema puede presentar lagunas o fallos, hace que el trabajo sea extremadamente agotador para aquellos que están supervisando el EA durante su funcionamiento.

Como programador, es importante analizar algunos puntos clave que pueden crear problemas, incluso cuando todo parece funcionar perfectamente. Esto no significa buscar problemas donde no existen, sino ser un profesional cuidadoso y atento que busca fallos en un sistema que, a primera vista, parece no tener problemas.

En la fase actual de desarrollo, nuestro estimado EA para uso manual y semiautomático (utilizando breakeven y trailing stop) no presenta un fallo devastador como Blockbuster (personaje del universo DC). Sin embargo, si lo utilizamos de forma 100% automática, la situación cambia y surge el riesgo de un fallo grave y potencialmente peligroso.

En el artículo anterior, abordé esta cuestión y pedí a los lectores que identificaran dónde estaba el fallo y cómo podía desencadenarse para causar problemas, por lo que aún no sería posible automatizar nuestro EA al 100%. Si la respuesta fue "no", está bien. Créeme, no todo el mundo puede identificar el fallo con sólo mirar el código y utilizarlo de forma manual o semiautomática. Sin embargo, si intenta automatizar el código, tendrás serios problemas. Este fallo es sin duda el más fácil de identificar, pero no tan sencillo de solucionar para su uso en un EA 100% automatizado.

Para entender de qué se trata, dividiremos el tema en tópicos, lo que hará más fácil identificar cómo algo aparentemente sin importancia puede causar grandes problemas.


Entendiendo el problema

El problema empieza cuando creamos un límite máximo de volumen que el EA puede operar diariamente. Es importante no confundir este límite diario con el volumen de cada operación - por el momento, sólo estamos considerando el límite máximo diario.

Para ilustrarlo, supongamos que el volumen es 100 veces el volumen mínimo. Esto significa que el EA podrá ejecutar tantas operaciones como sea posible hasta que se alcance este límite o hasta que la última regla añadida a la clase C_Manager impida que el volumen supere 100.

Ahora, vamos a entender cómo sucede esto en la práctica. Para ello, analicemos el código que permite operar:

inline bool IsPossible(const bool IsPending)
                        {
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((IsPending) && (m_TicketPending > 0)) return false;
                                if (m_StaticLeverage + m_InfosManager.Leverage > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

El fragmento de código anterior impide que se supere el volumen, pero ¿cómo ocurre esto realmente?

Supongamos que tú o el comerciante ha configurado el EA para trabajar con un apalancamiento de 3 veces el volumen mínimo requerido, y el volumen máximo, establecido en el código del EA (durante la compilación), es de 100 veces - como se explica en artículos anteriores. Después de 33 operaciones, el EA habrá alcanzado 99 veces el volumen mínimo negociado, lo que significa que sólo puede realizar una operación más. Sin embargo, debido a la línea resaltada en el código anterior, el operador debe modificar el volumen a 1 vez para alcanzar el límite máximo. De lo contrario, el EA no podrá ejecutar la operación.

La idea es limitar el volumen máximo que el EA puede operar, con el fin de evitar pérdidas - y esta debe ser siempre la principal y más importante preocupación, estableciendo un valor máximo superior a un parámetro previamente estipulado. Incluso si el EA no abre una posición con un volumen muy por encima del estipulado, puede haber pérdidas, pero éstas se pueden controlar de alguna manera.

Tal vez pienses: "No veo ningún fallo en este código". De hecho, no hay fallos en este código, y las funciones que lo utilizan (que se pueden ver más abajo) consiguen limitar el volumen negociado por el EA al límite máximo estipulado.

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                if (!IsPossible(true)) return;
                                m_TicketPending = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                
                                if (!IsPossible(false)) return;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                        }
//+------------------------------------------------------------------+

Pero entonces, ¿dónde está el fallo? De hecho, no es algo sencillo de entender. Para entender el problema, hay que pensar en cómo puede operar el EA, no sólo en cuanto al envío de órdenes, pues la clase C_Manager ya impide que el EA las dispare. El problema surge cuando combinamos diferentes tipos de órdenes, para obtener un disparador de fallos ESTREPITOSOS. Hay algunas formas de limitar o evitar la presencia de este tipo de disparador, y las exploraremos a continuación:

  • Elegir un tipo o modelo de envío de órdenes, basándose únicamente en órdenes de mercado o pendientes. Esta solución es adecuada para algunos modelos de negociación automatizada, pero nos limita a algunos tipos de sistemas comerciales.
  • Contabilizar de forma más profunda lo que ya se considerará posición y lo que hará cambios en la posición (es decir, las órdenes pendientes). Ésta es la mejor opción, pero complica la programación al tener que hacer suposiciones sobre el movimiento.
  • Utilizar las bases del sistema ya desarrollado, contabilizando lo que se hará sin hacer suposiciones sobre lo que se puede hacer. Aún así, intentaremos que el EA prediga el volumen que se negociará durante todo el periodo de un día. Esta tercera opción es la que pondremos en práctica. De esta forma conseguiremos evitar la presencia del disparador sin limitar los tipos de sistemas comerciales que puede soportar el EA,

Ahora que entendemos que el problema ocurre cuando tenemos una combinación de órdenes, vamos a profundizar en el tema. Volviendo al ejemplo de un apalancamiento de 3 veces, en el que ya había 33 operaciones con un límite diario de 100 veces, todo estaría bajo control si el EA funcionara sólo con órdenes de mercado. Sin embargo, si por alguna razón tenemos una orden pendiente en el servidor comercial, esto puede cambiar. Si esa orden pendiente añade otras 3 unidades al volumen mínimo nada más ser recogida, habremos superado el volumen máximo que estipula el EA.

Puede pensar que esas 2 unidades de volumen que han superado el límite de 100 no son muchas, pero esto puede ser un problema serio. Por ejemplo, imaginemos que el EA ha sido configurado para contabilizar un volumen de 50 unidades. Puede ejecutar 2 órdenes de mercado sin transgredir la regla de las 100. Pero si envía una orden pendiente para aumentar el volumen de la posición, ahora habrá alcanzado el volumen de 100 unidades. Sin embargo, esa orden pendiente aún no se ha ejecutado, ya que está pendiente. En algún momento, el EA puede decidir enviar otra orden de mercado, en cuyo caso el volumen volverá a ser de 50, pero el límite ya ha sido transgredido.

Inmediatamente la clase C_Manager se da cuenta de que el EA ha alcanzado el límite diario, considerando la operación de 100. Sin embargo, aunque es consciente de la existencia de una orden pendiente, la clase no sabe cómo gestionarla. Si la orden se ejecuta, el EA superará la regla de 100, alcanzando una operación de 150 unidades de volumen. ¿Entiendes el problema? El bloqueo establecido hace ya algún tiempo para evitar que el EA colgara demasiadas órdenes en el libro o abriera demasiadas posiciones con alto volumen se rompió por el simple hecho de que el disparador que automatiza el EA no previó esta posibilidad. Se suponía que la clase C_Manager podría evitar que el EA superara el volumen máximo establecido, pero esto no ocurría debido a la combinación de órdenes pendientes y de mercado. 

Muchos programadores simplemente sortean este problema utilizando sólo órdenes de mercado o sólo órdenes pendientes. Sin embargo, esto no hace que el problema desaparezca. Cada nuevo EA automatizado que se desarrolle tendrá que pasar por el mismo régimen de pruebas y análisis para evitar que se active el disparador.

Aunque el problema descrito anteriormente puede observarse incluso utilizando el sistema manualmente, es mucho menos probable que un operador cometa este fallo. Esto se debe a que echaría la culpa al operario y no al sistema de protección. Sin embargo, para un sistema 100% automático, esto es completamente inaceptable. Por esta y otras razones, NUNCA, BAJO NINGUNA CIRCUNSTANCIA, dejes un EA 100% automático funcionando sin vigilancia, aunque lo hayas programado. Incluso si eres un excelente programador, no es aconsejable dejarlo operando sin observación.

Si crees que digo tonterías, piensa que en los aviones hay sistemas de piloto automático que permiten despegar, recorrer la ruta y aterrizar sin intervención humana. Pero aun así, en la cabina siempre hay un piloto cualificado para manejar el avión. ¿A qué cree que se debe esto? La industria no se gastaría una fortuna en desarrollar un piloto automático para al final tener que formar a un piloto que maneje el avión. Eso no tendría sentido a menos que la industria no confiara plenamente en el piloto automático. Piénsalo un momento.


Corrección del fallo

Para corregir el fallo, es necesario hacer algo más que identificarlo. Hay que buscar soluciones eficaces. Ahora que conocemos el fallo y entendemos cómo se desencadena y cuáles son sus consecuencias, podemos buscar una solución.

Es importante confiar en la clase C_Manager para evitar la transgresión del volumen, pero para resolver el problema tenemos que hacer algunos cambios en el código. En primer lugar, debemos añadir una nueva variable, como se muestra en el fragmento siguiente:

                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        uint    Leverage;
                }m_Position, m_Pending;
                ulong   m_TicketPending;

Esta nueva variable nos ayudará a predecir el volumen y resolver el problema. Como resultado, se ha eliminado otra parte del código.

Después de añadir la nueva variable al sistema, se requerirán algunos cambios. Me centraré en las partes más relevantes, pero los cambios detallados se pueden encontrar en el código completo adjunto. El primer paso es crear una rutina para capturar los datos de la orden pendiente:

inline void SetInfoPending(void)
                        {
                                ENUM_ORDER_TYPE eLocal = (ENUM_ORDER_TYPE) OrderGetInteger(ORDER_TYPE);
                                
                                m_Pending.Leverage = (uint)(OrderGetDouble(ORDER_VOLUME_CURRENT) / GetTerminalInfos().VolMinimal);
                                m_Pending.IsBuy = ((eLocal == ORDER_TYPE_BUY) || (eLocal == ORDER_TYPE_BUY_LIMIT) || (eLocal == ORDER_TYPE_BUY_STOP) || (eLocal == ORDER_TYPE_BUY_STOP_LIMIT));
                                m_Pending.PriceOpen = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Pending.SL = OrderGetDouble(ORDER_SL);
                                m_Pending.TP = OrderGetDouble(ORDER_TP);
                        }

Capturar los datos de la orden es un poco diferente a capturar los datos de la posición. La principal diferencia es comprobar si estamos comprando o vendiendo, lo que se hace comprobando cada tipo posible que indique una orden de compra. El resto del procedimiento está claramente explicado.

También necesitamos un nuevo procedimiento:

                void UpdatePending(const ulong ticket)
                        {
                                if ((ticket == 0) || (ticket != m_Pending.Ticket) || (m_Pending.Ticket == 0)) return;
                                if (OrderSelect(m_Pending.Ticket)) SetInfoPending();
                        }

Este procedimiento actualiza los datos cuando la orden pendiente recibe alguna actualización del servidor y pasa la información al EA.

Para que el EA ejecute la llamada anterior, necesitamos añadir un nuevo evento a la rutina de manejo OnTradeTransaction:

void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
        switch (trans.type)
        {
                case TRADE_TRANSACTION_POSITION:
                        manager.UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_DELETE:
                        if (trans.order == trans.position) (*manager).PendingToPosition();
                        else (*manager).UpdatePosition(trans.position);
                        break;
                case TRADE_TRANSACTION_ORDER_UPDATE:
                        (*manager).UpdatePending(trans.order);
                        break;
                case TRADE_TRANSACTION_REQUEST: if ((request.symbol == _Symbol) && (result.retcode == TRADE_RETCODE_DONE) && (request.magic == def_MAGIC_NUMBER)) switch (request.action)
                        {
                                case TRADE_ACTION_DEAL:
                                        (*manager).UpdatePosition(request.order);
                                        break;
                                case TRADE_ACTION_SLTP:
                                        (*manager).UpdatePosition(trans.position);
                                        break;
                                case TRADE_ACTION_REMOVE:
                                        (*manager).EraseTicketPending(request.order);
                                        break;
                        }
                        break;
        }
}

Es precisamente la línea resaltada arriba la que llamará a la clase C_Manager.

Bueno, pero volvamos a la clase C_Manager, para seguir implementando una solución al problema del volumen.

Necesitamos hacer una reparación en el sistema como se describe en esta sección para que podamos elevar el nivel de seguridad del sistema. El fallo que se ha detectado y se ha ignorado durante mucho tiempo se encarga de actualizar el volumen que está abierto. Esto no afectaría a un sistema manual, pero para un sistema automatizado es fatal. Por lo tanto, para arreglar este fallo, necesitamos añadir la siguiente línea de código:

inline void LoadPositionValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = PositionsTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = PositionGetTicket(c0)) == 0) continue;
                                        if (PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
                                        if (PositionGetInteger(POSITION_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_TicketPending > 0))
                                        {
                                                C_Orders::ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
                                        {
                                                m_Position.Ticket = value;
                                                SetInfoPositions();
                                                m_StaticLeverage = m_Position.Leverage;
                                        }
                                }
                        }

Desarrollar un sistema totalmente automático es una tarea complicada y difícil. Mientras que un sistema manual o semiautomático puede no verse afectado por la ausencia de una línea indicada, cualquier fallo, por pequeño que sea, puede tener consecuencias catastróficas en un sistema automatizado. Esto es aún más preocupante si el operador no supervisa el EA ni comprende sus acciones, lo que inevitablemente provocará pérdidas financieras en el mercado.

El siguiente paso es modificar las rutinas de envío de órdenes y pedidos a precio de mercado para que puedan devolver un valor a quien las llama indicándole lo que está ocurriendo. El siguiente código inicial podrá proporcionar esta funcionalidad:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(true)) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(false)) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

Ten en cuenta que estoy aplicando una prueba para comprobar si la entrada es válida y sigue siendo válida. Esto se debe al hecho de que en una cuenta NETTING puede cerrar una posición mediante el envío de un volumen igual a la posición. En este caso, es importante eliminar los datos de la posición cerrada para garantizar la seguridad y fiabilidad del sistema. Esta práctica es esencial para un EA 100% automatizado, pero puede ser innecesaria para un EA manual.

El siguiente paso necesario es añadir una forma para que el EA sepa qué volumen enviar. Actualmente, es posible invertir la posición en el sistema actual, pero esto requiere dos llamadas o envíos de solicitudes al servidor en lugar de una sola. Es necesario cerrar la posición y enviar una nueva solicitud de apertura de posición. Con la información del volumen abierto proporcionada por el sistema, el EA puede saber qué volumen enviar.

const uint GetVolumeInPosition(void) const
                        {
                                return m_Position.Leverage;
                        }

El código anterior es suficiente para realizar esta acción. Sin embargo, en la clase de código, no hay una forma real de invertir la posición.

Para implementar esto, necesitamos modificar de nuevo las funciones de envío de órdenes. Sin embargo, es importante señalar que esto no es una necesidad para un EA manual o semiautomático. Los cambios se realizan porque los necesitamos para producir un EA automatizado. Además, es interesante añadir mensajes en algunos puntos para que un comerciante que esté supervisando el EA pueda entender lo que el EA está haciendo. Aunque no lo hagamos en el código de prueba, es importante considerar seriamente este añadido. Sólo mirando el gráfico en la pantalla puede no ser suficiente para notar algún comportamiento extraño del EA.

Después de implementar todos los cambios necesarios, por fin llegamos al punto clave que solucionará el problema que llevamos tratando desde el principio de este artículo. Hemos añadido una forma para que el EA pueda enviar cualquier volumen al servidor, a través de dos llamadas que la clase C_Manager pondrá a disposición del EA. Con esto, solucionamos el problema por el que el EA podía, en algún escenario, utilizar un volumen negociado mayor que el indicado en el código de gestión. Ahora podemos predecir el volumen que posiblemente entrará o saldrá de la posición que se está configurando o que ya está configurada.

El nuevo código para las llamadas se puede ver a continuación:

//+------------------------------------------------------------------+
                bool CreateOrder(const ENUM_ORDER_TYPE type, const double Price, const uint LeverageArg = 0)
                        {
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                m_Pending.Ticket = C_Orders::CreateOrder(type, Price, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                if (m_Pending.Ticket > 0) bRet = OrderSelect(m_Pending.Ticket);
                                if (bRet) SetInfoPending();
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+  
                bool ToMarket(const ENUM_ORDER_TYPE type, const uint LeverageArg = 0)
                        {
                                ulong tmp;
                                bool bRet = false;
                                
                                if (!IsPossible(type, (LeverageArg > 0 ? LeverageArg : m_InfosManager.Leverage))) return bRet;
                                tmp = C_Orders::ToMarket(type, (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceStop), (m_InfosManager.IsOrderFinish ? 0 : m_InfosManager.FinanceTake), m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                                m_Position.Ticket = (m_bAccountHedging ? tmp : (m_Position.Ticket > 0 ? m_Position.Ticket : tmp));
                                if (m_Position.Ticket > 0) bRet = PositionSelectByTicket(m_Position.Ticket);
                                if (!bRet) ZeroMemory(m_Position);
                                
                                return bRet;
                        }
//+------------------------------------------------------------------+

En el código, hemos añadido el argumento mencionado anteriormente, pero tenga en cuenta que tiene un valor por defecto de cero. Si no se indica un valor durante la llamada a la función, se utilizará el valor introducido durante la llamada al constructor.

Sin embargo, el tratamiento no se realizará en este paso, sino en otro, ya que el análisis de lo que debe ejecutar o permitir la clase C_Manager para enviar la orden es algo común tanto para colocar una orden pendiente como para enviar una solicitud de ejecución a precio de mercado. Para saber de qué valor estará pendiente el EA para que la clase C_Manager lo habilite, utilizamos una pequeña prueba con el operador ternario para rellenar adecuadamente el valor que se utilizará en la prueba de habilitación. Ahora, veamos qué es lo que hay que añadir en la rutina para configurar correctamente la prueba, que se puede ver justo debajo:

inline bool IsPossible(const ENUM_ORDER_TYPE type, const uint Leverage)
                        {
                                int i0, i1;
                                
                                if (!CtrlTimeIsPassed()) return false;
                                if ((m_StaticLeverage >= m_InfosManager.MaxLeverage) || (m_bAccountHedging && (m_Position.Ticket > 0))) return false;
                                if ((m_Pending.Ticket > 0) || (Leverage > INT_MAX)) return false;
                                i0 = (int)(m_Position.Ticket == 0 ? 0 : m_Position.Leverage) * (m_Position.IsBuy ? 1 : -1);
                                i1 = i0 + ((int)(Leverage * (type == ORDER_TYPE_BUY ? 1 : -1)));
                                if (((i1 < i0) && (i1 >= 0) && (i0 > 0)) || ((i1 > i0) && (i1 <= 0) && (i0 < 0))) return true;
                                if ((m_StaticLeverage + MathAbs(i1)) > m_InfosManager.MaxLeverage)
                                {
                                        Print("Request denied, as it would violate the maximum volume allowed for the EA.");
                                        return false;
                                }
                                
                                return true;
                        }

Al programar el mecanismo de activación, es importante tener en cuenta una cosa para evitar que las órdenes sean denegadas. Si hay una orden pendiente, debe ser eliminada antes de cualquier solicitud de cambio de volumen. De lo contrario, la solicitud será denegada al chocar con esas líneas. ¿Por qué ocurre esto? Parece algo extraño, pero merece una explicación.

La razón es que una orden pendiente es una operación incierta ya que no es posible saber si el EA permitirá que se ejecute o no. Si el sistema está utilizando una orden pendiente como forma de almacenar la posición y el volumen abierto cambia, es importante que la orden pendiente se elimine antes de que el volumen cambie. En cualquier caso, será necesario eliminar la orden pendiente para poder colocar una nueva que contenga el volumen correcto.

Por lo tanto, es comprensible que la clase C_Manager requiera la eliminación de cualquier orden pendiente durante el cambio de volumen abierto. Además, si hay una posición larga y se invierte la mano, se colocará una orden con un volumen mayor que el volumen abierto. Si se mantiene la orden de stop pendiente inicial, pueden surgir problemas en el momento de ejecutar la orden, aumentando aún más el volumen. Estas son más razones por las que la clase C_Manager no requiere órdenes pendientes durante la operación de volumen.

Espero que haya quedado claro por qué la clase requiere que se elimine la orden pendiente durante el cambio de volumen. No caigas en la tentación de eliminar esta línea, ya que el EA no enviará requerimientos al servidor.

Ahora tenemos un cálculo bastante extraño y una prueba aún más extraña, que puede causar un gran dolor de cabeza si intenta entenderlo sin entender el cálculo realizado anteriormente. Al final, hay otra prueba que, a primera vista, no tiene ningún sentido.

Vamos a tomárnoslo con calma para que todo el mundo pueda entender esta completa locura matemática. Para hacerlo más fácil, lo veremos a través de algunos ejemplos. Presta mucha atención al leer cada uno de los ejemplos para entender todo el código resaltado.

Ejemplo 1:

Supongamos que no hay ninguna posición abierta, el valor de i1 será igual al valor absoluto contenido en la variable Leverage, y este es el caso más sencillo de todos. Si estamos abriendo una posición o poniendo una orden en el libro, da igual. Si la suma del valor de i1 con el valor acumulado del EA es inferior al máximo indicado, se enviará la petición al servidor.

Ejemplo 2:

Supongamos que tenemos una posición corta con un volumen X, pero no tenemos ninguna orden pendiente. En este caso, podemos tener algunas situaciones diferentes, descritas a continuación:

  • Si la orden del EA es vender un volumen Y, entonces el valor de i1 será la suma entre X e Y. En este caso, es posible que la orden no se ejecute, ya que estaremos aumentando la posición corta.
  • Si la orden del EA es comprar un volumen Y y este es menor que el volumen X, entonces el valor de i1 será la diferencia entre X e Y. En este caso, la orden pasará, ya que la posición corta se reducirá.
  • Si la orden del EA es comprar Y e Y es igual a X, entonces i1 es igual a cero. En este caso, la orden pasará, ya que se cerrará la posición corta.
  • Si la orden del EA es comprar el volumen Y y éste es mayor que el volumen X, el valor de i1 será la diferencia entre X e Y. En este caso, la orden puede no pasar ya que estaremos convirtiendo la posición en una posición larga.

Lo mismo es válido para el caso de una posición larga. Sin embargo, también se modificará la petición del EA para que, al final, tengamos el mismo comportamiento indicado en la variable i1. Ahora, fíjate que en la prueba donde verificamos si la suma de i1 con lo acumulado por el EA, hasta ese momento es menor al máximo indicado, tenemos una llamada a la función MathAbs. Hacemos esto porque, en algunos casos, tendremos el valor de i1 siendo negativo y necesitamos que sea positivo para que la prueba ocurra correctamente.

Aún nos queda un último problema por resolver. Y es que cuando invertimos la posición, la actualización del volumen negociado no se hará correctamente. Para resolver esto, necesitamos hacer un pequeño cambio en el sistema de análisis. Este cambio se realiza en el siguiente procedimiento:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                uint tmp = m_Position.Leverage;
                                bool tBuy = m_Position.IsBuy;
                                
                                m_Position.Leverage = (uint)(PositionGetDouble(POSITION_VOLUME) / GetTerminalInfos().VolMinimal);
                                m_Position.IsBuy = ((ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE)) == POSITION_TYPE_BUY;
                                m_Position.TP = PositionGetDouble(POSITION_TP);
                                v1 = m_Position.SL = PositionGetDouble(POSITION_SL);
                                v2 = m_Position.PriceOpen = PositionGetDouble(POSITION_PRICE_OPEN);
                                if (m_InfosManager.IsOrderFinish) if (m_Pending.Ticket > 0) v1 = m_Pending.PriceOpen;
                                m_Position.EnableBreakEven = (m_InfosManager.IsOrderFinish ? m_Pending.Ticket == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return (int)(tBuy == m_Position.IsBuy ? m_Position.Leverage - tmp : m_Position.Leverage);
                        }

Primero, guardamos la posición en la que se encuentra el sistema antes de la actualización. Después no intervenimos en nada hasta el final, cuando comprobamos si ha habido algún cambio en la posición.  La idea es comprobar si seguimos largos o cortos. Lo mismo ocurre en el caso contrario. Si hay un cambio, devolvemos el volumen abierto. En caso contrario, realizamos el cálculo normal para saber cuál es el volumen actualmente expuesto. Con esto, podemos saber y actualizar adecuadamente el volumen que el EA ya ha negociado.


Consideraciones finales

Aunque el sistema actual es más complicado, debido a lo que tenemos que hacer para garantizar que no se produzcan problemas con la automatización, es importante señalar que casi no se muestran mensajes en el terminal para que el operador pueda seguir lo que está haciendo el EA. Esto es bastante relevante.

Sin embargo, como sólo estoy mostrando cómo crear un sistema totalmente automatizado, no sé qué información sería más relevante para que sepas lo que está pasando dentro del EA, ya que cada persona tiene unas necesidades diferentes. En cualquier caso, no aconsejo simplemente coger el código y lanzarlo directamente a una cuenta real sin realizar pruebas y ajustes previos para saber exactamente qué está pasando.

En el próximo artículo mostraré cómo ponerlo en marcha para que el EA pueda operar de forma autónoma. Así que hasta que se publique el artículo y tengas acceso a las explicaciones, te sugiero que estudies este artículo y trates de entender cómo medidas sencillas pueden resolver problemas que parecen mucho más complicados.

Quiero dejar claro que no estoy tratando de mostrar la única manera de crear un EA automático. Sólo estoy presentando una entre varias propuestas posibles y factibles de ser creadas y utilizadas. También destaco los riesgos que conlleva el uso de un EA automático, para que sepas cómo minimizarlos o reducirlos a un nivel aceptable.

Un último consejo para aquellos que están tratando de utilizar el EA, ya sea manualmente o con algún tipo de automatización, es que si el sistema no está permitiendo el envío de órdenes y presenta un mensaje indicando que el volumen está violando la regla de uso. Pueden tratar de cambiar el valor del parámetro 'MagicNumber':

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 10);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

Por defecto, este valor está fijado en 10, pero puede ser necesario utilizarlo con un valor más alto, especialmente en el mercado Forex. Mucha gente suele utilizar un valor de 100. Para realizar este cambio, simplemente sustituye el valor 10 por el valor deseado, como 1000 por ejemplo, si quieres utilizar un volumen diez veces mayor que el habitual 100.

int OnInit()
{
        string szInfo;
        
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08, false, 1000);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        for (ENUM_DAY_OF_WEEK c0 = SUNDAY; c0 <= SATURDAY; c0++)

De esta forma podrás enviar hasta 10 órdenes con el volumen indicado antes de que el EA bloquee el envío de nuevas órdenes.

En el siguiente vídeo puedes ver el sistema funcionando en su configuración actual.



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

Archivos adjuntos |
Algoritmos de optimización de la población: Optimización de malas hierbas invasoras (IWO) Algoritmos de optimización de la población: Optimización de malas hierbas invasoras (IWO)
La asombrosa capacidad de las malas hierbas para sobrevivir en una gran variedad de condiciones inspiró la idea de un potente algoritmo de optimización. El IWO es uno de los mejores entre los analizados anteriormente.
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.
De nuevo sobre el sistema de Murray De nuevo sobre el sistema de Murray
Los sistemas gráficos de análisis de precios son merecidamente populares entre los tráders. En este artículo, hablaremos sobre el sistema completo de Murray, que incluye no solo sus famosos niveles, sino también algunas otras técnicas útiles para valorar la posición actual del precio y tomar una decisión comercial.
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.