English Русский 中文 Deutsch 日本語 Português
preview
Cómo construir un EA que opere automáticamente (Parte 06): Tipos de cuentas (I)

Cómo construir un EA que opere automáticamente (Parte 06): Tipos de cuentas (I)

MetaTrader 5Trading | 27 enero 2023, 07:56
670 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Cómo construir un EA que opere automáticamente (Parte 05): Gatillos manuales (II), hemos puesto a punto un EA bastante sencillo y con un nivel muy alto de robustez y fiabilidad. Puede utilizarse para negociar con cualquier activo, ya sea en el mercado de divisas o en el de valores. No dispone de ningún tipo de automatización, es operado de forma totalmente manual.

Hasta ahora nuestro EA puede funcionar en cualquier tipo de situación, pero aún no está listo para ser automatizado, por lo que tenemos que hacer algunas cosas. Esto debe hacerse, incluso antes de añadir break even o el trailing stop, puesto que si antes añadimos estos mecanismos, tendremos que deshacer algunas cosas después. Así que tomaremos una ruta ligeramente diferente, en la que primero veremos la creación de un EA genérico.


El nacimiento de la clase C_Manager

La clase C_Manager será nuestra capa de aislamiento entre el EA y el sistema de órdenes. Ella también comenzará a promover algún tipo de automatización para nuestro EA, con lo cual lo dejará con algún tipo de gatillo automático, para hacer algunas cosas.

Así que veamos cómo esta clase se comienza a gestar. Su código inicial se ve a continuación:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
#include "C_Orders.mqh"
//+------------------------------------------------------------------+
class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
        public  :
//+------------------------------------------------------------------+
                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade)
                        :C_Orders(magic)
                        {
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                        }
//+------------------------------------------------------------------+
                ~C_Manager() { }
//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                C_Orders::CreateOrder(type, Price, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+  
                void ToMarket(const ENUM_ORDER_TYPE type)
                        {
                                C_Orders::ToMarket(type, m_InfosManager.FinanceStop, m_InfosManager.FinanceTake, m_InfosManager.Leverage, m_InfosManager.IsDayTrade);
                        }
//+------------------------------------------------------------------+
};

Lo que estás viendo en el código anterior es el esqueleto básico de lo que vamos a construir. Ten en cuenta que ahora el EA ya no tendrá que proporcionar ciertos detalles al enviar órdenes. Todo será gestionado en esta clase C_Manager. De hecho, en la llamada al constructor, pasamos todos los valores necesarios para crear una orden o hacer un pedido de apertura de una posición de mercado.

Pero quiero que tú notes un hecho, la clase C_Manager está heredando la clase C_Orders, pero esta herencia se está realizando de manera privada. ¿Por qué? La razón es la seguridad y el aumento de la confiabilidad, ya que al colocar esta clase aquí, como un tipo de «administrador», necesitamos que sea el único punto de comunicación entre el EA y la clase responsable de enviar las órdenes.

Debido a que C_Manager controlará el acceso al sistema de órdenes, pudiendo enviar, cerrar o modificar órdenes y posiciones, daremos al EA algún tipo de medio de acceder a tal sistema. Pero este acceso será muy limitado. Así que aquí están las dos funciones iniciales que el EA podrá utilizar para acceder al sistema de órdenes. Verás que son mucho más limitadas que las existentes en la clase C_Orders, pero a cambio son más seguras.

Para que entiendas el nivel de lo que estoy mostrando, compara el código del EA del artículo anterior con este, y solo con haber hecho esto se creó la clase C_Manager. Para facilitar, mira lo que sucedió en dos funciones del EA.

int OnInit()
{
        manager = new C_Orders(def_MAGIC_NUMBER);
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);

        return INIT_SUCCEEDED;
}

El código anterior fue eliminado y, en su lugar, ingresó un nuevo código, es cierto que tiene un mayor número de parámetros, pero eso es solo un detalle menor. Lo principal, y lo que considero que hace esto más arriesgado, se ve a continuación:

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        uint    BtnStatus;
        double  Price;
        static double mem = 0;
        
        (*mouse).DispatchMessage(id, lparam, dparam, sparam);
        (*mouse).GetStatus(Price, BtnStatus);
        if (TerminalInfoInteger(TERMINAL_KEYSTATE_CONTROL))
        {
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL, user03, user02, user01, user04);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_UP))  (*manager).ToMarket(ORDER_TYPE_BUY);
                if (TerminalInfoInteger(TERMINAL_KEYSTATE_DOWN))(*manager).ToMarket(ORDER_TYPE_SELL);
        }
        if ((def_SHIFT_Press(BtnStatus) != def_CTRL_Press(BtnStatus)) && def_BtnLeftClick(BtnStatus))
        {
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price, user03, user02, user01, user04);
                if (mem == 0) (*manager).CreateOrder((def_SHIFT_Press(BtnStatus) ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), mem = Price);
        }else mem = 0;
}

Hay partes que han sido eliminadas del mapa. A cambio, observa que el nuevo código que ha surgido es considerablemente más simple. Pero no solo eso, el hecho de usar una clase para hacer el trabajo de «administrador» garantiza que el EA usará exactamente los parámetros definidos en la inicialización de la clase. De esta manera, no corremos el riesgo de colocar un tipo de información incorrecta o equivocada en una de las llamadas. Concentramos todo en un solo lugar, en la clase C_Manager, que ahora está actuando como intermediaria en la conversación entre el EA y la clase C_Orders. Esto sí que aumenta significativamente el nivel de seguridad y confiabilidad del código presente en el EA.


Cuenta NETTING, cuenta EXCHANGE o cuenta HEDGING... he ahí la cuestión

A pesar de que muchas personas ignoran o desconocen este hecho, la verdad es que hay un problema grave aquí. Un problema que puede hacer que un EA funcione bien o mal, y ese problema es el tipo de cuenta utilizada. La mayoría de los operadores o usuarios de la plataforma MetaTrader 5 no tienen idea de que existen 3 tipos de cuentas en el mercado. Pero para aquellos que desean desarrollar un EA que opere de manera genérica y totalmente automática, este conocimiento es muy importante.

En general, para los propósitos de este artículo, mencionaré solo dos tipos de cuentas: NETTING y HEDGING. El motivo es simple: una cuenta NETTING funciona de la misma manera para un EA que una cuenta EXCHANGE. Así que no hablaré de 3 modelos, sino solo de 2.

Incluso con un EA con una automatización simple, como el gatillo de break even o trailing stop, el hecho de estar en una cuenta NETTING hace que su trabajo sea total y completamente diferente de un EA que esté en una cuenta HEDGING. Y la razón es cómo el servidor de negociación funciona. En una cuenta NETTING, el servidor de negociación creará un precio promedio a medida que aumentas o reduces tu posición.

Pero el servidor de una cuenta HEDGING no hace esto. Para él, cada posición puede ser totalmente diferente de otra, es decir, en este caso podrías tener una posición vendida al mismo tiempo que una comprada, y esto en el mismo activo al mismo tiempo. Pero esto no sucederá en una cuenta NETTING. Si intentas hacerlo, el servidor cerrará la posición.

Por esta razón, al usar un EA, debemos asegurarnos de saber si está diseñado para ser utilizado en una cuenta NETTING o una cuenta HEDGING, ya que la forma de trabajar es totalmente diferente, pero esto solo se aplica en casos de un EA automático o con algún nivel de automatización. Para un EA manual, esto no tiene la menor diferencia.

Debido a esto, no podemos crear ningún nivel de automatización sin generar alguna dificultad en términos de programación o usabilidad.

Para crear algo así, necesitamos estandarizar un poco las cosas. Es decir, necesitamos hacer que el EA pueda trabajar en cualquier tipo de cuenta de manera estandarizada. Es cierto que esto reducirá mucho lo que el EA puede hacer en términos de grado de libertad. Pero para un EA automático, no queremos ni es deseable que tenga un gran grado de libertad. Lo mejor es que esté bien contenido, para siempre se comporte bien; si se desvía un poco, debe ser retirado o, al menos, ser puesto en castigo.

La manera de normalizar las cosas es hacer que una cuenta HEDGING trabaje de manera similar a una cuenta NETTING, desde el punto de vista del EA. Sé que puede parecer confuso y complicado hacer esto, pero en realidad, lo que vamos a hacer es permitir que el EA solo tenga una posición abierta y solo una orden pendiente. Es decir, será extremadamente limitado y no podrá hacer nada más.

De esta manera, nacerá el siguiente código en la clase C_Manager:

class C_Manager : private C_Orders
{
        private :
                struct st00
                {
                        double  FinanceStop,
                                FinanceTake;
                        uint    Leverage;
                        bool    IsDayTrade;
                }m_InfosManager;
//---
                struct st01
                {
                        ulong   Ticket;
                        double  SL,
                                TP,
                                PriceOpen,
                                Gap;
                        bool    EnableBreakEven,
                                IsBuy;
                        int     Leverage;
                }m_Position;
                ulong           m_TicketPending;
                bool            m_bAccountHedging;
		double		m_Trigger;

En esta estructura, estamos creando todo lo que necesitaremos para trabajar con la posición abierta. Incluso, ya tenemos en ella algunas cosas relacionadas con el primer nivel de automatización, que se mostrará en el futuro como el break even y el trailing stop. La orden pendiente se almacenará de manera más sencilla, solo con el ticket.. Pero si en el futuro necesitamos más datos se implementarán, pero por ahora solo usaremos esto. También tenemos una otra variable que nos dice si estamos usando una cuenta HEDGING o NETTING, esto será útil para algunas cosas. Y de costumbre, se ha añadido otra variable que no se usará en este primer momento, pero será necesaria cuando creemos el gatillo del break even y del trailing stop.

Bien, así es como empezamos a normalizar las cosas, después de hacer esto, podemos hacer el cambio en el constructor de la clase, esto se puede ver a continuación:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                        }

Muestro el código poco a poco, para aquellos que no tienen experiencia o conocimiento sobre programación. Espero no ser aburrido en relación a esto, ya que quiero que todos puedan entender lo que se está haciendo. Pero vamos con las explicaciones. Estas líneas están diciendo al compilador que queremos que estas variables se inicialicen antes de que el código del constructor comience a ejecutarse. Piensa en esto de la siguiente manera: Cuando se crea una variable, normalmente el compilador le asigna un valor igual a cero.

Con estas líneas estamos diciendo al compilador cuál será el valor asignado a una variable determinada, tan pronto como sea creada. En este punto, reiniciamos todo el contenido de la estructura, de esta manera utilizamos menos código y obtenemos un resultado más rápido. Aquí estamos registrando el hecho de que estaremos tratando con una cuenta de tipo HEDGING, en caso de que sea necesario saber esto en algún momento, tenemos esta variable que nos lo indicará. Y aquí, estamos informando en el terminal qué tipo de cuenta se ha encontrado. Esto para que el usuario esté al tanto del tipo, en caso de que no lo sepa.

Pero antes de ver esos procedimientos, vamos pensar en algo: ¿Y si el EA encuentra más de una posición (cuenta HEDGING), o más de una orden pendiente? ¿Qué pasará? Bien, en este caso tendremos un error, ya que el EA no podrá trabajar con más de una posición y una orden, para esto creamos la siguiente enumeración en el código:

class C_Manager : private C_Orders
{
        enum eErrUser {ERR_Unknown};
        private :

// ... Resto do código ...

};

Aquí vamos a utilizar una enumeración, ya que es más sencillo añadir nuevos códigos de errores en ella. Para esto, solo tendrás que dar un nuevo nombre, y el compilador generará un valor para el código, sin que corras el riesgo de crear un valor duplicado por descuido en el momento de la digitación. Ten en cuenta que la enumeración está antes de cada cláusula privada, entonces será pública y queremos que sea así. Pero para acceder a ella fuera de la clase, necesitaremos usar un pequeño detalle para informar al compilador cuál es la enumeración correcta. Esto es muy bueno y útil cuando deseas usar enumeraciones relacionadas con una clase específica. Ahora vamos a ver los procedimientos que cargarán algo que puede haber quedado en el gráfico y el EA tiene que recuperar antes de comenzar a trabajar. La primera se ve a continuación:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

Vamos a entender cómo funciona este código y por qué tiene esta apariencia peculiar. Aquí usamos un bucle para leer cualquier orden pendiente en el libro, la función OrdersTotal devolverá un valor mayor que cero si existe alguna orden, pero el índice siempre comenzará en cero, esto viene de C / C++. Pero tenemos dos condiciones para finalizar el bucle, la primera es que el valor de la variable c0 sea menor que cero, y la segunda condición es que _LastError sea diferente de ERR_SUCESS, lo que indica que ha ocurrido algún tipo de fallo en el EA.

De esta forma, entramos en el bucle y capturamos la primera orden, cuyo índice es indicado por la variable c0, OrderGetTicket devolverá el valor del ticket de la orden o cero. Si es cero, volveremos al bucle, pero ahora restaremos una unidad a la variable c0.

Como OrderGetTicket carga los valores de la orden, y el sistema no diferencia entre ellas, necesitamos filtrar las cosas, de manera que el EA solo sepa sobre tu orden específica. Entonces, el primer filtro que usamos es el nombre del activo, para esto comparamos el nombre del activo contenido en la orden con el nombre del activo en el cual el EA está siendo ejecutado. Si son diferentes, la orden será ignorada y volveremos a buscar otra, si aún hay alguna.

El siguiente filtro en la secuencia es el número mágico, ya que puede haber más órdenes de otros EA o incluso órdenes colocadas por el usuario en el libro. La forma de saber si la orden fue o no publicada por el EA que se está inicializando es a través de este número, que cada EA deberá tener, debiendo ser diferentes por la razón que acabas de saber. Si el número mágico es diferente al número mágico utilizado por el EA, esta orden deberá ser ignorada, entonces volveremos al inicio, en busca de una nueva orden.

Ahora estamos en una encrucijada. Si el EA ha encontrado una orden que él había publicado antes de ser eliminada, por cualquier motivo, del gráfico (más adelante veremos cuáles pueden ser estos motivos), tendrá en su memoria, o mejor dicho, en la variable que indica el ticket de la orden pendiente, un valor que será diferente de cero. Entonces, si se encuentra una segunda orden, esto será considerado un error, y así esta función usará la enumeración para decir que ocurrió un error.

Aquí estoy usando el valor genérico ERR_Unknown, pero puedes crear un valor para especificar qué fue el error y este será indicado en la variable _LastError. La función SetUserError es la encargada de poner el valor del error en la variable _LastError. Pero si todo está bien y la variable que contiene el ticket de la orden está con el valor cero, el valor del ticket encontrado y que pasó los filtros será almacenado en la variable m_TicketPending para poder ser usado después. Con esto cerramos la explicación de este procedimiento, veamos el siguiente, que es responsable de buscar alguna posición abierta, su código se puede ver a continuación:

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_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Todo lo que dije para el código anterior también aplica para este. La diferencia es que antes estábamos manipulando órdenes y ahora posiciones. Pero la lógica es la misma. Hasta que tenemos la siguiente llamada: SetInfoPositions, que almacenará, ajustará y manejará los datos más recientes de la posición. Y para eso, usaremos el código que se puede ver a continuación:

inline int SetInfoPositions(void)
                        {
                                double v1, v2;
                                int tmp = m_Position.Leverage;
                                
                                m_Position.Leverage = (int)(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);
                                m_Position.EnableBreakEven = (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

Este código es muy interesante cuando se trata de trabajar con los datos más recientes de una posición. Pero ten cuidado, antes de llamarlo, es necesario actualizar los datos de la posición mediante una de las siguientes llamadas: PositionGetTicket, PositionSelect, PositionGetSymbol y PositionSelectByTicket. En general, aquí tenemos todo inicializado o ajustado según sea necesario. El hecho de haber puesto este código aparte es porque lo usaremos en otros puntos para actualizar los datos de la posición siempre que sea necesario.

Básicamente es esto, pero ahora necesitamos hacer una nueva modificación en el constructor de la clase, para que el EA pueda ser inicializado de forma correcta. Todo lo que necesitamos hacer es agregar las llamadas vistas anteriormente. Entonces el código final del constructor será el mostrado a continuación:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadPositionValid();
                                LoadOrderValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Estas dos líneas harán que el EA cargue la posición abierta y la orden pendiente. Ahora presta atención al siguiente hecho, un constructor no puede retornar ningún tipo de valor, ya que esto es un error. Sin embargo, necesitamos algún medio para informar al resto del código que algo salió mal en el constructor.

Cada sistema de programación nos proporciona o nos obliga a crear esos medios, pero aquí en MQL5 tenemos una forma bastante práctica, que es usar la variable _LastError para eso. Si todo está en perfecto estado en la inicialización, verás este mensaje en el terminalSi el sistema ha encontrado alguna posición, también verás este mensaje indicando cuál es el ticket de la posición que el EA estará observandoSi se ha encontrado alguna orden, también verás este mensaje informando el ticket de la orden pendiente encontrada por el EA.

Este valor de _LastError se usará como una forma de verificar si el EA salió de línea en algún momento. Entonces puede ser interesante que agregues más tipos de mensajes en la enumeración de errores, para tener una indicación más precisa de lo que realmente sucedió.


Un problema en la cuenta HEDGING para un EA automático

A pesar de que todo parezca hermoso y maravilloso, especialmente para quienes están empezando a aprender programación, la realidad es que se está avanzando para lograr un nivel de robustez necesario en un EA automático. Sin embargo, aún tenemos un problema potencial en el sistema cuando se utiliza en cuentas HEDGING. Y esto ocurre antes de ver el código encargado de permitir que el EA pueda enviar órdenes o solicitudes al servidor. El problema radica en que, a diferencia de una cuenta de NETTING, donde el servidor va creando un precio promedio conforme la posición va siendo modificada, ya sea por entradas de nuevas órdenes de mercado o por ejecución de órdenes pendientes, en una cuenta de HEDGING no hay tanto control y este no es fácilmente originado por el servidor.

El problema con las cuentas de HEDGING en este caso es que puedo tener una posición abierta y si se permite tener una orden pendiente, esta última, cuando se ejecuta, no cambiará directamente la posición abierta. Lo que puede pasar, y de hecho pasará, es que se abrirá una nueva posición cuando se ejecute la orden pendiente. Esta nueva posición abierta puede bloquear el precio de tal manera que no tenga ni ganancias ni pérdidas. Pero también puede aumentar nuestra posición general. Este es un hecho y sucederá tan pronto se ejecute la orden.

Este detalle, que existe en las cuentas de HEDGING, nos obliga a tomar otra medida, puedo evitar que el EA envíe órdenes de mercado si ya tengo una posición abierta o una orden pendiente en el libro. Esto es sencillo de hacer con base en el código que estoy mostrando. Pero el problema es que durante la inicialización, el EA puede encontrar una posición abierta y una orden pendiente en una cuenta HEDGING, lo cual no es un problema para una cuenta NETTING como expliqué anteriormente.

Pero en este caso, ¿qué debería hacer el EA? Recuerda que la clase C_Manager que controla el EA, no permite que tenga dos posiciones abiertas o dos órdenes pendientes. En este caso, tendré que eliminar la orden pendiente o cerrar la posición abierta. De una manera u otra, algo debe ser hecho, ya que no debería permitir esto en un EA automático, y lo repito, un EA automático nunca debería trabajar con más de una posición abierta o más de una orden pendiente al mismo tiempo. En un EA manual, la conversación es diferente.

De esta manera, tendrás que decidir qué medida tomar. ¿Cerrar la posición o eliminar la orden pendiente? En caso de querer cerrar la posición, la clase C_Orders ya ofrece el procedimiento para hacerlo. Pero en caso de eliminar la orden pendiente, aún no tenemos ningún procedimiento presente en la clase C_Orders. Entonces, necesitamos implementar una forma de hacerlo. Empezaremos por este punto, dando al sistema una forma de eliminar posiciones pendientes. Para hacerlo, necesitaremos agregar un nuevo código al sistema, y este puede ser visto a continuación:

class C_Orders : protected C_Terminal
{
        protected:
//+------------------------------------------------------------------+
inline const ulong GetMagicNumber(void) const { return m_MagicNumber; }
//+------------------------------------------------------------------+
                void RemoveOrderPendent(const ulong ticket)
                        {
                                ZeroMemory(m_TradeRequest);
                                m_TradeRequest.action   = TRADE_ACTION_REMOVE;
                                m_TradeRequest.order    = ticket;
                                ToServer();
                        };

// ... Restante do código da classe 

}

Ahora presta atención a algunos detalles en este código: Primero, está siendo colocado dentro de la cláusula protected, es decir, incluso si intentas usar directamente la clase C_Orders en el EA, no tendrás acceso a este código, debido a la razón que ya expliqué en un artículo anterior de esta misma serie. El segundo punto es que sirve para eliminar órdenes pendientes, no sirve para cerrar una posición o modificar la orden pendiente, solo para eliminarla.

Con este código ya implementado en la clase C_Orders, podemos volver a la clase C_Manager e implementar el sistema para evitar que el EA automático tenga, en caso de estar en una cuenta HEDGING, una orden pendiente, cuando ya tiene una posición abierta. Pero si quieres que cierre la posición y mantenga la orden pendiente, sólo tienes que hacer los cambios en el código, para tener este tipo de comportamiento. Pero lo único que no puede ocurrir es que el EA automático, en una cuenta HEDGING, tenga una posición abierta y una orden pendiente. Esto no se puede tolerar.

Un detalle: si estás en una cuenta HEDGING, puedes utilizar más de un EA al mismo tiempo, en el mismo activo. Si esto ocurre, el hecho de que un EA tenga una posición abierta y otro tenga una orden pendiente no afectará en absoluto al funcionamiento de ambos EAs. En este caso, son independientes y puede ocurrir que tengamos más de una posición abierta o más de una orden pendiente sobre el mismo activo. Lo que no puede ser es que un solo EA se encuentre en este tipo de situación. Esto para los EA automáticos. Seguiré insistiendo en esto, hasta que se grabe en la cabeza.

Si has observado bien, en el código del constructor, primero capturamos una posición, y sólo después capturamos la orden. Esto facilita la eliminación de la orden en caso necesario. Sin embargo, si quieres cerrar la posición y mantener la orden, basta con invertirlo en el constructor, para que primero se capturen las órdenes y luego se capture la posición, y, si hay necesidad, se cierre. Pero veamos cómo lo haremos, en el caso que estoy mostrando, que es capturar la posición y, si es necesario, eliminar las órdenes pendientes encontradas. A continuación se muestra el código correspondiente:

inline void LoadOrderValid(void)
                        {
                                ulong value;
                                
                                for (int c0 = OrdersTotal() - 1; (c0 >= 0) && (_LastError == ERR_SUCCESS); c0--)
                                {
                                        if ((value = OrderGetTicket(c0)) == 0) continue;
                                        if (OrderGetString(ORDER_SYMBOL) != _Symbol) continue;
                                        if (OrderGetInteger(ORDER_MAGIC) != GetMagicNumber()) continue;
                                        if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                        {
                                                RemoveOrderPendent(value);
                                                continue;
                                        }
                                        if (m_TicketPending > 0) SetUserError(ERR_Unknown); else m_TicketPending = value;
                                }
                        }

El cambio que tenemos que hacer es añadir este código resaltado, fíjate lo extremadamente sencillo que es la eliminación de una orden pendiente, pero aquí solo se eliminará. Si tenemos una posición abierta y la cuenta es de tipo HEDGING, tendremos una situación en la que la orden pendiente será eliminada. Pero si la cuenta es de tipo NETTING o no hay una posición abierta, este código no se ejecutará, lo que permite que el EA trabaje tranquilamente.

Pero como tal vez desees cerrar la posición y mantener la orden pendiente, veamos cómo debería ser el código en este caso. No será necesario cambiar el código de carga de la orden pendiente, puedes dejarlo como se muestra arriba. Pero será necesario hacer algunos cambios, y el primero es agregar el siguiente código en el procedimiento que carga la posición, como se puede ver a continuación:

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))
                                        {
                                                ClosePosition(value);
                                                continue;
                                        }
                                        if (m_Position.Ticket > 0) SetUserError(ERR_Unknown); else
					{
						m_Position.Ticket = value;
						SetInfoPositions();
					}
                                }
                        }

Al agregar este código, que está en destaque, podrás cerrar la posición abierta, con lo cual mantienes la orden pendiente. Pero hay un detalle. Para que puedas mantener la orden pendiente y cerrar la posición en una cuenta HEDGING, necesitarás cambiar un punto en el código del constructor, de manera que ahora quedará así:

                C_Manager(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage, bool IsDayTrade, double Trigger)
                        :C_Orders(magic),
                        m_bAccountHedging(false),
                        m_TicketPending(0),
                        m_Trigger(Trigger)
                        {
                                string szInfo;
                                
                                ResetLastError();
                                ZeroMemory(m_Position);
                                m_InfosManager.FinanceStop = FinanceStop;
                                m_InfosManager.FinanceTake = FinanceTake;
                                m_InfosManager.Leverage    = Leverage;
                                m_InfosManager.IsDayTrade  = IsDayTrade;
                                switch ((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE))
                                {
                                        case ACCOUNT_MARGIN_MODE_RETAIL_HEDGING:
                                                m_bAccountHedging = true;
                                                szInfo = "HEDGING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_RETAIL_NETTING:
                                                szInfo = "NETTING";
                                                break;
                                        case ACCOUNT_MARGIN_MODE_EXCHANGE:
                                                szInfo = "EXCHANGE";
                                                break;
                                }
                                Print("Detected Account ", szInfo);
                                LoadOrderValid();
                                LoadPositionValid();
                                if (_LastError == ERR_SUCCESS)
                                {
                                        szInfo = "Successful upload...";
                                        szInfo += StringFormat("%s", (m_Position.Ticket > 0 ? "\nTicket Position: " + (string)m_Position.Ticket + "\n" : ""));
                                        szInfo += StringFormat("%s", (m_TicketPending > 0 ? "\nTicket Order: " + (string)m_TicketPending : ""));
                                        Print(szInfo);
                                }
                        }

Es posible que no hayas notado ningún cambio, pero si comparas este código con el que se ve al final del tema anterior, notarás que esta parte en destaque es diferente. En este caso, se cerrará la posición, mientras que en el original se eliminará la orden. Esa es la gracia de la programación, a veces un pequeño detalle hace toda la diferencia. Aquí solo cambiamos el orden en el que se ejecuta el código, pero el resultado es completamente diferente.

En teoría, y quiero dejar esto bien claro, en teoría, el código visto hasta ahora no tiene ningún tipo de problema. Y se ejecutará perfectamente, pero quiero enfatizar esto. Pero es posible que el servidor de negociación reporte algún error, no porque en realidad haya algo mal en los requerimientos que se están enviando, sino debido a algún tipo de interacción que pueda ocurrir y así la variable _LastError contendrá un valor que indica la presencia de algún tipo de falla.

Algunas fallas pueden ser aceptadas, ya que no son críticas, y otras no pueden ser toleradas en absoluto. Entonces, si has entendido esta diferencia y aceptas esta idea, en ciertos puntos del código, puedes agregar una llamada a ResetLastError, para evitar que el EA sea expulsado del gráfico debido a haber generado algún tipo de error que muy probablemente no fue causado por él, sino por una interacción errónea entre el EA y el servidor comercial.

En este primer momento, no te voy a mostrar en qué puntos puedes agregar estas llamadas. Esto es para que tú no te sientas tentado a hacer estas llamadas de manera indiscriminada en cualquier punto, o que puedas ignorar el valor contenido en la variable _LastError. Hacer esto rompería toda la tesis de construir un EA fuerte, robusto, confiable, pero principalmente automático.


Conclusión

En este artículo, presenté las primeras bases para que empieces a pensar en cómo automatizar un EA de forma segura, estable y robusta. Programar un EA, para ser utilizado de forma automática, no es una tarea que personas con poca experiencia consigan realizar sin generar algún tipo de problema. Esto es algo extremadamente complicado y requiere mucho cuidado por parte del programador.

En el próximo artículo, veremos algunas cosas más que necesitan ser implementadas, para que podamos tener un EA automatizado. Pero no cualquier EA. De hecho, produciremos uno que sea lo más seguro posible al colocarlo en un gráfico. Pero siempre, con la debida precaución y las medidas correctas, para no causar ningún daño a nuestro patrimonio ganado con sudor.


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

Algoritmos de optimización de la población: Enjambre de partículas (PSO) Algoritmos de optimización de la población: Enjambre de partículas (PSO)
En este artículo, analizaremos el popular algoritmo de optimización de la población «Enjambre de partículas» (PSO — particle swarm optimisation). Con anterioridad, ya discutimos características tan importantes de los algoritmos de optimización como la convergencia, la tasa de convergencia, la estabilidad, la escalabilidad, y también desarrollamos un banco de pruebas y analizamos el algoritmo RNG más simple.
Cómo construir un EA que opere automáticamente (Parte 05): Gatillos manuales (II) Cómo construir un EA que opere automáticamente (Parte 05): Gatillos manuales (II)
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura. Al final del artículo anterior, pensé que sería apropiado permitir el uso del EA de forma manual, al menos durante un tiempo.
DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado
En este artículo, implementaremos la capacidad de cambiar las propiedades y el aspecto del control SplitContainer después de haberlo creado.
Cómo construir un EA que opere automáticamente (Parte 04): Gatillos manuales (I) Cómo construir un EA que opere automáticamente (Parte 04): Gatillos manuales (I)
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura.