English Русский 中文 Deutsch 日本語 Português
preview
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)

MetaTrader 5Trading | 13 abril 2023, 10:00
784 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Cómo construir un EA que opere automáticamente (Parte 08): OnTradeTransaction, expliqué cómo se puede aprovechar la plataforma MetaTrader 5 haciendo uso de una función de manejo de eventos bastante curiosa. Pero vamos a ver cómo construir el primer nivel de automatización para el EA.

A diferencia de muchos mecanismos que se encuentran por ahí, aquí mostraré un mecanismo que no sobrecargará al Expert Advisor (EA) ni a la plataforma. Este mecanismo se puede utilizar en cuentas de tipo HEDGING, aunque está enfocado principalmente a cuentas de tipo NETTING.

Inicialmente, comenzaremos con un sistema sencillo que utiliza órdenes de tipo OCO. Sin embargo, tenemos previsto ampliar la funcionalidad en el futuro para utilizar un sistema aún más robusto e interesante, especialmente para aquellos a los que les gusta operar en mercados muy volátiles en los que existe un alto riesgo de que se omitan las órdenes.


Creación del mecanismo breakeven y trailing stop para órdenes OCO

Un sistema de órdenes OCO (One-Cancels-the-Other), para aquellos que no sepan lo que es, es un sistema en el que el take profit y el stop loss se establecen en la propia orden o posición. Si la orden se retira o la posición se cierra, esas órdenes take profit o stop loss también se finalizarán.

Tanto las órdenes take profit como stop loss pueden eliminarse o añadirse en cualquier momento. A efectos prácticos y para no complicar innecesariamente el código, vamos a suponer que siempre se crearán en el momento en que el EA envíe la orden al servidor de negociación y se finalizarán cuando se alcance uno de los límites, cerrando así la posición.

Para crear el mecanismo de activación, vamos a centrar nuestra atención en la clase C_Manager. Allí tenemos prácticamente todo listo y preparado para recibir el sistema de disparo para activar el breakeven y el trailing stop.  Añadamos primero la rutina que generará el breakeven de una posición. Esta rutina se puede ver en su totalidad en el código de abajo:

inline void TriggerBreakeven(void)
                        {
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                                m_Position.EnableBreakEven = (ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, m_Position.PriceOpen, m_Position.TP) ? false : true);
                        }

Probablemente esperabas una rutina bastante más compleja que la que se muestra aquí. Pero confía en nosotros, esta sencilla rutina es capaz de activar el breakeven de una posición. Si no entiendes cómo es posible, vamos a analizar cómo ocurre para que podamos entender que no necesitamos nada más que esta sencilla rutina.

Lo primero que hacemos es ejecutar una llamada a la función PositionSelectByTicket. Esta función carga toda la información actualizada sobre la posición abierta. A continuación, utilizamos la función PositionGetDouble, con el argumento POSITION_PROFIT, para obtener el valor financiero más actual que fue cargado por la llamada PositionSelectByTicket. Comparamos este valor con el introducido durante la inicialización de la clase, en la llamada al constructor.

Si el valor es mayor o igual (ese es el disparador), esto indica que podemos alcanzar el breakeven. A continuación, enviamos el valor del precio en el que se abrió la posición a la rutina presente en la clase C_Orders. Si esta interacción tiene éxito, la clase indicará que se ha logrado el breakeven.

Esta función es bastante simple, ¿verdad? Sin embargo, se define en la cláusula privada. A lo que accederá el EA es a la función trailing stop, que puedes ver completa a continuación:

inline void TriggerTrailingStop(void)
                        {
                                double price;
                                
                                if ((m_Position.Ticket == 0) || (m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        if (MathAbs(price - m_Position.SL) >= (m_Position.Gap * 2))
                                                ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, (m_Position.SL + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1))), m_Position.TP);
                                }
                        }

Una vez que el breakeven se ha disparado y ejecutado, la próxima vez que el EA llame a la función TriggerTrailingStop, comprobaremos la posibilidad de un movimiento del stop loss.

Pero antes de ver eso, veamos dónde se llama a la función de breakeven. El movimiento del trailing stop tiene un disparador un poco más complicado que el movimiento del breakeven. Aquí, vamos a hacer algo un poco diferente. A diferencia del breakeven, que es independiente del activo, del modo de gráfico y del tipo de mercado, donde lo único que importa es el nivel de beneficio de la posición, en el trailing stop necesitamos saber dos cosas: el tipo de gráfico y el tipo de posición.

Algunos programadores de EAs que desarrollan el disparador para el trailing stop a veces no se preocupan por el tipo de trazado gráfico. En algunos casos, esto es correcto. Si el activo tiene incidencia de un spread relativamente alto en su historial, incluso en un sistema de trazado gráfico LAST, deberíamos ignorar el modo de representación gráfica. Porque si el movimiento está dentro del spread, es muy probable que tengamos problemas, porque la orden stop loss estará en un punto equivocado.

Así que es importante estudiar y entender bien el activo para diseñar mejor este tipo de disparador. Pero la idea aquí es capturar el precio del activo, no importa cómo lo hagas. Lo que hace falta es tener ese valor en la mano. Una vez hecho eso, vamos a restar ese precio de donde está el stop loss. En ese caso, no importa si estamos vendiendo o comprando, el valor se corregirá automáticamente para tener un valor en puntos, no un valor financiero. Este valor tendrá que ser al menos el doble de la cantidad de puntos que siempre calculamos cada vez que actualizamos el servidor comercial. Si esto se consigue, moveremos la posición donde está el stop loss a la cantidad de puntos del gap. Así, el gap será siempre el mismo, y el ciclo volverá a empezar.

Este mecanismo de activación funciona perfectamente en cualquier tipo de situación. Y un detalle importante: es superligero y muy rápido de ejecutar. Eso es crucial. Piensa en estos disparadores como en una ratonera. Si el mecanismo es demasiado complicado o tiene un tiempo de ejecución largo, el ratón acabará cogiendo el queso y huyendo antes de que se active la trampa.

Entendido esto, es necesario entender otra cosa: ¿cuál será la rutina o manejador de eventos que necesitamos usar para llamar a esas rutinas de arriba? Muchos podrían pensar que el manejador de eventos debería ser la rutina OnTick. ¿Verdad? ERROR. Y para explicar esto, vayamos al siguiente tema.


¿¡Por qué no usar OnTick como rutina de llamada para disparadores!?

Entiendo que puede ser tentador, muy tentador, de hecho, utilizar la rutina OnTick como una forma de llamar a cualquier otra rutina. Sin embargo, ese es, sin duda, el mayor error que puedes cometer. Lo correcto es utilizar el evento OnTime, como se muestra a continuación:

//+------------------------------------------------------------------+
int OnInit()
{
        manager = new C_Manager(def_MAGIC_NUMBER, user03, user02, user01, user04, user08);
        mouse = new C_Mouse(user05, user06, user07, user03, user02, user01);
        (*manager).CheckToleranceLevel();
        EventSetMillisecondTimer(100);

        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
        delete manager;
        delete mouse;
        EventKillTimer();
}
//+------------------------------------------------------------------+
void OnTick() { }
//+------------------------------------------------------------------+
void OnTimer()
{
        (*manager).TriggerTrailingStop();
}
//+------------------------------------------------------------------+

Nota que en el evento OnInit, establecemos un valor corto para generar el evento OnTime usando la función EventSetMillisecondTimer. Puedes establecer cualquier valor a partir de 50 milisegundos, dependiendo del caso. Sin embargo, también deberás llamar a la rutina EventKillTimer, dentro del evento OnDeInit. De esta manera, el EA liberará el evento OnTime, en lugar de hacer que la plataforma MetaTrader 5 genere un evento continuamente. Incluso si la plataforma, puede resolverlo, y deja de generar el evento, es una buena práctica, para liberar el sistema. Cada 100 milisegundos, o aproximadamente 10 veces en 1 segundo, tendremos la comprobación de nuestro disparador, que se muestra en el tema anterior.

Pero esto no responde a la pregunta: ¿Por qué no debemos utilizar la rutina OnTick como llamada al disparador? De hecho, la explicación anterior no responde a esa pregunta, pero podemos usarla como base para entender por qué no debemos hacerlo.

El evento OnTick se dispara en cada tick negociado por el servidor. Mirando el gráfico de barras, incluso en el intervalo de tiempo de un minuto, no tenemos una idea real de lo que está sucediendo. Para hacernos una idea precisa, tendríamos que bajar al nivel de los HFT (trading de alta frecuencia), también conocidos como robots institucionales.

Lo que mucha gente no sabe es que un EA que se ejecuta en una plataforma en un ordenador, a menudo a cientos de kilómetros del servidor de negociación, no tiene ninguna posibilidad de igualar a un HFT que se ejecuta en un servidor dedicado a pocos metros del servidor. Esto se debe a la latencia, que es el tiempo que tarda la información en llegar al servidor. Los que juegan en línea entenderán de lo que hablo, pero para los que no lo sepan, la latencia es mucho mayor que la frecuencia de las operaciones que ejecuta el servidor comercial. En otras palabras, es imposible tener el mismo rendimiento que un HFT.

Volviendo al tema del evento OnTick, en tan solo 1 milisegundo, puede ocurrir que el servidor dispare más de 10 eventos de este tipo. De manera general, si esta cantidad de eventos llega a la plataforma con el objetivo de activar todos los eventos que el servidor disparó, una vez que su EA esté en funcionamiento, la plataforma se bloqueará. Esto se debe a que estará completamente ocupada gestionando muchos más eventos de los que su procesador puede manejar, lo que significa que, independientemente de cómo se organicen las cosas, no podrá ejecutarse debido al elevado número de llamadas a la misma función, que en este caso es el gatillo que dispara el trailing stop.

Puede que, durante un tiempo, esto no suceda realmente y la plataforma siga funcionando sin problemas. Sin embargo, tan pronto como el sistema detecte que estamos posicionados y empiece a verificar el disparo, podríamos enfrentarnos a serios problemas, ya que la probabilidad de que la plataforma colapse es muy alta.

Y esto puede ocurrir especialmente en activos como los índices de futuros. Así que no intentes usar OnTick como forma de provocar disparos. Olvídate de este manejador de eventos para EA, no está ahí para que lo usemos los simples mortales, sino para que los HFTs se mantengan al día con el servidor de operaciones.

La forma adecuada, de hecho, para verificar un sistema de disparo es siempre utilizando el evento OnTime. De esta manera, éste debe establecerse lo suficientemente bajo como para que no sufra una pérdida de calidad, pero al mismo tiempo, dé la oportunidad de que todos los disparadores sean probados, analizados y ejecutados correctamente, sin sobresaltos. De esta forma tendrás un EA seguro, robusto, fluido y fiable.

Hay otra cuestión acerca de los disparadores que vale la pena mencionar, todavía dentro de esta cuestión del trailing stop. Algunos sistemas EA en realidad no efectúan el breakeven antes de iniciar el trailing stop. Ellos ya utilizan el trailing stop desde el momento en que se abre la posición. Esto puede parecer un poco extraño, pero en realidad, el detalle es que el sistema operativo o configuración ya tiene en su forma de actuar este tipo de concepto. En este caso, el sistema será muy similar al mostrado anteriormente, pero con la diferencia de la distancia del gap a utilizar. Sin embargo, el concepto seguirá siendo el mismo.

Además de este modelo, hay otro en el que no tenemos, de hecho, un trailing stop o breakeven. Tenemos otro tipo de sistema que se puede utilizar junto con los disparadores. Se trata de otro tipo de desencadenante, pero lo analizaremos en otra ocasión.


Trailing stop y breakeven mediante orden pendiente

El sistema presentado anteriormente funciona muy bien cuando la intención es tener una posición y el sistema de órdenes del tipo OCO. Sin embargo, hay un problema con este sistema de órdenes OCO: la volatilidad. Cuando la volatilidad es muy alta, las órdenes pueden ser omitidas y eso puede llevar a malas situaciones.

Existe una "solución" a este problema, que consiste en utilizar una orden pendiente para garantizar una salida. Sin embargo, esta "solución" no se puede implementar en un sistema de tipo HEDGING, es decir, no se puede utilizar con el EA presentado. Si intentas utilizarla en una cuenta HEDGING, el EA será expulsado del gráfico, ya que para la clase C_Manager estará cometiendo un grave error. Sin embargo, es posible adaptarla para permitir que el EA tenga dos posiciones abiertas en direcciones contrarias en una cuenta HEDGING. De esta forma, se puede hacer lo mismo que se ha mostrado anteriormente en caso de estar utilizando una cuenta NETTING.

Pero si te paras a pensarlo, no tiene sentido abrir una posición en direcciones contrarias en una cuenta HEDGING, ya que si el servidor de órdenes captura la orden pendiente, lo mejor sería que el servidor cerrara ambas posiciones. Esto haría que el EA se comportara de la misma manera en una cuenta HEDGING que en una cuenta NETTING, cerrando la posición abierta mediante una orden pendiente.

¿Te gusta la idea? Antes de que te entusiasmes, debes tener en cuenta algunos detalles y problemas al respecto.

En primer lugar, la orden pendiente no se ejecutará necesariamente en el punto que hayas indicado. Recuerda que una orden pendiente busca el precio donde quiera que esté. Otro problema es que si se produce un fallo grave del EA y la posición u orden se cancela o cierra, el otro extremo puede quedarse sin la contrapartida.

Pero hay una ventaja: si todo va bien y se pierde la conexión con el servidor, la orden pendiente en sentido contrario, con el mismo volumen, asegurará el cierre de la posición (cuentas NETTING) o la fijación del precio (cuentas HEDGING).

Como puede ver, utilizar este método tiene ventajas e inconvenientes.

Para utilizarlo, es necesario realizar algunos pequeños cambios en la clase C_Manager. Estos cambios son simples de entender y pueden ser revertidos si no quieres usarlos. Pero aquí hay un detalle importante: Si vas a utilizar este método, ten cuidado de no eliminar las órdenes pendientes, que fueron colocadas por el EA. Si lo haces, la parte de la operación opuesta responsable del cierre se verá comprometida.

Para este sistema, no es aconsejable permitir que el usuario lo modifique. Es decir, una vez compilado el código, no conviene desconectarlo del EA. Debe permanecer siempre activado o desactivado. Para ello, crearemos una nueva definición en el código de la clase C_Manager, que se puede ver a continuación:

//+------------------------------------------------------------------+
#define def_MAX_LEVERAGE                10
#define def_ORDER_FINISH                false
//+------------------------------------------------------------------+

Cuando esta definición se establezca en true (verdadero), el sistema utilizará el método stop a través de la orden pendiente. Esto significa que ya no tendrá un punto de take profit, ya que esto complicaría demasiado las cosas y podría generar la falta de orden opuesta. Si esta misma configuración se establece en false, utilizará el método de breakeven y trailing stop, visto en el tema inicial del artículo. De esta forma estarás utilizando un sistema de órdenes OCO. Por defecto dejaré la configuración en false. Si quieres utilizar el método explicado en este tema, cambia este valor de false a true y compila el EA.

Ahora necesitamos modificar los procedimientos que envían las órdenes de mercado o crean las órdenes pendientes. Se verán como se muestra a continuación:

//+------------------------------------------------------------------+
                void CreateOrder(const ENUM_ORDER_TYPE type, const double Price)
                        {
                                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 ((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));
                        }
//+------------------------------------------------------------------+

Es necesario modificar estos puntos para que no se creen los precios de take profit y stop loss. De esta manera, el servidor entenderá que esos precios no serán creados y tendrás una orden sin límites de ganancia o pérdida. Puede que te aterre enviar una orden de mercado que no contenga un stop loss real o añadir una orden pendiente que tampoco tenga un punto de stop loss. Pero no tengas tanto miedo. Es necesario experimentar con el sistema para entender lo que está sucediendo. Una vez hecho esto, el siguiente cambio a realizar se muestra a continuación:

                void PendingToPosition(void)
                        {
                                ResetLastError();
                                if ((m_bAccountHedging) && (m_Position.Ticket > 0))
                                {
                                        if (def_ORDER_FINISH)
                                        {
                                                if (ClosePosition(m_Position.Ticket)) ZeroMemory(m_Position.Ticket);
                                                ClosePosition(m_TicketPending);                                         
                                        }else SetUserError(ERR_Unknown);
                                }else m_Position.Ticket = (m_Position.Ticket == 0 ? m_TicketPending : m_Position.Ticket);
                                m_TicketPending = 0;
                                if (_LastError != ERR_SUCCESS) UpdatePosition(m_Position.Ticket);
                                CheckToleranceLevel();
                        }

Aquí, cuando el EA informa que una orden pendiente se ha convertido en una posición, podemos tener problemas si estamos en un tipo de cuenta HEDGING con una posición ya abierta. Si la configuración está en false, se producirá un mensaje de error grave. Si se establece en true, enviaremos la petición para cerrar la posición original y restablecer su región de memoria a cero, cerrando también la posición recién abierta. Nótese que si la conexión con el servidor está en buenas condiciones, conseguiremos cerrar ambas posiciones, poniendo a cero nuestra exposición en el mercado.

Ahora tenemos que hacer que el sistema cree una orden pendiente, que se utilizará como stop loss y permanecerá en el libro de órdenes. Este es un punto crítico en el sistema, ya que si no está bien planificado, habrá serios problemas debido a la falta de un stop loss. Por lo tanto, es necesario mantener un ojo en lo que el EA está haciendo observando la ventana de abajo:

Figura 01

Figura 01 - Punto donde vemos las órdenes pendientes y las posiciones abiertas

Esta ventana, mostrada en la figura 01, debe estar siempre abierta para que podamos analizar lo que el EA está haciendo en el servidor. No confíes ciegamente en el EA, por muy fiable que parezca.

Sospecha siempre de las intenciones del EA. Para que el EA tenga un punto de stop loss, la clase C_Manager tendrá que crear la orden pendiente. Se crea de la siguiente manera:

                void UpdatePosition(const ulong ticket)
                        {
                                int ret;
                                double price;
                                
                                if ((ticket == 0) || (ticket != m_Position.Ticket)) return;
                                if (PositionSelectByTicket(m_Position.Ticket))
                                {
                                        ret = SetInfoPositions();
                                        if (def_ORDER_FINISH)
                                        {
                                                price = m_Position.PriceOpen + (FinanceToPoints(m_InfosManager.FinanceStop, m_Position.Leverage) * (m_Position.IsBuy ? -1 : 1));
                                                if (m_TicketPending > 0) if (OrderSelect(m_TicketPending))
                                                {
                                                        price = OrderGetDouble(ORDER_PRICE_OPEN);
                                                        C_Orders::RemoveOrderPendent(m_TicketPending);
                                                }
                                                m_TicketPending = C_Orders::CreateOrder(m_Position.IsBuy ? ORDER_TYPE_SELL : ORDER_TYPE_BUY, price, 0, 0, m_Position.Leverage, m_InfosManager.IsDayTrade);
                                        }
                                        m_StaticLeverage += (ret > 0 ? ret : 0);
                                }else
				{
					ZeroMemory(m_Position);
                                	if (def_ORDER_FINISH)
					{
						RemoveOrderPendent(m_TicketPending);
						m_TicketPending = 0;
					}
				}
                                ResetLastError();
                        }

En primer lugar, comprobamos si el sistema de órdenes tiene el valor verdadero. Si se cumple esta condición, calculamos un punto de precio para iniciar el ensamblaje y utilizamos una orden pendiente como stop de la posición. Comprobamos si ya existe una orden posicionada. En caso afirmativo, capturamos el precio y eliminamos la orden del libro. En cualquier caso, intentaremos crear una nueva orden pendiente. Si todo es correcto, tendremos una orden pendiente en el libro que servirá como stop loss.

Atención: Si la posición se cierra, la orden pendiente debe ser finalizada. Esto se hace con las siguientes líneas de código.

Ahora necesitamos modificar la función SetInfoPosition para indicar correctamente si necesitamos o no alcanzar el punto de equilibrio, o si podemos ir directamente al trailing stop. La nueva función se verá como se muestra 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);
                                if (def_ORDER_FINISH) if (m_TicketPending > 0) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                m_Position.EnableBreakEven = (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.EnableBreakEven) || (m_Position.IsBuy ? (v1 < v2) : (v1 > v2));
                                m_Position.Gap = FinanceToPoints(m_Trigger, m_Position.Leverage);

                                return m_Position.Leverage - tmp;
                        }

Aquí hacemos una secuencia de pruebas para capturar el precio donde está la orden pendiente. En caso de que el ticket de la orden pendiente no se haya creado todavía por alguna razón, iniciamos adecuadamente el indicador de breakeven para nuestro propósito.

Hasta ahora, ni el breakeven ni el trailing stop se pueden utilizar en el sistema cuando se utiliza una orden pendiente como punto de stop. Para implementar este sistema, no necesitamos un disparador ya que ya está configurado. Sólo necesitamos implementar el sistema de movimiento de órdenes pendientes en el código. Para que el breakeven haga el movimiento, añadimos el siguiente procedimiento al sistema:

inline void TriggerBreakeven(void)
                        {
                                double price;
                                
                                if (PositionSelectByTicket(m_Position.Ticket))
                                        if (PositionGetDouble(POSITION_PROFIT) >= m_Trigger)
                                        {
                                                price = m_Position.PriceOpen + (GetTerminalInfos().PointPerTick * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH)
                                                {
                                                        if (m_TicketPending > 0) m_Position.EnableBreakEven = !ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                }else m_Position.EnableBreakEven = !ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                        }

En la versión anterior de esta función encargada de realizar el breakeven, el precio de la orden stop era exactamente el precio de apertura de la posición. Como a muchos comerciantes les gusta salir con algún beneficio, aunque sea pequeño, decidí modificar la función para que al ejecutar el disparador de breakeven, el EA posicione la orden de salida 1 tick por encima del precio de apertura. De esta manera, el comernciante saldrá con al menos 1 tick de beneficio.

Este sistema funciona para cualquier activo o tipo de mercado, ya que estamos utilizando datos del propio activo para saber dónde debe estar la línea de stop.

A continuación, comprobamos si estamos utilizando el modelo de stop basado en una orden pendiente. Si esto es cierto, comprobamos si hay algún valor en la variable de orden pendiente. Si lo hay, enviamos una solicitud para modificar el punto donde está posicionada la orden a la nueva posición. Si estamos utilizando un sistema basado en órdenes OCO, utilizamos el método visto anteriormente. Estas pruebas pueden parecer una tontería, pero nos evitan hacer solicitudes o enviar solicitudes con información no válida al servidor.

Ahora veamos como es el procedimiento del trailing stop para este caso:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (def_ORDER_FINISH ? m_TicketPending == 0 : m_Position.SL == 0)) return;
                                if (m_Position.EnableBreakEven) TriggerBreakeven(); else
                                {
                                        price = SymbolInfoDouble(_Symbol, (GetTerminalInfos().ChartMode == SYMBOL_CHART_MODE_LAST ? SYMBOL_LAST : (m_Position.IsBuy ? SYMBOL_ASK : SYMBOL_BID)));
                                        v1 = m_Position.SL;
                                        if (def_ORDER_FINISH) if (OrderSelect(m_TicketPending)) v1 = OrderGetDouble(ORDER_PRICE_OPEN);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                        {
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (def_ORDER_FINISH) ModifyPricePoints(m_TicketPending, price, 0, 0);
                                                else ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

Este procedimiento puede parecer extraño a primera vista, ya que he hecho uso de cosas que son inusuales para la mayoría de la gente. Entendamos lo que ocurre aquí. En primer lugar, comprobamos si tenemos alguna posición abierta. Esa es la parte fácil. Pero lo raro aquí es este operador ternario, que es bastante inusual verlo usado de esta manera. Aquí, vamos a separar si la prueba se hará por el ticket pendiente o por el valor del precio de stop loss. Esta comparación se utilizará junto con la comparación de posiciones. Si las pruebas indican que no hay nada que hacer, simplemente volvemos a la llamada.

Si hay algo que hacer, probamos el breakeven. Si ya se ha ejecutado, capturamos el precio actual del activo para realizar la prueba de si podemos o no hacer el trailing stop. Como es posible que necesitemos el precio donde está la orden pendiente y la factorización será la misma de todas formas, iniciamos la variable temporal con el posible valor que podría ser la línea de stop loss. Pero podría ser que estemos usando la orden pendiente como punto de stop. En ese caso, debería moverse, por lo que capturamos el precio donde está. Realizamos la factorización para comprobar si debemos o no hacer que el stop se mueva. Si podemos, ajustamos el precio al que se moverá la orden y movemos las cosas en consecuencia, ya sea la orden pendiente o la línea de stop loss. Pero lo que realmente se moverá dependerá del sistema que utilicemos.

En el vídeo 01 se puede ver una demostración de este sistema en funcionamiento. Para aquellos que imaginan que es algo muy diferente o incluso no funcional, vean el video y saquen sus propias conclusiones. Sin embargo, lo mejor es entender lo que está pasando, compilar el EA y hacer sus propias pruebas en la cuenta DEMO. Así, la comprensión de todo el sistema será más sólida y clara.

Vídeo 01 - Demostración del sistema de stop mediante orden pendiente.


Conclusión

En este artículo, he cubierto el sistema de disparo más simple que se puede poner en un EA y que a muchos les gusta o desean tener en suyo. Sin embargo, este sistema no es adecuado para ser utilizado si desea utilizar el EA en una configuración de cartera. En ese caso yo te aconsejaría utilizar un sistema manual. Pero esto es solo un consejo que te doy, ya que no se exactamente como pretendes utilizar estos conocimientos.

En el archivo adjunto, tendrás el código en su totalidad para poder estudiar y aprender más sobre este tipo de mecanismo aquí expuesto. Recuerda que en el código, inicialmente tendrás un EA que utilizará línea de stop. Para utilizar una orden pendiente como stop, deberás modificar el EA tal y como se explica en este artículo.

Ahora que tenemos la automatización mínima necesaria para el resto de pasos y ejemplos, en el próximo artículo, veremos cómo crear un EA 100% automático. ¡Buenos estudios!

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

Archivos adjuntos |
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.
Aprendiendo a diseñar un sistema comercial con Gator Oscillator Aprendiendo a diseñar un sistema comercial con Gator Oscillator
Bienvenidos a un nuevo artículo de la serie dedicada a la creación de sistemas comerciales basados en indicadores técnicos populares. En esta ocasión, hablaremos sobre el indicador Gator Oscillator y crearemos un sistema comercial utilizando estrategias simples.
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)
La automatización no significa nada si no se puede controlar el horario. Ningún trabajador puede ser eficiente trabajando 24 horas al día. Sin embargo, muchos creen que un sistema automatizado debe trabajar 24 horas al día. Siempre es bueno tener formas de configurar una franja horaria para el Expert Advisor. En este artículo, vamos a discutir cómo agregar correctamente tal franja horaria.
Características del Wizard MQL5 que debe conocer (Parte 5): Cadenas de Markov Características del Wizard MQL5 que debe conocer (Parte 5): Cadenas de Markov
Las cadenas de Markov son una poderosa herramienta matemática que se puede usar para modelar y predecir los datos de las series temporales en varios campos, incluido el financiero. En el modelado y la previsión de series temporales financieras, las cadenas de Markov se usan a menudo para modelar la evolución de los activos financieros a lo largo del tiempo, como los precios de las acciones o los tipos de cambio. Una de las principales ventajas de los modelos de cadenas de Markov es su simplicidad y sencillez de uso.