English Русский 中文 Deutsch 日本語 Português
preview
Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 3): Estrategias dinámicas de seguimiento de tendencias y reversión a la media

Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 3): Estrategias dinámicas de seguimiento de tendencias y reversión a la media

MetaTrader 5Ejemplos |
383 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

Las estrategias de trading algorítmico basadas en medias móviles se distinguen de la mayoría de las estrategias de trading por su capacidad para mantener nuestras aplicaciones de trading alineadas con la tendencia del mercado a largo plazo. Desafortunadamente, cuando los mercados están fluctuando y no siguen una tendencia real a largo plazo, nuestras estrategias de seguimiento de tendencias, que antes eran confiables, nos perjudican más de lo que nos benefician. Comprender cómo los mercados cambian entre regímenes de tendencia y regímenes de rango limitado puede ayudarnos significativamente a hacer un uso más eficaz de nuestras estrategias basadas en medias móviles.

Por lo general, los operadores tienden a clasificar los mercados como «dentro de un rango» o «en tendencia» antes de decidir qué estrategias son aplicables para ese día de negociación en particular. Existe poca bibliografía dedicada a analizar cómo los mercados cambian entre estos dos regímenes. En lugar de percibir los mercados como un entorno estático que existe en uno de dos estados. Queremos enmarcar los mercados financieros como entornos dinámicos, que alternan constantemente entre dos estados posibles, sin establecerse realmente en ninguno de ellos. Nuestro objetivo, hoy en día, es crear una única estrategia de negociación dinámica que pueda detectar de forma independiente cuándo el régimen subyacente del mercado cambia de una tendencia a un rango limitado o viceversa.

Es de esperar que esta estrategia dinámica única sustituya los paradigmas tradicionales de tener una estrategia para cada condición del mercado. La estrategia propuesta se basa en el cálculo de un canal de negociación sobre una base de renovación. La idea subyacente detrás de la estrategia es la creencia de que existe una frontera que separa los movimientos de tendencia y los movimientos dentro de un rango. Y prestando atención a dónde se encuentran los niveles de precios en relación con nuestro límite, podemos realizar operaciones mejor informadas. Los límites superior e inferior de nuestro canal se calculan sumando (límite superior) y restando (límite inferior) un múltiplo del valor ATR al valor medio del indicador de media móvil. Analizaremos en profundidad la estrategia en las siguientes secciones del artículo.

Sin embargo, el lector debe comprender que el canal se calcula dinámicamente cada día utilizando indicadores técnicos estándar incluidos en todas las instalaciones de MetaTrader 5. Nuestra estrategia inicial era una simple estrategia de seguimiento de tendencias, que entraría en posiciones largas siempre que los niveles de precios cerraran por encima de la media móvil de 100 períodos, y en posiciones cortas en caso contrario. Una vez abiertas las operaciones, se gestionaron utilizando un stop loss y un take profit fijos. En una prueba retrospectiva de cuatro años con datos M1 sobre el EURUSD, solo el 52 % de las operaciones realizadas con nuestra estrategia inicial resultaron rentables. Las reglas dinámicas basadas en canales que proponemos aumentaron nuestra proporción de operaciones ganadoras hasta el 86 % durante el mismo periodo de cuatro años en el marco temporal M1, sin utilizar técnicas de ajuste de curvas ni de inteligencia artificial.

Los resultados sugieren que el esfuerzo dedicado a aprender a estimar mejor el límite merece la pena por la recompensa obtenida. Al relajar nuestra necesidad de clasificar categóricamente los mercados en categorías bien definidas y, en su lugar, intentar seguir el ritmo natural del mercado, descubrimos que nuestra aplicación de trading fue capaz de alcanzar unas proporciones impresionantes de operaciones ganadoras. Además, dada la sencillez del diseño elegido para nuestro estilo de codificación, al lector le resultará fácil ampliar la plantilla que se le proporciona con su comprensión única del mercado. 


Resumen de la estrategia comercial

Como explicamos en la introducción del artículo, las estrategias de trading basadas en medias móviles son especialmente populares entre los traders porque nos permiten mantenernos alineados con la tendencia del mercado a largo plazo. En la figura 1 se muestra un ejemplo bastante excepcional de este principio en acción. La captura de pantalla procede del tipo de cambio diario del EURUSD y muestra una tendencia alcista del mercado que comenzó a finales de noviembre de 2016 y se prolongó hasta abril de 2018. Una carrera impresionante desde cualquier punto de vista. Tenga en cuenta que el indicador de media móvil también confirmó que los niveles de precios han estado en una tendencia alcista prolongada. 

En términos generales, podemos observar que nuestras posiciones largas podrían haberse establecido cada vez que los niveles de precios cerraron por encima de nuestra media móvil y nuestras posiciones cortas podrían haberse establecido cada vez que los niveles de precios cerraron por debajo de nuestra media móvil. 

Régimen de tendencia

Figura 1: Ejemplo de nuestro sistema de seguimiento de tendencias definido por la media móvil en acción.

El ejemplo que se muestra en la figura 1 ilustra la principal ventaja de las estrategias de media móvil cuando se emplean en mercados con tendencia. Sin embargo, cuando el mercado carece de una tendencia a largo plazo, como las condiciones de mercado ilustradas en la figura 2, estas estrategias simples de seguimiento de tendencias no serán eficaces. 

Régimen de mercado con rango limitado

Figura 2: Ejemplo de un periodo prolongado sin rentabilidad al utilizar sistemas de seguimiento de tendencias.

La estrategia propuesta en este artículo puede manejar ambas condiciones del mercado excepcionalmente bien sin necesidad de indicadores adicionales ni de una lógica comercial compleja para detectar cuándo cambiar de estrategia y, de hecho, a qué estrategia cambiar. Comencemos aprendiendo lo que se necesita para que nuestra sencilla estrategia de trading con medias móviles sea dinámica y se ajuste automáticamente.


Introducción a MQL5

Nuestro sistema de negociación está compuesto por un conjunto de diferentes partes:

Parte del sistema Finalidad prevista
Constantes del sistema Se trata de constantes ocultas al usuario final, cuyo objetivo es mantener la coherencia del comportamiento de nuestro sistema en ambas pruebas retrospectivas para evitar sesgos o cambios involuntarios que alteren la lógica de negociación.
Librerías En nuestra aplicación, solo importamos la librería comercial para ayudar a abrir y gestionar nuestras posiciones.
Variables globales Estas variables están destinadas a almacenar valores de indicadores, niveles de precios y otros datos en el sistema que serán compartidos por diferentes partes individuales del sistema.
Controladores de eventos del sistema Funciones como OnTick() son controladores de eventos impulsados por el sistema que nos ayudan a realizar nuestras tareas de manera organizada.
Funciones personalizadas Se trata de funciones adaptadas a nuestras necesidades específicas para seguir con éxito la tendencia definida por la media móvil.

Para comenzar, primero definiremos nuestras constantes del sistema. Tenga en cuenta que no cambiaremos estos valores en ninguna de las versiones posteriores que crearemos de nuestra aplicación comercial.

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average applied price
#define MA_MODE    MODE_EMA               //Moving average type
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH 1.5                      //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

A continuación, importaremos una de las librerías más utilizadas en la API MQL5, la librería Trade. Es esencial para gestionar fácilmente nuestras posiciones.

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

También tendremos que crear variables globales para almacenar los precios de mercado y los valores de los indicadores técnicos.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double ask,bid;

El cuerpo de nuestra aplicación MQL5 está compuesto por controladores de eventos. Estos controladores se activan cada vez que se produce un nuevo evento en nuestro terminal, como la oferta de nuevos precios o la eliminación de la aplicación del gráfico. Algunos eventos son activados por nuestro usuario final, y otros son activados por el servidor comercial. El controlador OnInit() se activa cuando nuestro usuario final inicia nuestra aplicación de trading. Llamará a una función específica que hemos diseñado para inicializar nuestras variables globales.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }

Cuando nuestro usuario final elimina la aplicación de negociación del gráfico, se llama al controlador OnDeinit(). Utilizaremos este controlador para liberar los recursos de memoria del sistema que ya no estamos consumiendo.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
IndicatorRelease(atr_handler);
IndicatorRelease(ma_handler);
IndicatorRelease(trading_ma_handler);
  }

El controlador OnTick() es activado por el servidor del operador, no por el usuario final. Se llama cada vez que recibimos información actualizada sobre los precios. Llamaremos a funciones específicas para actualizar nuestros indicadores técnicos y almacenar la nueva información sobre precios y, posteriormente, si no tenemos posiciones abiertas, buscaremos abrir una posición.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

Nuestras reglas para abrir posiciones siguiendo la estrategia de seguimiento de tendencias son fáciles de entender. Si los niveles de precios están por encima de la media móvil de 100 períodos, abriremos posiciones largas con stop loss fijos. De lo contrario, si los niveles de precios están por debajo de la media móvil, abriremos posiciones cortas.

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//Buy on rallies
   if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
      Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),"");

   if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
      Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH)),"");
  }

La función llamada por el controlador OnInit() para configurar nuestros indicadores técnicos.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

Nuestra función Update() será llamada por el controlador OnTick() para actualizar nuestras variables globales.

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
     }
  }
//+------------------------------------------------------------------+

Este es el estado actual de nuestra aplicación comercial en su forma actual.

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average applied price
#define MA_MODE    MODE_EMA               //Moving average type
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH 1.5                      //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double ask,bid;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
IndicatorRelease(atr_handler);
IndicatorRelease(ma_handler);
IndicatorRelease(trading_ma_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {
//Buy on rallies
   if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
      Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * SL_WIDTH)),(bid + (atr[0] * SL_WIDTH)),"");

   if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
      Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * SL_WIDTH)),(ask - (atr[0] * SL_WIDTH)));
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
     }
  }
//+------------------------------------------------------------------+


Establecimiento de un punto de referencia

Observemos el rendimiento de nuestra aplicación de seguimiento de tendencias a partir de los datos históricos M1 desde el miércoles 1 de enero de 2020 hasta el lunes 6 de enero de 2025. Utilizaremos el par EURUSD para nuestra prueba retrospectiva. Tenga en cuenta que no estamos ajustando ningún parámetro de la estrategia, por lo que la configuración «Forward» (A futuro) de la prueba retrospectiva se establece en «No». 

Figura 3: Las fechas de nuestra prueba retrospectiva.

Además, configuraremos nuestro modelo para que se base en ticks reales con el fin de emular mejor las condiciones reales de negociación en nuestro probador. Además, el uso del retraso aleatorio nos da una idea de cómo funcionará nuestro sistema en situaciones de estrés. Recuerde que la latencia experimentada al operar en tiempo real puede variar, por lo que, naturalmente, queremos mantenernos cerca de las condiciones en las que esperamos que nuestro sistema funcione en producción.

Figura 4: Las condiciones de nuestra prueba retrospectiva.

Esta es la curva de capital resultante obtenida por nuestra estrategia de trading durante nuestra prueba retrospectiva de 5 años. Presta atención a la diferencia entre el balance máximo de la cuenta y el balance final de la cuenta. Los resultados muestran claramente que nuestra estrategia no es estable y que tiende a perder dinero casi tan fácilmente como a obtener beneficios. Nuestras rachas ganadoras siguiendo esta estrategia fueron efímeras y fueron seguidas por períodos de caída que se prolongaron tanto como nuestros períodos ganadores. Es probable que esos largos periodos de caída sostenida estén asociados a periodos en los que el mercado no seguía una tendencia a largo plazo, y nuestra ingenua estrategia de negociación no puede gestionar adecuadamente esas condiciones de mercado.

Figura 5: Nuestra curva de capital obtenida a partir de la versión inicial de nuestra estrategia.

Cuando examinamos los resultados detallados de nuestra prueba retrospectiva, podemos ver que nuestra estrategia fue rentable durante el periodo de cinco años, lo que nos anima a intentar mejorarla; sin embargo, la proporción de operaciones ganadoras y perdedoras es casi del 50 %. Esto no es deseable. Queremos filtrar las operaciones perdedoras, para poder tener una mayor proporción de operaciones ganadoras. Nuestra media de victorias consecutivas es de 2 y nuestra media de derrotas consecutivas también es de 2, lo que respalda nuestra observación de que nuestro sistema parece tan propenso a generarnos beneficios como a hacernos perder nuestro capital. No se puede confiar en un sistema de este tipo para operar sin supervisión.

Figura 6: Resumen detallado de nuestra prueba retrospectiva de trading.


Resumen de la solución propuesta

Esta prueba retrospectiva inicial nos lleva ahora a analizar en detalle el sistema propuesto. Sabemos que nuestro sistema obtendrá beneficios cuando los mercados sigan una tendencia, pero perderá dinero cuando los mercados no tengan tendencia. Entonces, ¿cómo podemos diseñar nuestro sistema para decidir de forma independiente si es probable que un mercado siga una tendencia o no?

Saber cómo los mercados cambian entre estos dos modos ayudará a nuestro sistema a hacer un mejor uso del indicador de media móvil. En los días en que nuestro sistema considere que es probable que el mercado se mantenga dentro de un rango, realizará sus operaciones en contra de la tendencia implícita en la media móvil. De lo contrario, realizará sus operaciones en línea con la tendencia implícita.

En otras palabras, si nuestro algoritmo detecta que el símbolo con el que estamos operando probablemente se encuentre en un modo de rango limitado, cada vez que los niveles de precios cierren por encima de la media móvil, venderemos en lugar de comprar. Sin embargo, si nuestro algoritmo detecta que el símbolo se encuentra en modo de tendencia, entonces, cuando los niveles de precios cierren por encima de la media móvil, compraremos. Básicamente, un mismo evento del mercado se interpretará y gestionará de forma diferente dependiendo del modo en el que detectemos que se encuentra el mercado.

Así que ahora la única pregunta que queda es; ¿Cómo podemos identificar el modo en el que se encuentra el mercado?. Nuestra estrategia propuesta consiste en dividir los niveles de precios en cuatro zonas diferenciadas. La idea fundamental detrás de nuestra estrategia es intuitiva:

  1. Modo de tendencia: El mercado solo puede mostrar una tendencia real en la Zona 1 o en la Zona 4.
  2. Modo rango: El mercado solo puede estar realmente limitado por un rango en la Zona 2 o la Zona 3.
En la figura 7 se muestra una visualización de las cuatro zonas.

Figura 7: Un ejemplo ilustrativo sencillo de nuestro sistema de negociación basado en zonas.

Veamos cada zona por separado, empezando por la Zona 1. Cuando los niveles de precios se encuentran en la Zona 1, lo percibimos como un sentimiento alcista del mercado y buscaremos oportunidades de compra siempre que los niveles de precios cierren por encima de la media móvil de 100 períodos. El ancho de nuestra toma de ganancias y stop loss se mantendrá desde nuestra prueba retrospectiva inicial. Tenga en cuenta que solo buscaremos oportunidades largas para seguir las tendencias alcistas mientras nos encontremos en la Zona 1. ¡No tomaremos posiciones cortas mientras los niveles de precios se encuentren en la Zona 1!

Figura 8: Explicación de la importancia de la Zona 1.

Si los niveles de precios caen de la Zona 1 a la Zona 2, entonces cambia nuestro sentimiento del mercado. Ya no creemos que se observarán tendencias reales mientras los niveles de precios permanezcan en la Zona 2. Más bien, creemos que en la Zona 2, los niveles de precios tienden a oscilar alrededor de la banda media que separa la Zona 2 y la Zona 3. Buscaremos exclusivamente oportunidades de venta cuando estemos en la Zona 2, ya que creemos que los niveles de precios mostrarán una tendencia a retroceder hacia la banda media.

Si los niveles de precios suben por encima de la media móvil de 100 períodos mientras estamos en la Zona 2, venderemos, y la colocación de nuestro stop loss se mantendrá según nuestra estrategia comercial inicial. Sin embargo, hay que modificar el posicionamiento de nuestro take profit. Colocaremos nuestra toma de ganancias en la banda media que separa la Zona 2 y la Zona 3, ya que sospechamos que es ahí donde tenderán a estabilizarse los niveles de precios mientras nos mantengamos dentro de la Zona 2. Tenga en cuenta que no tomaremos posiciones largas mientras estemos en la Zona 2. Cada zona solo nos permite un tipo de posición.

Figura 9: Visualización de cómo evoluciona nuestra estrategia de trading a medida que atravesamos las cuatro zonas de mercado que hemos definido.

Espero que, llegados a este punto, el lector haya empezado a vislumbrar un patrón y que el resto de reglas le resulten intuitivas. Hagamos un divertido cuestionario para asegurarnos de que ambos estamos en la misma onda. Si los niveles de precios caen a la Zona 3, ¿qué tipo de posiciones crees que deberíamos tomar? Espero que mentalmente hayas dicho posiciones largas.

¿Y dónde colocaremos nuestro take profit? Me gustaría creer que el lector ahora entiende intuitivamente que cuando estamos en la Zona 2 o la Zona 3, nuestra toma de ganancias se colocará en la banda media que separa la Zona 2 y la Zona 3.

En otras palabras, cuando nos encontremos en la Zona 3, solo tomaremos posiciones largas cuando los niveles de precios cierren por debajo de la media móvil de 100 períodos. Nuestro take profit se colocará en la banda media, y nuestro stop loss será del mismo tamaño que en la estrategia original. No ocuparemos ninguna posición corta mientras estemos en la Zona 3.

Figura 10: Se cree que la zona 2 y la zona 3 son zonas de reversión a la media.

Por último, cuando los niveles de precios se encuentren en la Zona 4, solo buscaremos oportunidades para ocupar posiciones cortas. Tomaremos nuestras posiciones cortas siempre que los niveles de precios estén por debajo de nuestra media móvil de 100 períodos. El ancho de nuestro take profit y stop loss será idéntico al ancho utilizado en nuestra estrategia de referencia. No ocuparemos posiciones largas mientras los niveles de precios permanezcan en la Zona 4.

Figura 11: La zona 4 es donde creemos que se formarán tendencias bajistas. Por lo tanto, no tomaremos posiciones largas en la Zona 4.

Para implementar la estrategia de las 4 Zonas, tendremos que realizar modificaciones a la estrategia de tendencia original siguiendo:

Cambio propuesto Finalidad prevista
Nuevas variables del sistema Necesitaremos nuevas variables del sistema que nos den control sobre el canal que vamos a crear.
Creación de entradas de usuario Aunque el usuario no podrá controlar todos los aspectos del canal, algunos aspectos como el ancho del canal y el número de barras utilizadas para calcular el canal serán controlados por el usuario final.
Nuevas variables globales Se crearán nuevas variables globales relacionadas con el canal para ayudarnos a mejorar la lógica comercial de nuestra aplicación.
Modificaciones a nuestras funciones personalizadas Crearemos nuevas funciones en nuestra aplicación comercial y modificaremos algunas de las funciones existentes para implementar la lógica comercial que hemos delineado.

Los detalles sobre el cálculo del canal se discutirán a medida que avancemos.


Implementando nuestra solución en MQL5

Comenzaremos definiendo las constantes del sistema que nos ayudarán a calcular el canal. Para calcular el canal es necesario que primero apliquemos un indicador de media móvil en un marco temporal superior al previsto. Entonces, en nuestro ejemplo, queremos negociar el M1. Aplicaremos un promedio móvil de 20 períodos en el marco de tiempo diario y lo usaremos para calcular nuestro canal. Si calculamos el canal en el mismo marco temporal en el que pretendemos operar, entonces podemos observar que nuestro canal se mueve demasiado como para que podamos construir una estrategia comercial estable.

#define MA_PERIOD  20                     //Moving Average period for calculating the mid point of our range zone

A continuación, debemos definir variables globales que realicen un seguimiento del canal para nosotros. En particular, pretendemos saber dónde se encuentran los límites de la Zona 2 (límite superior) y la Zona 3 (límite inferior). Además, deseamos señalar dónde se encuentra el límite entre la Zona 2 y la Zona 3 (límite medio). La zona 1 se define a partir del límite superior y no tiene límite superior. Del mismo modo, la Zona 4 comienza donde termina la Zona 3 y no tiene límite inferior.

double range_zone_mid,range_zone_ub,range_zone_lb;
int    active_zone;

Ahora definiremos los parámetros del canal que permitiremos que el usuario final cambie. Como el período de cálculo a partir de datos históricos y el ancho del canal que desea el usuario. Es importante permitir al usuario controlar el ancho del canal porque el ancho del canal está asociado con la cantidad de riesgo en la cuenta. Para nuestra demostración, simplemente haremos que el ancho del canal sea equivalente a la lectura de 2 ATR. La mayoría de los mercados tienden a moverse 1 ATR en un día y recuerda que no queremos que nuestro canal se mueva demasiado.

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input group "Technical Analysis"
input double atr_multiple = 
2 ;            //ATR Multiple
input int    bars_used = 30;              //How Many Bars should we use to calculate the channel?

Necesitamos una función que determine en qué zona estamos actualmente. La lógica para determinar las zonas ya se ha explicado ampliamente.

//+------------------------------------------------------------------+
//| Get our current active zone                                      |
//+------------------------------------------------------------------+
void get_active_zone(void)
  {
   if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub)
     {
      active_zone = 1;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid))
     {
      active_zone = 2;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb))
     {
      active_zone = 3;
      return;
     }

   if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb)
     {
      active_zone = 4;
      return;
     }
  }
//+------------------------------------------------------------------+

También necesitamos actualizar nuestra función de configuración. Ignoremos las partes de la función que no han cambiado. Solo estamos introduciendo un nuevo indicador técnico. El primer indicador técnico que aplicamos fue un promedio móvil de 100 períodos en el marco temporal M1. Nuestro nuevo indicador es una media móvil de 20 períodos aplicada al marco temporal diario.

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   //We have omitted parts of the code that have not changed
   ma_handler  =  iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

A continuación, debemos realizar ajustes en nuestra función de actualización. Hemos omitido intencionalmente partes de la función que permanecieron sin cambios para que podamos centrarnos en las nuevas partes de la función. Comenzaremos inicializando un vector con ceros. A continuación, utilizando nuestro nuevo vector, copiamos el número de barras que nuestro usuario nos ha indicado que utilicemos para nuestros cálculos a partir de la nueva media móvil que hemos aplicado al marco temporal diario en el paso anterior.

A continuación, tomamos el valor medio de la media móvil diaria de 20 períodos y ese será el valor medio que separa la Zona 2 y la Zona 3. Los límites de la Zona 2 y la Zona 3 se calcularán sumando (Zona 2) y restando (Zona 3) un múltiplo de la lectura del ATR desde el punto medio, que calculamos utilizando la media móvil de 20 períodos.

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      //Omitted parts of the function that remained unchanged
      vector ma_average = vector::Zeros(1);
      ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used);
      range_zone_mid = ma_average.Mean();
      range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple));
      range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple));
      get_active_zone();
      Comment("Zone: ",active_zone);
      ObjectDelete(0,"RANGE HIGH");
      ObjectDelete(0,"RANGE LOW");
      ObjectDelete(0,"RANGE MID");
      ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid);
      ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb);
      ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub);
     }
  }

La última modificación que debemos realizar se aplicará a la forma en que encontraremos nuestras configuraciones. La última modificación que debemos realizar se aplicará a la forma en que encontraremos nuestras configuraciones. Para resumir la idea principal, solo seguiremos la tendencia en las zonas 1 y 4. Es decir, si el precio cierra por encima de la media móvil de 100 períodos en la Zona 1, compraremos. De lo contrario, si nos encontramos en las zonas 2 o 3, iremos en contra de la tendencia, lo que significa que si los niveles de precios cierran por encima de la media móvil de 100 períodos en la zona 2, venderemos en lugar de comprar como hicimos en la zona 1.

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {

// Follow the trend
   if(active_zone == 1)
     {
      //Buy on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),"");
     }

// Go against the trend
   if(active_zone == 2)
     {
      //Sell on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid);
     }

// Go against the trend
   if(active_zone == 3)
     {
      //Buy the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,"");
     }

// Follow the trend
   if(active_zone == 4)
     {
      //Sell the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH)));
     }
  }

En resumen, así es como queda la versión revisada de nuestra estrategia comercial.

//+------------------------------------------------------------------+
//|                              Dynamic Moving Average Strategy.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define MA_PRICE    PRICE_CLOSE           //Moving average shift
#define MA_MODE    MODE_EMA               //Moving average shift
#define MA_SHIFT   0                      //Moving average shift
#define ATR_PERIOD 14                     //Period for our ATR
#define TF_1       PERIOD_D1              //Hihger order timeframe
#define MA_PERIOD  20                     //Moving Average period for calculating the mid point of our range zone
#define TRADING_MA_PERIOD  100            //Moving average period
#define SL_WIDTH   1.5                    //How wide should the stop loss be?
#define CURRENT_VOL 0.1                   //Trading volume

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_handler,trading_ma_handler,atr_handler;
double ma[],atr[],trading_ma[];
double range_zone_mid,range_zone_ub,range_zone_lb;
double ask,bid;
int    active_zone;

//+------------------------------------------------------------------+
//| User inputs                                                      |
//+------------------------------------------------------------------+
input group "Technical Analysis"
input double atr_multiple = 1;            //ATR Multiple
input int    bars_used = 30;              //How Many Bars should we use to calculate the channel?

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   IndicatorRelease(ma_handler);
   IndicatorRelease(atr_handler);
   IndicatorRelease(trading_ma_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
   if(PositionsTotal() == 0)
      find_setup();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Find setup                                                       |
//+------------------------------------------------------------------+
void find_setup(void)
  {

// Follow the trend
   if(active_zone == 1)
     {
      //Buy on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),(bid + (atr[0] * SL_WIDTH)),"");
     }

// Go against the trend
   if(active_zone == 2)
     {
      //Sell on rallies
      if(iClose(Symbol(),PERIOD_CURRENT,0) > trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * 1.5)),range_zone_mid);
     }

// Go against the trend
   if(active_zone == 3)
     {
      //Buy the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Buy(CURRENT_VOL,Symbol(),ask,(bid - (atr[0] * 1.5)),range_zone_mid,"");
     }

// Follow the trend
   if(active_zone == 4)
     {
      //Sell the dip
      if(iClose(Symbol(),PERIOD_CURRENT,0) < trading_ma[0])
         Trade.Sell(CURRENT_VOL,Symbol(),bid,(ask + (atr[0] * atr_multiple)),(ask - (atr[0] * SL_WIDTH)));
     }
  }

//+------------------------------------------------------------------+
//| Setup our global variables                                       |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler =  iATR(Symbol(),TF_1,ATR_PERIOD);
   trading_ma_handler  =  iMA(Symbol(),PERIOD_CURRENT,TRADING_MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
   ma_handler  =  iMA(Symbol(),TF_1,MA_PERIOD,MA_SHIFT,MA_MODE,MA_PRICE);
  }

//+------------------------------------------------------------------+
//| Update                                                           |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),PERIOD_CURRENT,0);
   ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
   bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);

   if(time_stamp != current_time)
     {
      time_stamp = current_time;
      CopyBuffer(ma_handler,0,0,1,ma);
      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(trading_ma_handler,0,0,1,trading_ma);
      vector ma_average = vector::Zeros(1);
      ma_average.CopyIndicatorBuffer(ma_handler,0,1,bars_used);
      range_zone_mid = ma_average.Mean();
      range_zone_ub = (range_zone_mid + (atr[0] * atr_multiple));
      range_zone_lb = (range_zone_mid - (atr[0] * atr_multiple));
      get_active_zone();
      Comment("Zone: ",active_zone);
      ObjectDelete(0,"RANGE HIGH");
      ObjectDelete(0,"RANGE LOW");
      ObjectDelete(0,"RANGE MID");
      ObjectCreate(0,"RANGE MID",OBJ_HLINE,0,0,range_zone_mid);
      ObjectCreate(0,"RANGE LOW",OBJ_HLINE,0,0,range_zone_lb);
      ObjectCreate(0,"RANGE HIGH",OBJ_HLINE,0,0,range_zone_ub);
     }
  }

//+------------------------------------------------------------------+
//| Get our current active zone                                      |
//+------------------------------------------------------------------+
void get_active_zone(void)
  {
   if(iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_ub)
     {
      active_zone = 1;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_ub) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_mid))
     {
      active_zone = 2;
      return;
     }

   if((iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_mid) && (iClose(Symbol(),PERIOD_CURRENT,0) > range_zone_lb))
     {
      active_zone = 3;
      return;
     }

   if(iClose(Symbol(),PERIOD_CURRENT,0) < range_zone_lb)
     {
      active_zone = 4;
      return;
     }
  }
//+------------------------------------------------------------------+

Veamos si estos cambios nos proporcionarán el control que deseamos. Fijaremos el periodo durante el cual se realizará la prueba retrospectiva.

Figura 12: El periodo de nuestra prueba retrospectiva coincide con el periodo inicial que utilizamos.

Del mismo modo, ajustaremos las condiciones de la prueba retrospectiva para que sean coherentes con nuestra prueba anterior.

Figura 13: Asegúrese de que ambas estrategias se prueban en las mismas condiciones.

Nuestra nueva estrategia tiene dos aportaciones. El primer ajuste controla el ancho del canal, y el segundo controla cuántos compases se utilizan para calcular el canal. 

Figura 14: Los controles que nuestro usuario final puede ajustar.

La nueva curva de capital creada por nuestra estrategia revisada tiene períodos de pérdidas como cualquier otra estrategia, pero fíjese en lo rápido que se recupera de una pérdida. No se queda estancada como lo hacía nuestra antigua estrategia comercial. Pierde una operación, pero rápidamente se realinea con el mercado. Esto se evidencia en el hecho de que nuestros períodos de pérdidas son seguidos por una racha de operaciones rentables.

Figura 15: La curva de capital generada por nuestra nueva estrategia comercial.

Cuando analizamos los resultados detallados de nuestra nueva estrategia comercial, podemos ver que la proporción de nuestras operaciones ganadoras aumentó del 52 % al 86 %, y nuestras operaciones perdedoras disminuyeron del 47 % al 13 %. Nuestra ganancia media es menor que nuestra pérdida media, pero hay que tener en cuenta que nuestro stop loss y nuestro take profit son fijos, por lo que este problema podría resolverse manteniendo el stop loss fijo si estamos perdiendo y permitiendo que se desplace si estamos obteniendo ganancias. Además, nuestra media de victorias consecutivas pasó de 2 a 9, mientras que, por otro lado, nuestra media de derrotas consecutivas cayó de 2 a 1. Nuestra estrategia original realizó 300 operaciones. Mientras que nuestra nueva estrategia realizó un total de 1301 operaciones. Así que nuestra nueva estrategia consiste en realizar más operaciones y ganar más a menudo. 

Figura 16: Resumen detallado del rendimiento de nuestra nueva estrategia.


Conclusión

En el debate de hoy, hemos partido de una estrategia ingenua de seguimiento de tendencias y la hemos orientado para tomar decisiones más informadas utilizando los datos históricos disponibles en nuestra terminal MetaTrader 5. En teoría, podemos implementar esta estrategia con lápiz y papel, pero afortunadamente para nosotros, la API MQL5 nos agiliza todo este proceso. Desde el cálculo rápido de medidas estadísticas utilizando funciones vectoriales hasta la ejecución óptima de operaciones, nuestra aplicación MetaTrader 5 realiza gran parte del trabajo pesado por nosotros en segundo plano. En futuros artículos, consideraremos aumentar aún más nuestra proporción de operaciones rentables y ejercer un control adicional sobre el tamaño de nuestras operaciones perdedoras. 

Archivo adjunto Descripción
Benchmark Moving Average Strategy La implementación inicial de nuestra estrategia de seguimiento de tendencias
Dynamic Moving Average Strategy Nuestra nueva estrategia dinámica propuesta

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16856

Métodos de conjunto para mejorar las tareas de clasificación en MQL5 Métodos de conjunto para mejorar las tareas de clasificación en MQL5
En este artículo, presentamos la implementación de varios clasificadores de conjunto en MQL5 y analizamos su eficacia en diferentes situaciones.
Operar con noticias de manera sencilla (Parte 6): Ejecución de operaciones (III) Operar con noticias de manera sencilla (Parte 6): Ejecución de operaciones (III)
En este artículo se implementará la filtración de noticias para eventos de noticias individuales basándose en sus identificadores. Además, se mejorarán las consultas SQL anteriores para proporcionar información adicional o reducir el tiempo de ejecución de la consulta. Además, se hará funcional el código creado en los artículos anteriores.
Dominando los registros (Parte 2): Formateo de registros Dominando los registros (Parte 2): Formateo de registros
En este artículo, exploraremos cómo crear y aplicar formateadores de registros en la biblioteca. Veremos todo, desde la estructura básica de un formateador hasta ejemplos de implementación práctica. Al finalizar, tendrás el conocimiento necesario para formatear registros dentro de la biblioteca y comprenderás cómo funciona todo detrás de escena.
Reimaginando las estrategias clásicas en MQL5 (Parte 13): Minimizar el retraso en los cruces de medias móviles Reimaginando las estrategias clásicas en MQL5 (Parte 13): Minimizar el retraso en los cruces de medias móviles
Los cruces de medias móviles son ampliamente conocidos por los operadores de nuestra comunidad y, sin embargo, la esencia de la estrategia ha cambiado muy poco desde su creación. En este artículo, le presentaremos un ligero ajuste a la estrategia original, cuyo objetivo es minimizar el retraso presente en la estrategia de trading. Todos los seguidores de la estrategia original podrían considerar revisar la estrategia de acuerdo con las ideas que discutiremos hoy. Al utilizar dos medias móviles con el mismo periodo, reducimos considerablemente el retraso en la estrategia de trading, sin violar los principios fundamentales de la estrategia.