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

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

MetaTrader 5Ejemplos | 17 mayo 2023, 09:47
653 0
Daniel Jose
Daniel Jose

Introducción

En el artículo anterior, Cómo construir un EA que opere automáticamente (Parte 14):  Automatización (VI), hablé sobre la clase C_Automaton, expliqué lo básico sobre ella. Pero visto que hacer uso de la clase C_Automaton para automatizar el sistema no es algo tan sencillo como puede parecer, en este artículo veremos cómo hacerlo por medio de ejemplos.

Aquí veremos 3 modelos distintos, toda la explicación se centrará, única y exclusivamente, en explicar cómo adaptar la clase C_Automaton, para que cada uno de los modelos pueda ser implementado. Si quieres conocer más detalles sobre el sistema, consulta los artículos anteriores, pues aquí sólo se tratará la parte de la adaptación, es decir, la parte dependiente.

Así que sin más preámbulos, ya que el tema es en realidad bastante largo, vamos a los ejemplos prácticos. 


Primer ejemplo de automatización: media móvil exponencial de 9

Este ejemplo, utiliza el código de EA, que encontrarás en el apéndice como EA_v1.mq5. Vamos a empezar con el constructor de la clase, que se puede ver en el fragmento siguiente:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, 0, 0, Leverage, IsDayTrade, 0, false, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars  = iBars(NULL, m_TF);
                                m_Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                        }

Vamos a entender lo que tenemos aquí, a diferencia del sistema por defecto:

  • No tendremos, stop loss o take profit en el sistema, lo que significa que no habrá un límite de pérdida o ganancia en el sistema, ya que el propio EA generará estos límites, dependiendo de los movimientos del precio;
  • No se creará ningún trailing stop, breakeven u orden pendiente de stop;
  • Utilizaremos un solo handle, que será un indicador de media móvil exponencial de 9 periodos sobre el precio de cierre.

Todo lo demás ya se ha explicado anteriormente, por lo que podemos pasar al procedimiento de cálculo de este mecanismo. La forma de hacerlo se explicó en el artículo anterior, velo tú mismo para más detalles. Pero una vez definida la forma de calcular, he aquí que aparece el siguiente código:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if (m_Buff[0] > m_Buff[m_Infos.Shift]) return TRIGGER_BUY;
                                        if (m_Buff[0] < m_Buff[m_Infos.Shift]) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

¿Suena complicado? En realidad, no lo es. Es bastante sencillo, siempre que se sigan las reglas esbozadas en un diagrama de flujo, como expliqué en el artículo anterior. Inicialmente, intentamos leer el contenido del indicador; si no es posible, devolvemos un disparador nulo, lo que puede provocar la pérdida de la señal.

Hay situaciones, especialmente cuando la señal perdida es saliente, en las que el sistema puede empezar a generar pérdidas. Sin embargo, no debemos hacer suposiciones precipitadas. Por lo tanto, el Expert Advisor debe estar siempre supervisado. Una solución sería señalar el cierre de una posición a la clase C_Manager en caso de errores, pero como he mencionado, evitamos suposiciones, por lo que la decisión de añadir o no esta señal queda a su discreción.

A continuación actualizamos el contador de barras para que la señal se active sólo en la siguiente barra. Sin embargo, esto sólo ocurrirá si hay una respuesta del indicador; de lo contrario, en el próximo evento OnTime, la señal será probada de nuevo. Es importante evitar suposiciones sobre lo que está sucediendo. La secuencia de eventos es crucial: si actualizáramos el número de barras antes, nos perderíamos el siguiente evento OnTime, pero como lo hacemos más tarde, tenemos la oportunidad de intentar leer de nuevo el indicador.

Ahora, realizamos el cálculo para determinar si comprar o vender. Para entender este cálculo, es necesario comprender cómo se calcula la media móvil exponencial. A diferencia de otras medias móviles, la media exponencial reacciona más rápidamente a los cambios de precio, indicando si la barra ha cerrado por encima o por debajo de ella.

Un punto crucial en este cálculo es que sólo informará con la suficiente rapidez si comparamos el valor actual de la media con el valor inmediatamente anterior. Cualquier cambio mínimo puede activar la señal. Si deseas reducir la sensibilidad, puedes cambiar el valor en m_Infos.Shift. Esto da como resultado una simulación de desplazamiento de la media móvil para capturar un movimiento específico, ajustando la sensibilidad del sistema.

Este tipo de desplazamiento es común en algunas configuraciones, como Joe di Napoli. Muchos creen que es necesario observar la barra en relación con la media móvil, pero en realidad, sólo es necesario ajustar la media adecuadamente para entender si la barra está siguiendo el patrón. En el caso de la configuración de Joe di Napoli, es necesario calcular el zig-zag de la media móvil para disparar en el punto adecuado. Aún así, no necesitamos observar las barras, con la media es suficiente.

Un detalle sobre el cálculo anterior: el punto cero en el cálculo representa el valor más reciente en el buffer, es decir, el último valor calculado por el indicador. 

Si el valor más reciente es superior al anterior, el EA debería comprar inmediatamenteSi es inferior, el EA debe vender.

He aquí una cuestión curiosa: este sistema recuerda al conocido sistema de Larry Williams. Algunos optan por añadir un elemento extra: en lugar de comprar o vender inmediatamente, se puede enviar una orden pendiente al máximo o mínimo de la barra que hizo que la media móvil activara la señal. Dado que la clase C_Manager garantiza que sólo quede una orden pendiente en el servidor, sólo tendría que cambiar, no el cálculo, sino la función Triggers. Así, en lugar de una petición de operación de mercado, el sistema enviaría una orden pendiente con los datos de la barra que generó la señal.

Este no será el código que estará en el archivo adjunto, pero la cosa se vería como se muestra en el fragmento de abajo:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_BUY, iHigh(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (GetVolumeInPosition() == 0)
                                                {
                                                        DestroyOrderPendent();
                                                        CreateOrder(ORDER_TYPE_SELL, iLow(NULL, m_TF, 0));
                                                }
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                        };

Hemos añadido una nueva función al código de la clase C_Manager, que encontrarás en el apéndice, así que no te preocupes. Ahora, observa cómo la orden se generará de forma diferente a la entrada en el mercado. Ahora tenemos una entrada basada en el precio de uno de los límites de la barra, que se adapta a medida que evoluciona la situación. Si la orden no se ejecuta en la barra en la que se creó el disparador, en la siguiente barra, la orden se reposicionará automáticamente. Este modelo, sin embargo, no será efectivamente en el apéndice, sólo estoy demostrando su potencial.

Creo que ya se ha dado cuenta de la flexibilidad del sistema. Sin embargo, sólo hemos arañado la superficie de lo que es posible con este sistema de clases. Para ilustrar otro uso, veamos un segundo ejemplo.


Segundo ejemplo de automatización: Uso del indicador RSI o IFR

Ahora que ya has entendido el uso de un sistema de medias móviles, vamos a examinar la aplicación de un indicador. En este ejemplo, utilizaremos un único indicador que es bastante popular. El método, sin embargo, es aplicable a cualquier otro indicador u oscilador, la idea es universal.

El código del EA en este caso será presentado en el apéndice como EA_v2.mq5. Comencemos con el constructor, como se muestra a continuación:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                            bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                            const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, 0, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod),
                         m_Handle(INVALID_HANDLE)
                        {
                                m_Infos.Shift      = iShift;
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                ArraySetAsSeries(m_Buff, true);
                                m_nBars = iBars(NULL, m_TF);
                                m_Handle = iRSI(NULL, m_TF, 14, PRICE_CLOSE);

                        }

A diferencia del primer ejemplo, observemos lo que tenemos aquí. Vale la pena recordar que se puede replicar el mismo proceso del ejemplo anterior, o adaptar el ejemplo anterior para que sea similar a este:

  • Establecemos un valor que se utilizará como Stop loss una vez abierta la posición;
  • Establecemos un valor que se utilizará como Breakeven, Trailing Stop;
  • Declararemos una orden pendiente como punto de stop;
  • Especificamos que utilizaremos el indicador RSI con un periodo de 14, basado en el precio de cierre.
  • Los valores de sobrecompra y sobreventa son reportados y almacenados para uso futuro.

Observa lo increíblemente sencillo que es el proceso. Puede sustituir fácilmente el iRSI por cualquier otro indicador de su elección. El siguiente paso será el cálculo, que se puede ver a continuación:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                        
                                if (((iRet = iBars(NULL, m_TF)) > m_nBars) && (m_Handle != INVALID_HANDLE))
                                {
                                        if (CopyBuffer(m_Handle, 0, 0, m_Infos.Shift + 1, m_Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                        m_nBars = iRet;
                                        if ((m_Buff[0] > m_Buff[m_Infos.Shift]) && (m_Buff[0] > m_Infos.OverSold) && (m_Buff[m_Infos.Shift] < m_Infos.OverSold)) return TRIGGER_BUY;
                                        if ((m_Buff[0] < m_Buff[m_Infos.Shift]) && (m_Buff[0] < m_Infos.OverBought) && (m_Buff[m_Infos.Shift] > m_Infos.OverBought)) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

En el ámbito del cálculo, procedemos a pruebas parecidas a las del ejemplo anterior, más algunos detalles adicionales. La verdad es incontestable, pero aun así, llama la atención una observación: analizamos si la tendencia del indicador ha pasado a ser decreciente o creciente. Este factor es relevante, ya que puede indicar una corrección en el indicador, justificando así nuestro desplazamiento para identificar este tipo de movimiento. Sin embargo, de un modo u otro, comprobamos si el indicador señala una situación de sobreventa o sobrecompra antes de tomar una segunda decisión basada en la salida del indicador de esta región. Esto nos proporciona un desencadenante para comprar o vender.

Aparte de esto, no observamos ningún cambio importante. Como se puede ver, el procedimiento es relativamente sencillo y no varía significativamente entre un estudio y otro. Simplemente definimos el disparador que desencadenará la acción para adaptarlo a cualquier modelo y metodología necesaria para automatizar el EA

Sin embargo, a diferencia del ejemplo anterior, en éste buscamos implementar un movimiento de breakeven y trailing stop. El objetivo es que si se produce algún movimiento que pueda generar beneficios, podamos cerrar la posición de forma más ventajosa. Esta modificación se implementa con el siguiente agregado al código:

inline virtual void Triggers(void) final
                        {
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                TriggerTrailingStop();
                        };

Nótese que la rutina se ha mantenido prácticamente igual a la original. Sin embargo, se añadió esta llamada, que promoverá lo que queremos, en este caso, el breakeven y el trailing stop. Observa que todo se reduce a cuándo y dónde posicionar las llamadas, porque el sistema ya tiene todos los detalles para ser utilizado, adaptándose a nuestras necesidades en cada caso que realmente queramos trabajar.

Veamos un ejemplo más para consolidar mejor las ideas.


Tercer ejemplo de automatización: Cruce de medias móviles

Este ejemplo se hace evidente al utilizar EA_v3.mq5. Pero como no vamos a ceñirnos sólo al proceso de automatización más básico. Sin embargo, no nos limitaremos al proceso de automatización más rudimentario. Observarás que en la clase C_Manager se han realizado algunas modificaciones sutiles. La primera de ellas corresponde a la implementación de una rutina que permite al sistema de automatización discernir si existe una posición larga o corta, como se ilustra a continuación:

const bool IsBuyPosition(void) const
                        {
                                return m_Position.IsBuy;
                        }

Esta función, en realidad, no confirma si la posición está abierta o no, sino que sólo devuelve si la variable indica compra o venta. La intención no es autentificar la existencia de una posición abierta, sino determinar si es larga o corta, lo que explica la simplicidad de la función. No obstante, puedes comprobar si existe una posición abierta si tu sistema de automatización requiere dicha comprobación. En cualquier caso, para lo que voy a ejemplificar, esta función ya es adecuada. Esta adecuación se debe a la función que se presentará a continuación:

                void LockStopInPrice(const double Price)
                        {
                                if (m_InfosManager.IsOrderFinish)
                                {
                                        if (m_Pending.Ticket == 0) return;
                                        if ((m_Pending.PriceOpen > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Pending.PriceOpen < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = Price, m_Pending.SL = 0, m_Pending.TP = 0);
                                }else
                                {
                                        if (m_Position.SL == 0) return;
                                        if ((m_Position.SL > Price) && (m_Position.IsBuy)) return;
                                        if ((m_Position.SL < Price) && (!m_Position.IsBuy)) return;
                                        ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, Price, m_Position.TP);
                                }
                        }

La función anterior pretende posicionar el precio Stop en un punto concreto. En esencia, realiza la misma acción que realiza el código del trailing stop cuando ya se ha disparado un breakeven. Sin embargo, en ciertos tipos de operaciones, en realidad no hacemos breakeven en la posición, sino que movemos el stop loss en función de algún criterio, como el máximo o mínimo de la barra anterior, el precio indicado en alguna media móvil, o cualquier otro mecanismo de automatización. En este caso, necesitamos una función específica para realizar esta tarea. Hay que tener en cuenta que dispone de mecanismos para evitar que el valor se mueva en una dirección que aumente la pérdida. Este tipo de bloqueo es crucial, especialmente si pretende enviar valores basados en medias móviles.

Basándonos en esto, ahora tenemos el siguiente código para la función de trailing stop:

inline void TriggerTrailingStop(void)
                        {
                                double price, v1;
                                
                                if ((m_Position.Ticket == 0) || (m_InfosManager.IsOrderFinish ? m_Pending.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)));
                                        v1 = (m_InfosManager.IsOrderFinish ? m_Pending.PriceOpen : m_Position.SL);
                                        if (v1 > 0) if (MathAbs(price - v1) >= (m_Position.Gap * 2)) 
                                                LockStopInPrice(v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1)));
                                        {                                               
                                                price = v1 + (m_Position.Gap * (m_Position.IsBuy ? 1 : -1));
                                                if (m_InfosManager.IsOrderFinish) ModifyPricePoints(m_Pending.Ticket, m_Pending.PriceOpen = price, m_Pending.SL = 0, m_Pending.TP = 0);
                                                else    ModifyPricePoints(m_Position.Ticket, m_Position.PriceOpen, price, m_Position.TP);
                                        }
                                }
                        }

Las partes suprimidas se eliminaron porque ahora tenemos la llamada resaltada, lo que nos permite reutilizar mejor el código.

Ahora que he introducido las modificaciones en la clase C_Manager, vamos a explorar cómo producir este tercer ejemplo, empezando por los cambios. Estos pueden variar dependiendo de cada caso. Sin embargo, es esencial no perder de vista la planificación realizada para crear la automatización. Y como aquí la automatización requiere un poco más de recursos que en los casos anteriores, veamos los cambios necesarios, que serán suficientes para cualquier modelo que utilice dos indicadores simultáneamente.

Empecemos por las declaraciones de variables:

class C_Automaton : public C_Manager
{
        protected:
                enum eTrigger {TRIGGER_NONE, TRIGGER_BUY, TRIGGER_SELL};
        private :
                enum eSelectMedia {MEDIA_FAST, MEDIA_SLOW};
                struct st00
                {
                        int     Shift,
                                nBars;
                        double  OverBought,
                                OverSold;
                }m_Infos;
                struct st01
                {
                        double  Buff[];
                        int     Handle;
                }m_Op[sizeof(eSelectMedia) + 1];
                int     m_nBars;
                ENUM_TIMEFRAMES m_TF;

Aquí tenemos una enumeración que nos ayudará a acceder, con un lenguaje de alto nivel, a los datos de las medias, de forma que evitaremos confusiones durante la programación del cálculo.

La siguiente característica es una estructura que nos permitirá acceder tanto al buffer como al handle del indicador. Sin embargo, hay que tener en cuenta que esta estructura se declara como un array, y el tamaño de este array es precisamente la cantidad de datos presentes en la enumeración más uno Es decir, independientemente del número de elementos que vayamos a utilizar, basta con añadirlos a la enumeración. De esta forma, el array se adaptará al modelo final que vayamos a construir.

En cierto sentido, esto es más adecuado que el modelo de clase por defecto. Pero como podíamos utilizar un modelo más sencillo, este se desplegó primero. Sin embargo, dada la posibilidad de implantar un modelo más sencillo, ésta era la opción inicial. Así, creo que la comprensión queda más clara y garantizada.

Genial, ahora ya sabes cómo añadir varios indicadores de forma sencilla a la clase C_Automaton. Sin embargo, vamos a ver cómo iniciar realmente el proceso en el constructor de la clase, como se puede ver a continuación:

                C_Automaton(const ulong magic, double FinanceStop, double FinanceTake, uint Leverage,
                                                bool IsDayTrade, double Trailing, const ENUM_TIMEFRAMES iPeriod,
                                                const double OverBought = 70, const double OverSold = 30, const int iShift = 1)
                        :C_Manager(magic, FinanceStop, FinanceTake, Leverage, IsDayTrade, Trailing, true, 10),
                         m_TF(iPeriod)
                        {
                                for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                {
                                        m_Op[c0].Handle = INVALID_HANDLE;
                                        ArraySetAsSeries(m_Op[c0].Buff, true);
                                }
                                m_Infos.Shift      = (iShift < 3 ? 3 : iShift);
                                m_Infos.OverBought = OverBought;
                                m_Infos.OverSold   = OverSold;
                                m_nBars = iBars(NULL, m_TF);
                                m_Op[MEDIA_FAST].Handle = iMA(NULL, m_TF, 9, 0, MODE_EMA, PRICE_CLOSE);
                                m_Op[MEDIA_SLOW].Handle = iMA(NULL, m_TF, 20, 0, MODE_SMA, PRICE_CLOSE);
                        }

Aquí es donde comienza la magia. Así es como inicializo, por defecto, todos los punteros, independientemente de su cantidad. La forma más sencilla es a través de este bucle. No hay que preocuparse de cuántos punteros se introducen en la enumeración. Es irrelevante, porque el bucle los cubrirá todos.

Ahora viene la parte que realmente requiere atención. En este punto, todos los indicadores utilizarán el mismo intervalo de tiempo, pero es posible que necesites intervalos diferentes para cada indicador. En este caso, necesitarás ajustar un poco el código de este constructor. Los cambios, sin embargo, serán mínimos y pertenecerán sólo al modelo específico que estés creando.

Ten cuidado de informar de esto cuando captures los indicadores que se utilizarán más tarde. Necesitas capturarlos para que cada handle sea instanciado correctamente. Si esto no se hace correctamente, sin duda puedes tener problemas con tu modelo. En este caso, estoy indicando que utilizaré en el sistema la media móvil exponencial de 9 periodos y la media móvil aritmética de 20 periodos. Pero no hay ningún impedimento para combinar indicadores diferentes y distintos, siempre que, por supuesto, sea útil para tu sistema operativo.

Nota importante: Cabe mencionar aquí una especificidad: Si estás utilizando un indicador personalizado que has creado, no es necesario que esté en el gráfico del activo. Puesto que no lo encontrará entre los indicadores por defecto, para iniciar el tirador y obtener los datos de este indicador personalizado, deberá utilizar la función iCustom. Consulta la documentación sobre cómo utilizar esta función para acceder a tu indicador personalizado. De nuevo, no tiene que estar necesariamente en el gráfico.

Presta atención a este punto específico, es de suma importancia. En el Expert Advisor, cuando no informamos un valor de desplazamiento, no podemos utilizar el valor por defecto. Esto se debe a que si utilizamos el valor por defecto, tendremos dificultades para comprobar el cruce de las medias. Necesitamos indicar un valor mínimo de desplazamiento, que es 3, aunque podemos utilizar 2 si queremos un disparo más sensible. Sin embargo, no podemos utilizar 1, ya que esto impide un análisis adecuado. Para entender por qué, tenemos que ver cómo funciona el cálculo.

Una vez que el constructor inicializa correctamente los datos que vamos a utilizar, tendremos que desarrollar la parte responsable de los cálculos, para que el mecanismo de disparo haga que el EA funcione automáticamente. En el caso de un mecanismo con múltiples indicadores, es necesario que el sistema funcione de forma ligeramente diferente a cuando utilizamos un único indicador. Esto puede observarse examinando el código siguiente:

inline eTrigger CheckTrigger(void)
                        {
                                int iRet;
                                bool bOk = false;
                                        
                                if (iRet = iBars(NULL, m_TF)) > m_nBars)
                                {
                                        for (int c0 = sizeof(eSelectMedia); c0 <= 0; c0--)
                                        {
                                                if (m_Op[c0].Handle == INVALID_HANDLE) return TRIGGER_NONE;
                                                if (CopyBuffer(m_Op[c0].Handle, 0, 0, m_Infos.Shift + 1, m_Op[c0].Buff) < m_Infos.Shift + 1) return TRIGGER_NONE;
                                                bOk = true;
                                        }
                                        if (!bOk) return TRIGGER_NONE; else m_nBars = iRet;
                                        if ((m_Op[MEDIA_FAST].Buff[1] > m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] < m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_BUY;
                                        if ((m_Op[MEDIA_FAST].Buff[1] < m_Op[MEDIA_SLOW].Buff[1]) && (m_Op[MEDIA_FAST].Buff[m_Infos.Shift] > m_Op[MEDIA_SLOW].Buff[m_Infos.Shift])) return TRIGGER_SELL;
                                };
                                return TRIGGER_NONE;
                        }

En este código, utilizamos un bucle que realiza las tareas sin necesidad de preocuparse por el número de indicadores utilizados. Sin embargo, es imprescindible que todos estén correctamente inicializados en el constructor, ya que sin esto, la fase de cálculo no podrá generar disparadores de compra o venta.

En primer lugar, comprobamos si el identificador del indicador se ha inicializado correctamente. Si no es así, no tendremos un disparador válido. Una vez hecha esta comprobación, comenzamos a capturar los datos del buffer del indicador. Si te surge alguna duda en el uso de esta función, te aconsejamos que consultes la función CopyBuffer de la documentación. Esta función te será especialmente útil si estás trabajando con un indicador personalizado.

Con todos los indicadores capturados en sus respectivos buffers, podemos pasar a la fase de cálculo. Sin embargo, se nota una peculiaridad en el código antes de la fase de cálculo. Esta parte del código está diseñada para evitar que el sistema de cálculo se ejecute si tenemos una lista nula de enumeradores. Sin este bloqueo, el sistema de cálculo se activaría incluso con la lista nula, lo que compromete la robustez del sistema. Ahora, volviendo a la fase de cálculo y teniendo en cuenta que estamos utilizando un sistema de cruce de medias, hay que prestar especial atención.

Observen que evitamos probar precipitadamente el valor cero (el más reciente) presente en el buffer. La razón de esto es que, antes del cierre del tiempo del gráfico, podemos tener un falso cruce de las medias, lo que resultaría en un disparo accidental.

Es natural preguntarse si el sistema comprobará los búferes sólo cuando se genere una nueva barra. La respuesta es sí, pero si hay un cruce de medias en el momento exacto de la generación de la barra, el sistema disparará una orden. Por lo tanto, no tenemos en cuenta el valor más reciente, asumiendo el anterior. Esta es la razón para establecer el desplazamiento a por lo menos 2 para la máxima sensibilidad, o 3, como en este ejemplo. Sin embargo, no dudes en experimentar con distintos métodos de cálculo. Este ejemplo tiene fines didácticos y no debe utilizarse en una cuenta real.

Para terminar este último modelo, veamos algo más sobre el sistema. Esto puede verse justo debajo:

inline virtual void Triggers(void) final
                        {
#define def_HILO 20
                                if (!CtrlTimeIsPassed()) ClosePosition(); else switch (CheckTrigger())
                                {
                                        case TRIGGER_BUY:
                                                if (m_Memory == TRIGGER_SELL) ClosePosition();
                                                if (m_Memory != TRIGGER_BUY) ToMarket(ORDER_TYPE_BUY);
                                                m_Memory = TRIGGER_BUY;
                                                break;
                                        case TRIGGER_SELL:
                                                if (m_Memory == TRIGGER_BUY) ClosePosition();
                                                if (m_Memory != TRIGGER_SELL) ToMarket(ORDER_TYPE_SELL);
                                                m_Memory = TRIGGER_SELL;
                                                break;
                                }
                                LockStopInPrice(IsBuyPosition() ?  iLow(NULL, m_TF, iLowest(NULL, m_TF, MODE_LOW, def_HILO, 0)) : iHigh(NULL, m_TF, iHighest(NULL, m_TF, MODE_HIGH, def_HILO, 0)));
#undef def_HILO
                        };

La gran virtud de esta función es precisamente este código, en el que tenemos un trailing stop muy curioso. De esta forma la orden, o precio stop, según el caso, estará en el máximo o mínimo, dependiendo, claro está, si estamos comprando o vendiendo. El valor a utilizar es muy similar a un indicador conocido por muchos que operan en B3, llamado HILO. Este indicador, para quien no lo conozca, busca el máximo o mínimo del precio dentro de un determinado número de barras. El código responsable de esto es precisamente estos: Aquí buscamos el valor LO, y aquí el valor HI, en ambos casos HILO es 20.

Con esto cierro este tercer ejemplo.


Consideraciones finales

En esta breve serie, he presentado el desarrollo de un EA automático, intentando hacerlo de la forma más lúdica y sencilla posible. Aunque he intentado simplificar, es esencial que inviertas tiempo en comprender realmente el proceso.

He identificado los principales fallos, retos y dificultades inherentes al trabajo de un programador en la creación de un EA autónomo. Además, he destacado los valiosos conocimientos y la perspectiva transformadora que obtendrá observando el mercado.

He intentado facilitarte la creación de un sistema seguro, fiable y robusto que sea modular, compacto y ligero. Este sistema debe poder funcionar en conjunción con varios otros, ya que la versatilidad es crucial para la eficacia de un EA. No basta con tener un sistema que sólo opere con un único activo, ya que esto limita sus oportunidades de beneficio.

El último artículo de esta serie, con tres ejemplos prácticos, puede despertar un interés especial. Sin embargo, es esencial absorber los conocimientos de toda la serie para sacar el máximo provecho de este artículo en concreto. He tratado de transmitir que no es necesario ser un prodigio de la programación o tener múltiples títulos, sólo una sólida comprensión de cómo funcionan la plataforma MetaTrader 5 y el lenguaje MQL5.

Además, mostré cómo crear condiciones específicas para que su sistema funcione con eficacia, incluso cuando MQL5 o MetaTrader 5 no tienen el indicador que le gustaría utilizar. Esto se mostró en el ejemplo 3, donde enseñé cómo crear internamente el indicador HILO. Sin embargo, es crucial que el sistema esté correctamente implementado y probado. Un sistema mal ejecutado, por muy bien diseñado que esté, puede provocar pérdidas en lugar de beneficios.

Para cerrar esta serie, quiero hacer hincapié en que no he cubierto todas las posibilidades. No era mi objetivo profundizar en todos los detalles hasta el punto de crear una biblioteca de modelado para crear EAs automatizados. Retomaré esta discusión en una nueva serie de artículos, donde abordaré el desarrollo de una herramienta útil para los novatos en el mercado.

Recuerda, la prueba real de cualquier EA automatizado no ocurre en el probador de estrategias de la plataforma MetaTrader 5, sino en una cuenta DEMO con el mercado en pleno funcionamiento. Allí, el EA se prueba sin ajustes que puedan enmascarar su rendimiento real. Con esto, cierro esta serie, y nos vemos en la próxima. Adjunto todos los códigos comentados en esta serie para su estudio y análisis, para que pueda entender realmente cómo funciona un EA automatizado.


NOTA IMPORTANTE: No utilices los EA disponibles en el anexo sin los conocimientos adecuados. Dichos EA son sólo para demostración y uso educativo.

Si vas a hacer uso de ellos en una cuenta REAL, hazlo asumiendo el riesgo, ya que pueden generar pérdidas sustanciales a tu patrimonio.


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

Archivos adjuntos |
Ejemplo de creación de la estrategia comercial compleja Owl Ejemplo de creación de la estrategia comercial compleja Owl
Mi estrategia se basa en los fundamentos clásicos del trading y en el perfeccionamiento de indicadores ampliamente usados en todo tipo de mercados. En la práctica, se trata de una herramienta lista para usar que nos permite sacar el máximo rendimiento a la nueva estrategia de negociación rentable propuesta.
Cómo construir un EA que opere automáticamente (Parte 14): Automatización (VI) Cómo construir un EA que opere automáticamente (Parte 14): Automatización (VI)
Aquí pondremos realmente en práctica todos los conocimientos de esta serie. Finalmente construiremos un sistema 100% automático y funcional. Pero para hacer esto, tendrás que aprender una última cosa.
Cómo elegir un asesor comercial: Veinte signos claros de un mal robot Cómo elegir un asesor comercial: Veinte signos claros de un mal robot
En este artículo intentaremos responder a la pregunta: ¿cómo elegir el asesor comercial adecuado? ¿Cuáles son los más adecuados para nuestro portafolio y cómo podemos descartar la mayoría de los robots comerciales disponibles en el mercado? Este artículo presenta veinte señales claras de que un asesor es de mala calidad. El presente material le ayudará a tomar decisiones más informadas y a crear una colección de asesores comerciales rentables.
Perceptrón multicapa y algoritmo de retropropagación (Parte 3): Integración con el simulador de estrategias - Visión general (I) Perceptrón multicapa y algoritmo de retropropagación (Parte 3): Integración con el simulador de estrategias - Visión general (I)
El perceptrón multicapa es una evolución del perceptrón simple, capaz de resolver problemas separables no linealmente. Junto con el algoritmo de retropropagación, es posible entrenar eficientemente esta red neuronal. En la tercera parte de la serie sobre el perceptrón multicapa y la retropropagación, mostraremos cómo integrar esta técnica con el simulador de estrategias. Esta integración permitirá utilizar análisis de datos complejos y tomar mejores decisiones para optimizar las estrategias de negociación. En este resumen, analizaremos las ventajas y los retos de la aplicación de esta técnica.