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

MetaTrader 5Ejemplos |
368 3
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

En nuestras discusiones anteriores, hemos adoptado diferentes perspectivas sobre cómo podemos maximizar nuestra eficiencia al utilizar estrategias de cruce de medias móviles. En resumen, hemos realizado un ejercicio con más de 200 símbolos diferentes y hemos observado que nuestro ordenador parece aprender a predecir mejor los valores medios móviles futuros que su tendencia a predecir correctamente los niveles de precios futuros. He proporcionado un enlace rápido al artículo, aquí. Ampliamos esta idea y modelamos dos medias móviles, con el único objetivo de predecir los cruces antes que los participantes ocasionales del mercado y ajustar nuestras operaciones en consecuencia. También he incluido un enlace al segundo artículo, al que puede acceder fácilmente aquí

En nuestro debate de hoy, ampliaremos una vez más la estrategia original para minimizar el retraso inherente a la estrategia comercial. Las estrategias cruzadas tradicionales requieren que exista una diferencia temporal entre los dos períodos de la media móvil. Sin embargo, nos alejaremos de esta escuela de pensamiento convencional y utilizaremos el mismo período en ambas medias móviles.

En este punto, algunos lectores pueden preguntarse si esto todavía puede considerarse una estrategia de cruce de medias móviles, ya que ¿cómo se cruzarán las medias móviles si ambas comparten el mismo período? La respuesta es sorprendentemente sencilla: aplicamos una media móvil al precio de apertura y otra al precio de cierre, respectivamente.
Cuando la media móvil de apertura está por encima de la media móvil de cierre, los niveles de precios terminan más bajos de lo que comenzaron. Esto es idéntico a saber que los niveles de precios están bajando. Y lo contrario es cierto cuando la media móvil de cierre está por encima de la apertura.
Un período común en ambas medias móviles es poco convencional, sin embargo, lo he seleccionado para nuestro ejercicio de hoy con el fin de demostrar al lector una posible forma de acallar cualquier crítica sobre el retraso asociado a los cruces de medias móviles.

Antes de entrar en la parte técnica de nuestro análisis, este artículo puede leerse como un ejemplo de una clase generalizada de estrategias de negociación que pueden extenderse fácilmente a muchos otros indicadores técnicos. Por ejemplo, el indicador de fuerza relativa también se puede aplicar de forma independiente a la apertura, el cierre, el máximo o el mínimo, y observaremos que el RSI que sigue el precio de apertura tenderá a subir por encima del RSI que sigue el precio de cierre cuando los mercados se encuentren en tendencia bajista.


Resumen de nuestra prueba retrospectiva

Para poder apreciar la importancia de nuestro debate de hoy, primero estableceremos un punto de referencia creado por la estrategia tradicional de cruce. A continuación, compararemos el rendimiento anterior con lo que esperamos conseguir utilizando nuestra versión renovada de la estrategia.

He seleccionado el par EURUSD para nuestro debate de hoy. El EURUSD es el par de divisas más negociado del mundo. Es significativamente más volátil que la mayoría de los pares de divisas y, por lo general, no es una buena opción para estrategias simples basadas en cruces. Como ya hemos comentado anteriormente, nos centraremos en el marco temporal diario. Realizaremos una prueba retrospectiva de nuestra estrategia con datos históricos de aproximadamente cuatro años, desde el 1 de enero de 2020 hasta el 24 de diciembre de 2024. El periodo de la prueba retrospectiva se destaca a continuación en la figura 1.

Figura 1: Visualización de nuestro periodo de backtest de 4 años del EURUSD en nuestra terminal MetaTrader 5 utilizando el marco temporal mensual.

Aunque las estrategias tradicionales de cruce son intuitivas de comprender y están respaldadas por principios fundamentales razonablemente sólidos, estas estrategias a menudo requieren una optimización interminable para garantizar un uso eficaz. Además, los periodos «adecuados» que deben utilizarse para el indicador de movimiento lento y rápido no son evidentes a primera vista y pueden cambiar drásticamente. 

En resumen, la estrategia original se basa en las intersecciones creadas por dos medias móviles que siguen el precio de cierre del mismo valor, pero con períodos diferentes. Cuando la media móvil con el período más corto se encuentra en la parte superior, interpretamos esto como una señal de que los niveles de precios han estado en una tendencia alcista y es probable que continúen subiendo. Lo contrario se aplica cuando la media móvil con el período más largo está en la parte superior, lo que interpretamos como una señal bajista. En la figura 2 se muestra un ejemplo ilustrativo.

Figura 2: Ejemplo de la estrategia tradicional de cruce de medias móviles en acción. La línea amarilla es la media móvil rápida y la blanca es la lenta.

En la figura 2 anterior, hemos seleccionado aleatoriamente un periodo de tiempo que muestra las limitaciones de la estrategia clásica. A la izquierda de la línea vertical del gráfico, se observa que la acción del precio se mantuvo estancada en un rango durante aproximadamente dos meses. Esta acción letárgica de los precios genera señales de trading que se invierten rápidamente y que muy probablemente no sean rentables. Sin embargo, tras este periodo de pésimos resultados, finalmente vimos cómo los niveles de precios rompían al alza en una tendencia real a la derecha de la línea vertical. Las estrategias tradicionales de cruce funcionan mejor en condiciones de mercado con tendencia. Sin embargo, la estrategia propuesta en este artículo aborda estos problemas con elegancia.



Estableciendo un punto de referencia

Nuestra aplicación comercial se puede conceptualizar como 4 componentes principales que trabajarán juntos para ayudarnos a operar:

Característica Descripción
Constantes del sistema Nos ayudará a aislar las mejoras que aportan los cambios que estamos realizando en la lógica comercial de nuestra aplicación.
Variables globales Responsable de realizar un seguimiento de los valores de los indicadores, los precios actuales del mercado y potencialmente más información que necesitamos.
Controladores de eventos Efectúa diversas tareas en el momento oportuno para cumplir nuestro objetivo de negociar eficazmente los cruces de medias móviles.
Funciones personalizadas Cada función personalizada de nuestro sistema tiene una tarea específica delegada y todas las funciones juntas nos ayudan a lograr nuestro objetivo.

La versión de referencia de nuestra aplicación será minimalista en su implementación. Nuestra primera orden del día es configurar constantes del sistema que permanecerán fijas en ambas pruebas que realizaremos. Nuestras constantes del sistema son importantes para hacer comparaciones justas entre diferentes estrategias comerciales y nos impiden cambiar involuntariamente configuraciones que no deberían cambiarse en las pruebas, como el tamaño de nuestro stop loss que debe ser constante en ambas pruebas para garantizar una comparación justa.

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01      //--- Trading volume

También definiremos algunas variables globales importantes que utilizaremos para obtener los valores de los indicadores y los precios actuales del mercado.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

Utilizaremos la biblioteca comercial para gestionar nuestras posiciones. 

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

En MQL5, los asesores expertos se construyen a partir de controladores de eventos. Hay muchos tipos diferentes de eventos que ocurren en la terminal MetaTrader 5. Estos eventos pueden activarse por acciones realizadas por el usuario o si se cotizan nuevos precios. Cada evento está emparejado con un controlador de eventos que se llama cada vez que se activa el evento. Por lo tanto, he diseñado nuestra aplicación de tal manera que cada controlador de eventos tiene su propia función designada que llamará a su vez para realizar las tareas necesarias para una estrategia de cruce de promedio móvil.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

La función de configuración se llama cuando se inicia nuestro sistema. El controlador OnInit se llamará cuando la aplicación comercial se aplique por primera vez a un gráfico y pasará la cadena de comando a nuestra función de configuración personalizada que aplicará nuestros indicadores técnicos por nosotros.

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

Si ya no utilizamos nuestra aplicación comercial, se llama al controlador de eventos on Deinit, que llamará a nuestra función de liberación que liberará los recursos del sistema que anteriormente consumían nuestros indicadores técnicos.

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

Cada vez que se coticen nuevos precios de mercado, se llamará al controlador OnTick, que a su vez llamará a la función de actualización para almacenar la nueva información de mercado que esté disponible. Posteriormente, si no tenemos posiciones abiertas, buscaremos una configuración de trading. De lo contrario, gestionaremos las posiciones que tengamos abiertas.

/+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

Nuestras reglas para ingresar a puestos son sencillas y ya se han explicado en detalle anteriormente. Entramos en posiciones largas si nuestra media rápida está por encima de la media lenta, y si ocurre lo contrario ocuparemos posiciones cortas.

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

Finalmente, nuestro stop loss se ajustará dinámicamente utilizando el indicador Average True Range. Agregaremos un múltiplo fijo de la lectura ATR por encima y por debajo de nuestro precio de entrada para marcar nuestros niveles de stop loss y take profit. Además, también agregaremos la lectura promedio de ATR durante los últimos 90 días (1 ciclo económico), nuestra intención al hacerlo es tener en cuenta los niveles de volatilidad recientes en el mercado. Por último, utilizaremos un operador ternario para ajustar los niveles de take profit y stop loss. Nuestra regla es que los stops sólo deben actualizarse si la nueva posición será más rentable que la anterior. Los operadores ternarios nos permiten expresar esta lógica de forma compacta. Además, los operadores ternarios también nos brindan la flexibilidad de ajustar fácilmente el take profit y el stop loss independientemente uno del otro.

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

Al poner todo junto, nuestro código actual se ve así hasta ahora.

//+------------------------------------------------------------------+
//|                                               Channel And MA.mq5 |
//|                                        Gamuchirai Zororo Ndawana |
//|                          https://www.mql5.com/en/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com/en/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| This version off the application is mean reverting               |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| System constants                                                 |
//+------------------------------------------------------------------+
#define TF_1           PERIOD_D1 //--- Our main time frame
#define TF_2           PERIOD_H1 //--- Our lower time frame
#define ATR_PERIOD     14        //--- The period for our ATR
#define ATR_MULTIPLE   3         //--- How wide should our stops be?
#define VOL            0.01         //--- Trading volume

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    trade;
int    ma_f_handler,ma_s_handler,atr_handler;
double ma_f[],ma_s[],atr[];
double bid,ask;
double o,h,l,c;
double original_sl;

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup
   setup();
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Release our indicators
   release();
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Update system variables
   update();
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Custom functions                                                 |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);

   ma_f_handler = iMA(Symbol(),TF_1,10,0,MODE_EMA,PRICE_CLOSE);
   ma_s_handler = iMA(Symbol(),TF_1,60,0,MODE_EMA,PRICE_CLOSE);
  }

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_f_handler);
   IndicatorRelease(ma_s_handler);
  }

//+------------------------------------------------------------------+
//| Update system variables                                          |
//+------------------------------------------------------------------+
void update(void)
  {
//--- Update the system
   static datetime time_stamp;
   datetime current_time = iTime(Symbol(),TF_2,0);
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      CopyBuffer(atr_handler,0,0,1,atr);
      CopyBuffer(ma_s_handler,0,0,1,ma_s);
      CopyBuffer(ma_f_handler,0,0,1,ma_f);

      o  = iOpen(Symbol(),TF_1,0);
      h  = iHigh(Symbol(),TF_1,0);
      l  = iLow(Symbol(),TF_1,0);
      c  = iClose(Symbol(),TF_1,0);
      bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
      ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);

      if(PositionsTotal() == 0)
         find_position();
      if(PositionsTotal() > 0)
         manage_position();
     }
  }

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_s[0] > ma_f[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_s[0] < ma_f[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

//+------------------------------------------------------------------+
//| Manage our positions                                             |
//+------------------------------------------------------------------+
void manage_position(void)
  {
//--- Select the position
   if(PositionSelect(Symbol()))
     {
      //--- Get ready to update the SL/TP
      double initial_sl  = PositionGetDouble(POSITION_SL);
      double initial_tp  = PositionGetDouble(POSITION_TP);
      //--- Calculate the average ATR move
      vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90);
      double buy_sl      = (ask - ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double sell_sl     = (bid + ((ATR_MULTIPLE * atr[0]) + atr_mean.Mean()));
      double buy_tp      = (ask + ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double sell_tp     = (bid - ((ATR_MULTIPLE * 0.5 * atr[0]) + atr_mean.Mean()));
      double new_sl      = ((trade == 1) && (initial_sl <  buy_sl)) ? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl);
      double new_tp      = ((trade == 1) && (initial_tp <  buy_tp)) ? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp);

      if(initial_sl == 0 && initial_tp == 0)
        {
         if(trade == 1)
           {
            original_sl = buy_sl;
            Trade.PositionModify(Symbol(),buy_sl,buy_tp);
           }

         if(trade == -1)
           {
            original_sl = sell_sl;
            Trade.PositionModify(Symbol(),sell_sl,sell_tp);
           }

        }
      //--- Update the position
      else
         if((initial_sl * initial_tp) != 0)
           {
            Trade.PositionModify(Symbol(),new_sl,new_tp);
           }
     }
  }
//+------------------------------------------------------------------+

Dado que nuestra estrategia actual no emplea IA ni ninguna técnica de ajuste de curvas, podemos realizar una prueba retrospectiva simple sin preocuparnos por el sobreajuste a los datos que tenemos disponibles. Además, no tenemos parámetros de entrada que necesiten ajustarse. Por lo tanto, hemos establecido «Forward» en «No» porque no necesitamos emplear las capacidades de pruebas prospectivas del terminal MetaTrader 5 en este momento..

Figura 3: Selección de las fechas para nuestra prueba retrospectiva.

Además, es una buena práctica probar sus aplicaciones comerciales en el entorno más estresante que pueda simular. Por lo tanto, hemos seleccionado el modo «Retraso aleatorio» para nuestra prueba retrospectiva de hoy, con nuestro modelo configurado para utilizar ticks reales. Cuando se utilizan ticks reales, el tiempo necesario para descargar datos históricos puede ser mayor que cuando se utilizan otros modos, como «Solo precios de apertura». Sin embargo, es probable que los resultados sean más cercanos a los resultados reales en garrapatas vivas.

Nuestro segundo lote de configuraciones

Figura 4: Nuestro segundo lote de configuraciones para nuestra prueba retrospectiva de cruce EURUSD.

Cuando analizamos los resultados que hemos obtenido utilizando la estrategia de cruce simple, podemos ver rápidamente los problemas potenciales que podríamos encontrar si siguiéramos la estrategia en su forma original. Tenga en cuenta que desde el comienzo de la prueba retrospectiva, hasta julio de 2022, nuestra estrategia fue trabajar arduamente simplemente para alcanzar el punto de equilibrio. Este es un período de reducción de casi la mitad de nuestra prueba retrospectiva, o 2 años. Esto es indeseable y no es característico del tipo de estrategia en la que podemos confiar para negociar nuestro dinero sin supervisión.

Figura 5: Análisis de nuestra curva de ganancias y pérdidas producida al seguir la estrategia de cruce de medias móviles.

Nuestra estrategia en su forma original es apenas rentable y pierde cerca del 61% de todas las operaciones que realiza. Esto nos genera expectativas negativas respecto de la estrategia, y nuestro pesimismo se ve aún más validado por el hecho de que nuestro ratio de Sharpe está muy cerca de 0. Pero observe cómo drásticamente podemos mejorar nuestra estrategia haciendo unos simples ajustes a la lógica comercial que estamos empleando. 

Figura 6: Resumen detallado de nuestro rendimiento utilizando la estrategia tradicional de cruce de medias móviles.



Mejorando la estrategia original

En la figura 7 a continuación, le proporciono una ilustración visual de nuestra nueva estrategia de cruce sugerida. Las líneas azul y verde son medias móviles de 5 períodos que siguen el precio de cierre (azul) y el precio de apertura (verde). Observe que cuando la media móvil azul está por encima de la media móvil verde, los niveles de precios estaban subiendo. Además, preste atención a la capacidad de respuesta de nuestros cruces ante los cambios en los niveles de precios. Las estrategias tradicionales de cruce tardarán un tiempo variable en reflejar cualquier cambio en la tendencia del mercado. Sin embargo, cuando seguimos los niveles de precios utilizando nuestra nueva estrategia, podemos observar rápidamente los cambios en la tendencia, e incluso los periodos de consolidación, en los que nuestras dos medias móviles siguen cruzándose entre sí, pero sin avanzar realmente.

Figura 7: Visualización de nuestra nueva estrategia de cruce en el marco temporal diario del EURUSD.

So far, our system has been able to trade profitably, but we can do better. El único cambio que vamos a realizar en nuestro sistema es modificar las condiciones en las que se activan nuestras posiciones:

Cambio Descripción
Comercio
Reglas
Nuestras reglas tradicionales compran cuando la media móvil rápida está por encima de la lenta. En su lugar, ahora compraremos cuando nuestra media móvil abierta esté por encima de la media móvil de cierre.

Para lograr la mejora deseada, debemos realizar algunos cambios en la forma original de nuestra estrategia comercial actual:

Cambio Descripción
Variables adicionales del sistema Necesitaremos una nueva variable de sistema responsable de fijar el período de la media móvil de apertura y cierre.
Nuevas variables globales Se crearán nuevas variables globales para realizar un seguimiento de la nueva información a la que estamos prestando atención.
Modificar funciones personalizadas Algunas de las funciones personalizadas que hemos creado hasta ahora deben ampliarse a la luz del nuevo diseño del sistema que estamos siguiendo.

En su mayor parte, todas las demás partes de nuestro sistema se conservarán. Queremos aislar las mejoras que se producen al cambiar nuestra perspectiva sobre los cruces de medias móviles. Por lo tanto, para alcanzar nuestro objetivo, comenzaremos por crear una nueva constante del sistema para fijar el período de nuestras medias móviles.

//--- Omitted code that has not changed
#define MA_PERIOD      2         //--- The period for our moving average following the close

A continuación, debemos definir nuevas variables globales para la nueva información que estamos registrando. Ahora estamos creando controladores de promedio móvil para cada uno de los 4 precios cotizados (apertura, máximo, mínimo y cierre).

//--- Omitted code that have not changed
int    ma_h_handler,ma_l_handler,ma_c_handler,ma_o_handler;
double ma_h[],ma_l[],ma_c[],ma_o[];

Cuando se lance nuestra aplicación de trading, tendremos que cargar algunos indicadores adicionales además de los que ya conocemos.

//+------------------------------------------------------------------+
//| Setup our system                                                 |
//+------------------------------------------------------------------+
void setup(void)
  {
   atr_handler  = iATR(Symbol(),TF_1,ATR_PERIOD);
   ma_h_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   ma_c_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_CLOSE);
   ma_o_handler = iMA(Symbol(),TF_1,MA_PERIOD,0,MODE_EMA,PRICE_OPEN);
  }

Lo mismo se aplica a nuestra función personalizada responsable de eliminar nuestro asesor experto, que se ampliará para dar cabida a los nuevos indicadores que hemos introducido en el sistema.

//+------------------------------------------------------------------+
//| Release variables we do not need                                 |
//+------------------------------------------------------------------+
void release(void)
  {
   IndicatorRelease(atr_handler);
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
   IndicatorRelease(ma_c_handler);
   IndicatorRelease(ma_o_handler);
  }

Por último, las condiciones que utilizaremos para abrir nuestras operaciones deben actualizarse para que estén en consonancia con nuestra nueva lógica de negociación.

//+------------------------------------------------------------------+
//| Find a position                                                  |
//+------------------------------------------------------------------+
void find_position(void)
  {

   if((ma_o[0] > ma_c[0]))
     {
      Trade.Sell(VOL,Symbol(),bid,0,0,"");
      trade = -1;
     }

   if((ma_o[0] < ma_c[0]))
     {
      Trade.Buy(VOL,Symbol(),ask,0,0,"");
      trade = 1;
     }
  }

Veamos ahora qué diferencia supone esto para nuestros resultados finales. Primero configuraremos nuestra nueva aplicación de trading para operar durante el mismo periodo de tiempo que utilizamos en nuestra primera prueba.

Figura 8: Configuración de nuestro nuevo y mejorado algoritmo de trading para operar durante el mismo periodo de tiempo que utilizamos en nuestra primera prueba.

Además, querremos realizar ambas pruebas retrospectivas en condiciones idénticas para garantizar que nuestra prueba sea imparcial. De lo contrario, si se le da una ventaja injusta a una estrategia, se pondría en duda la integridad de nuestras pruebas hasta el momento.

Figura 9: Queremos asegurarnos de que las condiciones de prueba sean idénticas en ambas pruebas para realizar una comparación justa.

Ya podemos ver una gran mejora con respecto a los resultados iniciales que obtuvimos. Pasamos los dos primeros años de nuestra prueba retrospectiva inicial tratando de alcanzar el punto de equilibrio. Sin embargo, con nuestra nueva estrategia comercial, superamos esa limitación y obtuvimos beneficios durante los cuatro años. 

Figura 10: Nuestra nueva estrategia comercial rompió con el periodo de operaciones no rentables que habría sido difícil de resolver utilizando estrategias cruzadas tradicionales.

En nuestra prueba retrospectiva inicial, nuestro sistema realizó un total de 41 operaciones y, en nuestra última prueba retrospectiva, hemos realizado un total de 42 operaciones. Por lo tanto, nuestro nuevo sistema está asumiendo más riesgos que la antigua forma de operar. Porque si dejamos pasar más tiempo, la brecha entre ellos puede seguir aumentando. Sin embargo, aunque nuestro nuevo sistema parece estar realizando más operaciones que el antiguo, nuestra ganancia total con el antiguo sistema era de 59,57 $, y ahora nuestra ganancia total se ha más que duplicado, hasta alcanzar los 125,36 $. Recuerde que, por ahora, hemos limitado nuestro sistema de negociación a realizar una operación con un lote mínimo. Además, en nuestro primer sistema, nuestra pérdida bruta fue de 410,02 $ y, con nuestra nueva estrategia, nuestra pérdida bruta ha descendido a 330,74 $. 

Al diseñar sistemas, tenemos que pensar en términos de compensaciones. Nuestro nuevo sistema está funcionando mejor para nosotros. Sin embargo, también debemos tener en cuenta que el tamaño de nuestro beneficio medio ha descendido de 29,35 $ a 22,81 $. Esto se debe a que, esporádicamente, nuestro nuevo sistema perderá operaciones con las que nuestro antiguo sistema obtenía beneficios. Sin embargo, este ocasional arrepentimiento puede estar justificado, dados los beneficios que obtenemos en términos de rendimiento. 

Nuestro índice de Sharpe ha aumentado de 0,18 en nuestra primera prueba a 0,5 en nuestra prueba actual. Esta es una buena señal que indica que estamos utilizando mejor nuestro capital. Además, nuestra proporción de operaciones perdedoras ha caído del 60,98 % en la primera prueba a un nuevo mínimo del 52,38 %.

Figura 11: Análisis detallado del rendimiento de nuestra nueva estrategia comercial.



Conclusión

La mayoría de los miembros de nuestra comunidad suelen ser desarrolladores independientes que trabajan solos en sus proyectos. Para nuestros compañeros de la comunidad que se encuentran en esas situaciones, creo que algoritmos sencillos como el que les hemos sugerido aquí pueden ser soluciones más prácticas. Es más fácil de mantener, desarrollar y ampliar. Gestionar una base de código compleja y extensa como desarrollador único no es tarea fácil, ni siquiera para desarrolladores con experiencia. Y si eres nuevo en nuestra comunidad de trading algorítmico, esta estrategia puede resultarte especialmente útil. Si te ha gustado leer este artículo, no te pierdas nuestro próximo debate, en el que intentaremos superar los excelentes resultados que hemos obtenido hoy. 

Nombre del archivo Descripción
Open & Close MA Cross Este archivo contiene nuestra nueva versión rediseñada de la estrategia de cruce de medias móviles.
Traditional MA Cross Este archivo contiene la implementación clásica de los cruces de medias móviles.

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

linfo2
linfo2 | 24 ene 2025 en 19:16
Gracias por el código y las ideas, me gusta como has estructurado tu código. Usted me ha introducido al tipo de datos Vector, yo no había utilizado esto antes. muy útil para mí en otros lugares
  vector atr_mean;
      atr_mean.CopyIndicatorBuffer(atr_handler,0,0,90); atr_mean.Mean())
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 2 feb 2025 en 03:33
linfo2 #:
Gracias por el código y las ideas, me gusta como has estructurado tu código. Me has introducido en el tipo de datos Vector, no lo había usado antes, me ha sido muy útil.
Gracias Niel, mi objetivo es combinar la simplicidad con el rigor técnico, es bueno saber que está dando sus frutos.

Y sí hombre la clase vector es un cambio de juego, en realidad proporciona más funcionalidad de la que hacemos uso en el día a día.

Tengo muchas ganas de aprender a usar la clase Matrix porque nos permite construir modelos lineales con una sola llamada a una función.
Celestine Nwakaeze
Celestine Nwakaeze | 13 sept 2025 en 10:39
Muchas gracias por esta maravillosa forma de estructurar el código. Como principiante, este artículo ha llevado mi aprendizaje muy lejos. Más grasa en el codo. Dios te bendiga para mí. Gracias.
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.
Automatización de estrategias de trading en MQL5 (Parte 3): Sistema RSI de recuperación de zona para la gestión dinámica de operaciones Automatización de estrategias de trading en MQL5 (Parte 3): Sistema RSI de recuperación de zona para la gestión dinámica de operaciones
En este artículo, creamos un sistema (un EA) de recuperación de zona RSI en MQL5, utilizando señales RSI para lanzar operaciones y una estrategia de recuperación para gestionar las pérdidas. Implementamos una clase «ZoneRecovery» para automatizar las entradas de operaciones, la lógica de recuperación y la gestión de posiciones. El artículo concluye con información sobre backtesting para optimizar el rendimiento y mejorar la eficacia del EA.
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
Los mercados financieros suelen clasificarse en dos tipos: los que se mueven dentro de un rango y los que siguen una tendencia. Esta visión estática del mercado puede facilitarnos las operaciones a corto plazo. Sin embargo, está desconectado de la realidad del mercado. En este artículo, buscamos comprender mejor cómo se mueven exactamente los mercados financieros entre estos dos modos posibles y cómo podemos utilizar nuestra nueva comprensión del comportamiento del mercado para ganar confianza en nuestras estrategias de negociación algorítmica.
Algoritmo de optimización de Escalera Real - Royal Flush Optimisation (RFO) Algoritmo de optimización de Escalera Real - Royal Flush Optimisation (RFO)
El algoritmo Royal Flush Optimization del autor ofrece una nueva perspectiva en la resolución de problemas de optimización sustituyendo la clásica codificación binaria de los algoritmos genéticos por un enfoque basado en sectores e inspirado en los principios del póquer. El RFO demuestra cómo la simplificación de los principios básicos puede dar lugar a un método de optimización eficaz y práctico. El artículo presenta un análisis detallado del algoritmo y los resultados de las pruebas.