English Русский 中文 Deutsch 日本語
preview
Reimaginando las estrategias clásicas en MQL5 (Parte 12): Estrategia de ruptura en EURUSD

Reimaginando las estrategias clásicas en MQL5 (Parte 12): Estrategia de ruptura en EURUSD

MetaTrader 5Ejemplos |
52 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

En este artículo, construiremos juntos una estrategia comercial en MQL5. Implementaremos una estrategia de trading de ruptura y la mejoraremos de forma iterativa para liberar todo su potencial. Analicemos algunas de las especificaciones de nuestra estrategia.

Nos centraremos en el par EURUSD y negociaremos sus movimientos en el marco temporal H1. Nuestra estrategia de ruptura registrará primero los precios máximos y mínimos actuales que se ofrecen en el par EURUSD. A medida que pase el tiempo, esperaremos a ver si los niveles de precios abren y cierran completamente fuera del canal creado por el precio máximo y mínimo inicial que registramos.

Cuando esto ocurre, nuestra estrategia de trading habrá encontrado una tendencia, es decir, que es probable que los mercados sigan moviéndose en una dirección concreta. Este no es el punto en el que se introducen nuestras posiciones. Entraremos en nuestras posiciones cuando se confirme nuestra tendencia. Una vez que los precios se abran y cierren completamente más allá del punto extremo de la vela que rompió nuestro canal inicial, abriremos posiciones largas si estamos por encima del canal y posiciones cortas en caso contrario.

Hasta ahora, el sistema que hemos especificado abrirá demasiadas operaciones. Necesitamos especificar otros indicadores de fortaleza o debilidad que nos ayuden a filtrar las operaciones no rentables que podríamos llegar a realizar. La media móvil nos puede ayudar a identificar rápidamente las tendencias del mercado.

Diseñaremos nuestro sistema para supervisar primero los precios actuales ofrecidos en el mercado en el que nos encontramos y, a continuación, observaremos en qué dirección se sale del canal y si esa salida está respaldada por la evolución futura de los precios. Si la ruptura que observamos es coherente con la evolución del precio que observamos tras la ruptura, utilizaremos nuestras medias móviles para sincronizar la ejecución de nuestra orden.

Preferiremos ir en largo cuando la media móvil rápida esté por encima de la lenta, y lo contrario será válido para nuestras posiciones cortas. Todas nuestras operaciones se actualizarán activamente utilizando el indicador Average True Range para calcular nuestros ajustes de stop loss y take profit.

Probaremos nuestra estrategia de trading desde el 1 de enero de 2020 hasta el 30 de noviembre de 2024 en el marco temporal H1.

Nuestros indicadores técnicos se configurarán de la siguiente manera:

  1. Media móvil rápida: Media móvil exponencial de 5 períodos aplicada al precio de cierre.
  2. Media móvil lenta: Media móvil exponencial de 60 períodos aplicada al precio de cierre.
  3. Average True Range: Indicador ATR de 14 períodos.
Nuestra aplicación de trading opera siguiendo las reglas básicas del trading. Inicialmente, cuando nuestro sistema se carga por primera vez, simplemente marcaremos el punto máximo y mínimo de la vela anterior y luego esperaremos hasta que el precio rompa por cualquiera de los dos lados. Hasta que esto suceda, nuestro sesgo seguirá siendo 0 y no tendremos ninguna confirmación ni operaciones realizadas.


    Figura 1: El estado inicial de nuestra aplicación de trading de ruptura.

    Después de algún tiempo, los niveles de precios finalmente se abrirán y cerrarán fuera del canal. Este punto extremo será nuestro sesgo, el lado que creemos que seguirán los mercados. Nuestro sesgo se confirmará si los niveles de precios cierran posteriormente por debajo del sesgo. De lo contrario no realizaremos ninguna operación.


    Figura 2: Nuestra aplicación comercial ha detectado un sesgo de mercado.

    Si los niveles de precios confirman nuestro sesgo, entonces tendremos la confianza para abrir una posición en el mercado. Nuestra estrategia inicialmente será seguir tendencias. Entonces, si los precios superan el canal, buscaremos oportunidades para comprar.

      Figura 3: Nuestras posiciones se abren después de que se haya confirmado nuestro sesgo.


      Introducción a MQL5

      Nuestra aplicación comercial está diseñada utilizando lógica comercial y conceptos de análisis técnico fundamental. Destaquemos los elementos clave que contiene el código.

      Parte del sistema
      Propósito previsto
      Constantes y parámetros
      Arreglaremos ciertos aspectos de nuestro algoritmo comercial para mantener la coherencia en todas nuestras pruebas, como los períodos de los promedios móviles, el tamaño del lote y el ancho de nuestro stop loss y take profit.
      Variables globales
      Estas variables se utilizan en diferentes partes de nuestro código y es importante que cuando las usemos, apuntemos al mismo valor cada vez. Algunas de las variables globales en nuestra aplicación incluyen el máximo y el mínimo del canal, la dirección que creemos que seguirá el mercado (sesgo) y otros valores de indicadores técnicos.

      También necesitaremos definir otras variables importantes en nuestra aplicación comercial para ayudarnos a realizar un seguimiento del estado del mercado. Conozcamos los más importantes.

      Variable
      Propósito previsto
      Sesgo
      El parámetro de sesgo simboliza la dirección en la que parecen moverse los precios, se permite el valor 1 si la tendencia es alcista y -1 si la tendencia es bajista. De lo contrario, se establecerá en 0.
      Medias móviles
      La media móvil rápida (ma_f) y la media móvil lenta (ma_s) determinan la tendencia. Si ma_f[0] > ma_s[0] y el precio (c) está por encima de la media móvil rápida, se abre una posición de compra. De lo contrario, si ma_f[0] < ma_s[0] y el precio está por debajo de la media móvil lenta, se abre una posición de venta.
      Ruptura
      Cuando se rompe el nivel del canal (límite superior o inferior), se establece la dirección del movimiento (sesgo).
      Niveles de ruptura
      El nivel de ruptura nos indicará en qué dirección creemos que seguirán los mercados en el futuro. Si los mercados superan el límite superior, nuestra perspectiva será alcista.
      Confirmación de señal
      Nuestras operaciones no se realizarán sin la confirmación de la señal. La señal se confirma si el mercado mantiene su dirección tras la ruptura. Si se pierde la confirmación, la posición se puede ajustar o cerrar.
      Gestión de órdenes Las operaciones que realizaremos dependerán de la tendencia que observemos actualmente en el mercado. En caso de una tendencia alcista (bias == 1), se envía el comando: Trade.Buy(vol, Symbol(), ask, channel_low, 0, «Volatility Doctor AI»); De lo contrario, en caso de una tendencia bajista (sesgo == -1), se envía el comando: Trade.Sell(vol, Symbol(), bid, channel_high, 0, «Volatility Doctor AI»);
       Stop loss  Inicialmente se establece en channel_low para las compras y en channel_high para las ventas, y se actualiza en el futuro utilizando el valor ATR.

      Ahora que tenemos un esquema conceptual de los elementos variables de nuestra estrategia, comencemos a construir juntos nuestra estrategia comercial. En primer lugar, debemos especificar los detalles de nuestra aplicación comercial.

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.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"
      

      Ahora carga la biblioteca comercial.

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

      Defina las constantes para nuestra aplicación de trading, como los periodos de algunos de nuestros indicadores técnicos.

      //+------------------------------------------------------------------+
      //| Constants                                                        |
      //+------------------------------------------------------------------+
      const  int ma_f_period = 5; //Slow MA
      const  int ma_s_period = 60; //Slow MA
      

      Ahora definamos las entradas que nuestro usuario final puede ajustar. Dado que mantenemos fijos nuestros indicadores técnicos, el usuario final no se ve abrumado por numerosos parámetros.

      //+------------------------------------------------------------------+
      //| Inputs                                                           |
      //+------------------------------------------------------------------+
      input  group "Money Management"
      input int lot_multiple = 5; //Lot Multiple
      input int atr_multiple = 5; //ATR Multiple
      

      Variables globales que utilizaremos en la mayor parte de nuestro programa.

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      

      Cuando nuestra aplicación de trading se cargue por primera vez, llamaremos a una función especializada para cargar nuestros indicadores técnicos y preparar otros datos de mercado necesarios para nosotros.

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

      Si ya no utilizamos nuestro Asesor Experto, debemos liberar los recursos que ya no estamos utilizando.

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

      Cada vez que recibamos precios actualizados, actualizaremos nuestras variables globales y luego buscaremos nuevas oportunidades para operar.

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
      
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            if(PositionsTotal() == 0)
               find_setup();
           }
        }
      

      La siguiente función se encargará de cargar nuestros indicadores técnicos y obtener datos del mercado.

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
         channel_high = iHigh(Symbol(),PERIOD_M30,1);
         channel_low  = iLow(Symbol(),PERIOD_M30,1);
         vol = lot_multiple * SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
         atr_handler = iATR(Symbol(),PERIOD_CURRENT,14);
         ma_fast     = iMA(Symbol(),PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow     = iMA(Symbol(),PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

      Cuando nuestra estrategia se cargue por primera vez, marcaremos los precios máximos y mínimos actuales que se ofrecen en el mercado. De esta manera, todos los precios futuros que observemos podrán analizarse en su contexto y compararse con los niveles de precios iniciales que vimos cuando llegamos por primera vez.

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

      Si tenemos posiciones abiertas, debemos actualizar nuestros valores de stop loss y take profit en consecuencia. Ajustaremos nuestra configuración de riesgo utilizando un múltiplo del rango medio verdadero, de modo que nuestra configuración de riesgo esté relacionada con los niveles actuales de volatilidad del mercado.

      //+---------------------------------------------------------------+
      //| Manage setup                                                  |
      //+---------------------------------------------------------------+
      void manage_setup(void)
        {
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         Print("Managing Position");
      
         if(PositionSelect(Symbol()))
           {
            Print("Position Found");
            initial_sl = PositionGetDouble(POSITION_SL);
           }
      
         if(bias == 1)
           {
            Print("Position Buy");
            double new_sl = (ask - (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl < new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
         if(bias == -1)
           {
            Print("Position Sell");
            double new_sl = (bid + (atr[0] * atr_multiple));
            Print("Initial: ",initial_sl,"\nNew: ",new_sl);
            if(initial_sl > new_sl)
              {
               Trade.PositionModify(Symbol(),new_sl,0);
               Print("DONE");
              }
           }
      
        }
      

      Si no tenemos posiciones abiertas, seguiremos las reglas que hemos descrito anteriormente para identificar oportunidades de negociación. Recordemos que buscamos observar una fuerte acción del precio que se desprenda del canal inicial en el que encontraremos el precio. Después, ganaremos la confianza suficiente para comprometernos con la operación, si los niveles de precios siguen moviéndose en la misma dirección y no cruzan el canal abierto que acaban de crear.

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- We are updating the system
         o = iOpen(Symbol(),PERIOD_CURRENT,1);
         h = iHigh(Symbol(),PERIOD_CURRENT,1);
         l = iLow(Symbol(),PERIOD_CURRENT,1);
         c = iClose(Symbol(),PERIOD_CURRENT,1);
         bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
         ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
         CopyBuffer(atr_handler,0,0,1,atr);
         CopyBuffer(ma_fast,0,0,1,ma_f);
         CopyBuffer(ma_slow,0,0,1,ma_s);
      
      //--- If we have no market bias
         if(bias == 0)
           {
            //--- Our bias is bullish
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high)
            )
              {
               bias = 1;
               bias_level = h;
               bo_h = h;
               bo_l = l;
               mark_bias(h);
              }
      
            //--- Our bias is bearish
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low)
            )
              {
               bias = -1;
               bias_level = l;
               bo_h = h;
               bo_l = l;
               mark_bias(l);
              }
           }
      
      //--- Is our bias valid?
         if(bias != 0)
           {
      
            //--- Our bearish bias has been violated
            if
            (
               (o > channel_high) &&
               (h > channel_high) &&
               (l > channel_high) &&
               (c > channel_high) &&
               (bias == -1)
            )
              {
               forget_bias();
              }
            //--- Our bullish bias has been violated
            if
            (
               (o < channel_low) &&
               (h < channel_low) &&
               (l < channel_low) &&
               (c < channel_low) &&
               (bias == 1)
            )
              {
               forget_bias();
              }
      
            //--- Our bullish bias has been violated
            if
            (
               ((o < channel_high) && (c > channel_low))
            )
              {
               forget_bias();
              }
      
            //--- Check if we have confirmation
            if((confirmation == 0) && (bias != 0))
              {
               //--- Check if we are above the bias level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level) &&
                  (bias == 1)
               )
                 {
                  confirmation = 1;
                 }
      
               //--- Check if we are below the bias level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level) &&
                  (bias == -1)
               )
                 {
                  confirmation = 1;
                 }
              }
           }
      
      //--- Check if our confirmation is still valid
         if(confirmation == 1)
           {
            //--- Our bias is bullish
            if(bias == 1)
              {
               //--- Confirmation is lost if we fall beneath the breakout level
               if
               (
                  (o < bias_level) &&
                  (h < bias_level) &&
                  (l < bias_level) &&
                  (c < bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
      
            //--- Our bias is bearish
            if(bias == -1)
              {
               //--- Confirmation is lost if we rise above the breakout level
               if
               (
                  (o > bias_level) &&
                  (h > bias_level) &&
                  (l > bias_level) &&
                  (c > bias_level)
               )
                 {
                  confirmation = 0;
                 }
              }
           }
      
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  Trade.Buy(vol,Symbol(),ask,channel_low,0,"Volatility Doctor AI");
                  initial_sl = channel_low;
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  Trade.Sell(vol,Symbol(),bid,channel_high,0,"Volatility Doctor AI");
                  initial_sl = channel_high;
                 }
              }
           }
         Comment("O: ",o,"\nH: ",h,"\nL: ",l,"\nC:",c,"\nC H: ",channel_high,"\nC L:",channel_low,"\nBias: ",bias,"\nBias Level: ",bias_level,"\nConfirmation: ",confirmation,"\nMA F: ",ma_f[0],"\nMA S: ",ma_s[0]);
        }
      

      Cuando los niveles de precios rompen fuera del canal que teníamos inicialmente, marcaremos el nivel de precio extremo creado por la vela que rompió el canal. Ese nivel extremo es nuestro nivel de sesgo.

      //+---------------------------------------------------------------+
      //| Mark our bias levels                                          |
      //+---------------------------------------------------------------+
      void mark_bias(double f_level)
        {
         ObjectCreate(0,"Bias",OBJ_HLINE,0,0,f_level);the
        }
      

      Por último, si los niveles de precios vuelven a caer dentro del canal de negociación después de haberlo superado previamente, consideraremos que el canal anterior no es válido y actualizaremos la nueva posición del canal a los niveles creados por la vela de ruptura.

      //+---------------------------------------------------------------+
      //| Forget our bias levels                                        |
      //+---------------------------------------------------------------+
      void forget_bias()
        {
         update_channel(bo_h,bo_l);
         bias = 0;
         bias_level = 0;
         confirmation = 0;
         ObjectDelete(0,"Bias");
        }
      //+------------------------------------------------------------------+
      

      Ahora estamos listos para realizar una prueba retrospectiva de la estrategia de trading de ruptura. He llamado a la aplicación «MTF Channel 2», que significa Multiple Time Frame Channel (Canal de múltiples marcos temporales). Seleccioné el símbolo EURUSD en el marco temporal H1. Las fechas de nuestras pruebas son las mismas que las que especificamos anteriormente. El lector observará que estos tres ajustes concretos se mantuvieron fijos en las tres pruebas.

      Nuestra configuración inicial

      Figura 4: El primer conjunto de ajustes utilizados para nuestra prueba retrospectiva inicial.

      Estos no son todos los parámetros que configuramos. Seleccionamos la configuración de retraso aleatorio para imitar situaciones de negociación en tiempo real, en las que la latencia experimentada puede variar. También decidimos modelar la prueba basándonos en operaciones reales, para intentar obtener una experiencia fiel al trading real.

      Nuestra segunda configuración

      Figura 5: Segundo conjunto de ajustes seleccionados para probar nuestra estrategia.

      Fijaremos los ajustes utilizados en nuestro Asesor Experto para que sean los mismos en todas las pruebas que realizaremos. Mantener estos ajustes nos ayudará a aislar la rentabilidad que se obtiene al elegir mejores reglas de negociación.

      Configuración de nuestro sistema

      Figura 6: Nuestra configuración de gestión del dinero.

      Veamos nuestra estrategia en acción. En la figura 7, podemos ver en la parte derecha de la captura de pantalla las variables internas que utiliza nuestra aplicación para tomar sus decisiones. Tenga en cuenta que todas nuestras operaciones solo se realizarán si la confirmación está establecida en 1.

      Nuestro sistema en acción

      Figura 7: Backtesting de nuestra estrategia de trading en el par EURUSD.

      Desafortunadamente, podemos ver que nuestra estrategia estaba perdiendo dinero. Esto es una señal de que hay margen de mejora.

      El balance de nuestra cuenta a lo largo del tiempo.

      Figura 8: Visualización del gráfico asociado a nuestra prueba retrospectiva.

      Veamos más detalles sobre la prueba que acabamos de realizar. Podemos ver claramente que nuestra estrategia identificó un total de 53 operaciones y que el 70 % de ellas no fueron rentables. Nuestro índice de Sharpe es negativo. Son métricas de rendimiento deficientes.

      Por otro lado, nuestra ganancia promedio es mayor que nuestra pérdida promedio, lo cual es una buena noticia. Veamos cómo podemos mejorar nuestro rendimiento. Queremos ejercer un mayor control sobre nuestras pérdidas brutas y medias, al tiempo que maximizamos nuestro beneficio medio y la proporción de operaciones rentables.

      Análisis detallado del sistema 1

      Figura 9: Detalles de nuestra prueba retrospectiva.

      Mejorando nuestros primeros resultados

      Mientras observaba la prueba retrospectiva, era frustrante ver cómo el asesor experto cometía el mismo error una y otra vez. La mayoría de nuestras pérdidas se debieron a que realizábamos operaciones basadas en fluctuaciones insignificantes del precio que, casualmente, satisfacían todas nuestras condiciones. La única solución para esto es seleccionar mejores condiciones que puedan discriminar de forma natural los movimientos débiles y fuertes del mercado.

      Una opción que tenemos es comparar el rendimiento del euro y el dólar estadounidense con respecto a un índice de referencia común. Podemos usar GBP para esto. Compararemos el rendimiento de los pares EURGBP y GBPUSD antes de comprometernos a abrir una posición. Es decir, si en nuestro gráfico observamos que el EURUSD se encuentra en una fuerte tendencia alcista, también nos gustaría ver que el EURGBP se mueve en la misma tendencia y que el GBPUSD también se encuentre, con suerte, en una tendencia alcista.

      En otras palabras, si los niveles de precios del EURUSD nos dan la impresión de que el euro se está encareciendo con respecto al dólar, solo ganaremos confianza si también observamos que el euro se está apreciando frente a la libra esterlina, mientras que el dólar se está abaratando simultáneamente con respecto a la libra esterlina. Este tipo de cambio triangular de tres vías nos ayudará, con suerte, a identificar falsas rupturas. Nuestro razonamiento es que las fluctuaciones que afectan a los tres mercados a la vez pueden ser movimientos realmente fuertes de los que podemos sacar provecho.

      Añadiremos unas pocas líneas de código para modificar la estrategia comercial original que hemos creado hasta ahora. Para implementar los cambios que estamos pensando, primero crearemos nuevas variables globales para realizar un seguimiento del precio de los pares EURGBP y GBPUSD. También tendremos que aplicar indicadores técnicos a nuestros otros dos mercados para poder realizar un seguimiento de las tendencias en dichos mercados.

      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      double channel_high = 0;
      double channel_low  = 0;
      double o,h,l,c;
      int    bias = 0;
      double bias_level = 0;
      int    confirmation = 0;
      double vol,bid,ask,initial_sl;
      int    atr_handler,ma_fast,ma_slow;
      double atr[],ma_f[],ma_s[];
      double bo_h,bo_l;
      int    last_trade_state,current_state;
      int    eurgbp_willr, gbpusd_willr;
      string symbols[] = {"EURGBP","GBPUSD"};
      

      Cuando nuestro Asesor Experto se cargue por primera vez, tendremos que realizar algunos pasos adicionales para realizar un seguimiento de la evolución de los precios en nuestros símbolos de referencia. Estas actualizaciones se implementarán en la función de configuración.

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Select the symbols we need
         SymbolSelect("EURGBP",true);
         SymbolSelect("GBPUSD",true);
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         eurgbp_willr = iWPR(symbols[0],PERIOD_CURRENT,wpr_period);
         gbpusd_willr = iWPR(symbols[1],PERIOD_CURRENT,wpr_period);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
        }
      

      Del mismo modo, cuando nuestra aplicación comercial ya no se utilice, tendremos que publicar algunos indicadores técnicos adicionales.

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //---
         IndicatorRelease(eurgbp_willr);
         IndicatorRelease(gbpusd_willr);
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
        }
      

      Nuestra función OnTick seguirá siendo la misma. Sin embargo, las funciones que llamará se modificarán. En primer lugar, cada vez que actualizamos nuestro canal, debemos actualizar tres canales en los mercados que seguimos. Uno en el EURUSD, el segundo en el EURGBP y el último en el GBPUSD.

      //+---------------------------------------------------------------+
      //| Update channel                                                |
      //+---------------------------------------------------------------+
      void update_channel(double new_high, double new_low)
        {
         channel_high = new_high;
         channel_low  = new_low;
         ObjectDelete(0,"Channel High");
         ObjectDelete(0,"Channel Low");
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
        }
      

      La mayor parte del programa se mantuvo igual, el cambio más significativo que hicimos fue que ahora exigimos que nuestra aplicación de negociación compruebe otros dos mercados antes de decidirse a comprometerse con la operación. Si nuestros fundamentos nos dan la confianza de que la ruptura que estamos viendo en el EURUSD puede estar respaldada por una fuerza real, entonces tomaremos la posición. Estas actualizaciones se reflejarán en la función de configuración de búsqueda.

      También observará que la función llama a una nueva función que no definimos en la versión anterior de la aplicación de la estrategia de ruptura. La función de confirmación adicional comprobará los dos mercados de referencia para nuestras condiciones comerciales fundamentales.

      //+---------------------------------------------------------------+
      //| Find Setup                                                    |
      //+---------------------------------------------------------------+
      void find_setup(void)
        {
      //--- I have omitted code pieces that were unchanged
      //--- Do we have a setup?
         if((confirmation == 1) && (bias == 1) && (current_state != last_trade_state))
           {
            if(ma_f[0] > ma_s[0])
              {
               if(c > ma_f[0])
                 {
                  if(additional_confirmation(1))
                    {
                     Trade.Buy(vol,"EURUSD",ask,channel_low,0,"Volatility Doctor");
                     initial_sl = channel_low;
                     last_trade_state = 1;
                    }
                 }
              }
           }
      
         if((confirmation == 1) && (bias == -1)  && (current_state != last_trade_state))
           {
            if(ma_f[0] < ma_s[0])
              {
               if(c < ma_s[0])
                 {
                  if(additional_confirmation(-1))
                    {
                     Trade.Sell(vol,"EURUSD",bid,channel_high,0,"Volatility Doctor");
                     initial_sl = channel_high;
                     last_trade_state = -1;
                    }
                 }
              }
           }
      }

      Esta función debería ayudarnos a distinguir el ruido del mercado de la verdadera fortaleza. Al buscar confirmación en otros mercados relacionados, esperamos seleccionar siempre las operaciones más sólidas posibles.

      //+---------------------------------------------------------------+
      //| Check for true strength                                       |
      //+---------------------------------------------------------------+
      bool additional_confirmation(int flag)
        {
      //--- Do we have additional confirmation from our benchmark pairs?
      
      //--- Record the average change in the EURGBP and GBPUSD Market
         vector eurgbp_willr_f = vector::Zeros(1);
         vector gbpusd_willr_f = vector::Zeros(1);
      
         eurgbp_willr_f.CopyIndicatorBuffer(eurgbp_willr,0,0,1);
         gbpusd_willr_f.CopyIndicatorBuffer(gbpusd_willr,0,0,1);
      
         if((flag == 1) && (eurgbp_willr_f[0] > -50) && (gbpusd_willr_f[0] < -50))
            return(true);
         if((flag == -1) && (eurgbp_willr_f[0] < -50) && (gbpusd_willr_f[0] > -50))
            return(true);
      
         Print("EURGBP WPR: ",eurgbp_willr_f[0],"\nGBPUSD WPR: ",gbpusd_willr_f[0]);
         return(false);
        }
      

      Esta versión de nuestra aplicación se titulará «MTF EURUSD Channel». La primera versión que creamos era más generalizada y se podía utilizar fácilmente para operar con cualquier otro símbolo en nuestra terminal. Sin embargo, esta versión utilizará los pares EURGBP y GBPUSD como referencia, por lo que es más especializada y solo está destinada a operar con el par EURUSD. El lector observará que nuestras condiciones de prueba son idénticas a las de la primera prueba. Realizaremos esta prueba retrospectiva utilizando el mismo marco temporal y los mismos periodos que en la primera prueba, desde el 1 de enero de 2020 hasta el 30 de noviembre de 2024.

      Nuestro segundo lote del EA que se someterá a prueba.

      Figura 10: El primer lote de ajustes para nuestra prueba retrospectiva de la estrategia de ruptura del canal EURUSD.

      Si tiene intención de seguir la configuración que le muestro aquí, tenga en cuenta que establecer la opción Modelado en «Cada tick basado en ticks reales» puede llevar mucho tiempo, dependiendo de su conexión a Internet, ya que la terminal MT5 solicitará datos completos a su bróker para modelar el mercado de la forma más realista posible. Por lo tanto, no se alarme si el proceso tarda varios minutos en completarse y no apague el ordenador mientras se está realizando.

      Nuestro segundo lote de entradas para nuestra aplicación comercial.

      Figura 11: Debemos mantener la segunda serie de ajustes idénticos a los que utilizamos en la primera prueba.

      Usar un lote múltiplo de 1 significa que todas mis operaciones se realizarán con el tamaño mínimo de lote. Si conseguimos que nuestro sistema sea rentable con un tamaño mínimo de lote, entonces aumentar el múltiplo del lote nos resultará muy útil. Sin embargo, si nuestro sistema no es rentable con el lote mínimo, no ganaremos nada aumentando el tamaño del lote.

      Nuestra configuración de parámetros

      Figura 12: Los parámetros que utilizaremos para controlar el comportamiento de nuestra aplicación.

      Ahora podemos ver cómo funciona nuestro sistema de trading con datos históricos. Tenga en cuenta que esta versión de nuestro sistema supervisa tres mercados a la vez. En primer lugar, siempre realizaremos un seguimiento del par EURUSD, para poder obtener nuestra tendencia a partir de él.

      Nuestro sistema en acción con el EURUSD

      Figura 13: Nuestro sistema en acción con el par EURUSD.

      Nuestras posiciones solo pueden abrirse si observamos que los pares EURGBP y GBPUSD tienden en direcciones opuestas, como se muestra en las figuras 14 y 15 a continuación. Evaluaremos la tendencia en los dos mercados utilizando el rango porcentual de Williams. Si el WPR está por encima del nivel 50, consideramos que la tendencia es alcista.

      Nuestro primer par de referencia

      Fig. 14: Nuestro primer par de confirmación, el GBPUSD.

      En este caso, encontramos una oportunidad de trading para comprar el EURUSD. Identificamos esta oportunidad porque las lecturas del WPR de los dos mercados se encontraban en lados opuestos del nivel 50. Es probable que este desequilibrio vaya seguido de condiciones de mercado volátiles, ideales para cualquier estrategia de ruptura.

      Nuestro segundo par de referencia.

      Figura 15: Nuestro segundo par de referencia.

      La figura 9 muestra cómo cambia el saldo de nuestra cuenta de trading simulada a lo largo del tiempo. Nuestro objetivo es comprender en profundidad por qué nuestra estrategia está fallando, para poder intentar mejorar sus puntos débiles.

      Los cambios en el saldo de la cuenta a lo largo del tiempo

      Figura 16: Gráfico del saldo de nuestra cuenta a lo largo del tiempo.

      Desafortunadamente, los cambios que hemos realizado en nuestro sistema han reducido la rentabilidad de nuestra aplicación comercial. Nuestras pérdidas y ganancias medias aumentaron en las mismas cantidades. Y la proporción de operaciones rentables disminuyó ligeramente. 

      Análisis detallado del sistema 2.

      Figura 17: Resultados detallados de nuestra prueba retrospectiva.

      Último intento para mejorar

      No logramos mejorar en lo que más importa: la rentabilidad. En lugar de intentar imponer nuestras opiniones al mercado, dejaremos que el ordenador aprenda a utilizar las medias móviles mejor de lo que nosotros somos capaces. Nuestras opiniones sobre cómo operar de manera eficaz están sesgadas hasta cierto punto.

      Por otro lado, si permitimos que nuestro ordenador aprenda la relación entre el precio de cierre y la media móvil, entonces nuestro ordenador puede crear sus propias reglas de negociación y operar basándose en lo que espera que suceda a continuación, en contraposición a la forma reactiva de negociación que hemos estado practicando hasta ahora.

      Para empezar, creé un script que nos ayudara a extraer datos históricos del mercado. Simplemente arrastre y suelte el script en el mercado en el que desea operar para que podamos empezar. El script obtendrá los datos del mercado por usted y también obtendrá las dos medias móviles que necesitamos para nuestra estrategia en el mismo formato que utilizamos en nuestra aplicación de trading.

      //+------------------------------------------------------------------+
      //|                                                      ProjectName |
      //|                                      Copyright 2020, CompanyName |
      //|                                       http://www.companyname.net |
      //+------------------------------------------------------------------+
      #property copyright "Gamuchirai Zororo Ndawana"
      #property link      "https://www.mql5.com/en/users/gamuchiraindawa"
      #property version   "1.00"
      #property script_show_inputs
      
      //+------------------------------------------------------------------+
      //| Script Inputs                                                    |
      //+------------------------------------------------------------------+
      input int size = 100000; //How much data should we fetch?
      
      //+------------------------------------------------------------------+
      //| Global variables                                                 |
      //+------------------------------------------------------------------+
      int    ma_f_handler,ma_s_handler;
      double ma_f_reading[],ma_s_reading[];
      
      //+------------------------------------------------------------------+
      //| On start function                                                |
      //+------------------------------------------------------------------+
      void OnStart()
        {
      //--- Load indicator
         ma_s_handler  = iMA(Symbol(),PERIOD_CURRENT,60,0,MODE_EMA,PRICE_CLOSE);
         ma_f_handler  = iMA(Symbol(),PERIOD_CURRENT,5,0,MODE_EMA,PRICE_CLOSE);
      
      //--- Load the indicator values
         CopyBuffer(ma_f_handler,0,0,size,ma_f_reading);
         CopyBuffer(ma_s_handler,0,0,size,ma_s_reading);
      
         ArraySetAsSeries(ma_f_reading,true);
         ArraySetAsSeries(ma_s_reading,true);
      
      //--- File name
         string file_name = "Market Data " + Symbol() +" MA Cross" +  " As Series.csv";
      
      //--- Write to file
         int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
      
         for(int i= size;i>=0;i--)
           {
            if(i == size)
              {
               FileWrite(file_handle,"Time","Open","High","Low","Close","MA 5","MA 60");
              }
      
            else
              {
               FileWrite(file_handle,iTime(Symbol(),PERIOD_CURRENT,i),
                         iOpen(Symbol(),PERIOD_CURRENT,i),
                         iHigh(Symbol(),PERIOD_CURRENT,i),
                         iLow(Symbol(),PERIOD_CURRENT,i),
                         iClose(Symbol(),PERIOD_CURRENT,i),
                         ma_f_reading[i],
                         ma_s_reading[i]
                        );
              }
           }
      //--- Close the file
         FileClose(file_handle);
      
        }
      //+------------------------------------------------------------------+
      

      Análisis de datos en Python

      Ahora que ya tienes los datos del mercado en formato CSV, podemos empezar a crear un modelo de IA que, con suerte, nos ayudará a predecir las falsas rupturas y a evitarlas.

      import pandas as pd
      import numpy  as np
      from   sklearn.model_selection import TimeSeriesSplit,cross_val_score
      from   sklearn.linear_model    import Ridge
      from sklearn.metrics import mean_squared_error
      import matplotlib.pyplot as plt
      import seaborn as sns

      Lea los datos del mercado que hemos extraído anteriormente. Presta atención a la columna «Time» (Hora) de mi marco de datos y fíjate en que la última entrada que tengo tiene fecha del 18 de abril de 2019. Esto se está haciendo deliberadamente. Recordemos que las fechas de inicio de las pruebas anteriores comenzaron el 1 de enero de 2020. Esto significa que no nos estamos engañando a nosotros mismos al darle al modelo todas las respuestas de nuestra prueba.

      #Define the forecast horizon
      look_ahead          = 24
      #Read in the data
      data                = pd.read_csv('Market Data EURUSD MA Cross As Series.csv')
      #Drop the last 4 years
      data                =  data.iloc[:(-24 * 365 * 4),:]
      data.reset_index(drop=True,inplace=True)
      #Label the data
      data['Target']      = data['Close'].shift(-look_ahead)
      data['MA 5 Target']      = data['MA 5'].shift(-look_ahead)
      data['MA 5 Close Target']      = data['Target'] - data['MA 5 Target']
      data['MA 60 Target']      = data['MA 60'].shift(-look_ahead)
      data['MA 60 Close Target']      = data['Target'] - data['MA 60 Target']
      data.dropna(inplace=True)
      data.reset_index(drop=True,inplace=True)
      data


      Figura 18: Nuestros datos históricos del mercado.

      Probemos para ver si en el mercado EURUSD, las medias móviles siguen siendo más fáciles de predecir que el propio precio. Para comprobar nuestra hipótesis, entrenaremos 30 redes neuronales idénticas para predecir tres objetivos uno por uno. Primero, pronosticaremos el precio futuro, el promedio móvil de 5 períodos y el promedio móvil de 60 períodos. Todos los objetivos se proyectarán 24 pasos hacia el futuro. En primer lugar, registraremos nuestra precisión prediciendo el precio directamente.

      #Classical error
      classical_error = []
      epochs = 1000
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        classical_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close']],data.loc[:,'Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      A continuación, registraremos nuestra precisión prediciendo la media móvil de 5 períodos.

      #MA Cross Over error
      ma_5_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=epochs,early_stopping=False,solver='lbfgs')
        ma_5_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 5']],data.loc[:,'MA 5 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      Por último, registraremos nuestra precisión al predecir la media móvil de 60 períodos.

      #New error
      ma_60_error = []
      for i in np.arange(0,30):
        model = MLPRegressor(hidden_layer_sizes=(10,4),max_iter=10000,early_stopping=False,solver='lbfgs')
        ma_60_error.append(np.mean(np.abs(cross_val_score(model,data.loc[:,['Open','High','Low','Close','MA 60']],data.loc[:,'MA 60 Target'],cv=tscv,scoring='neg_mean_squared_error'))))

      Cuando representamos gráficamente nuestros resultados. Como podemos ver en la figura 12, la predicción de la media móvil de 60 períodos fue la que generó más errores en nuestro sistema, y la predicción de la media móvil de 5 períodos produjo menos errores que la predicción directa del precio.

      plt.plot(classical_error)
      plt.plot(ma_5_error)
      plt.plot(ma_60_error)
      plt.legend(['OHLC','MA 5 ','MA 60'])
      plt.axhline(np.mean(classical_error),color='blue',linestyle='--')
      plt.axhline(np.mean(ma_5_error),color='orange',linestyle='--')
      plt.axhline(np.mean(ma_60_error),color='green',linestyle='--')
      plt.grid()
      plt.ylabel('Cross Validated Error')
      plt.xlabel('Iteration')
      plt.title('Comparing Different The Error Associated With Different Targets')
      plt.show()

      Figura 19: Visualización del error asociado a diferentes objetivos.

      Ahora intentemos exportar un modelo para nuestra aplicación comercial. Importa las bibliotecas que necesitamos.

      import onnx
      from skl2onnx import convert_sklearn
      from skl2onnx.common.data_types import FloatTensorType
      from sklearn.neural_network import MLPRegressor

      Especifique los modelos que necesitamos. Utilizaré dos modelos para esta tarea, ya que la media móvil a corto plazo es fácil de predecir, utilizaré un modelo Ridge simple para pronosticarla. Sin embargo, nuestra media móvil de 60 períodos resultó ser un reto. Por lo tanto, utilizaré una red neuronal para predecir la media móvil a largo plazo.

      ma_5_model = Ridge()
      ma_5_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Target'])
      ma_5_height_model = Ridge()
      ma_5_height_model.fit(data[['Open','High','Low','Close','MA 5']],data['MA 5 Close Target'])
      ma_60_model = Ridge()
      ma_60_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Target'])
      ma_60_height_model = Ridge()
      ma_60_height_model.fit(data[['Open','High','Low','Close','MA 60']],data['MA 60 Close Target'])

      Prepárese para exportar a ONNX.

      initial_type = [('float_input', FloatTensorType([1, 5]))]
      ma_5_onx = convert_sklearn(ma_5_model, initial_types=initial_type, target_opset=12 )
      ma_5_height_onx = convert_sklearn(ma_5_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_height_onx = convert_sklearn(ma_60_height_model, initial_types=initial_type, target_opset=12 )
      ma_60_onx = convert_sklearn(ma_60_model, initial_types=initial_type, target_opset=12 )

      Guardar en formato ONNX.

      onnx.save(ma_5_onx,'eurchf_ma_5_model.onnx')
      onnx.save(ma_60_onx,'eurchf_ma_60_model.onnx')
      onnx.save(ma_5_height_onx,'eurusd_ma_5_height_model.onnx')
      onnx.save(ma_60_height_onx,'eurusd_ma_60_height_model.onnx')

      Actualizaciones finales en MQL5

      Apliquemos nuestros nuevos modelos para ver si nos pueden ayudar a filtrar las falsas rupturas en el mercado. La primera actualización que debemos realizar es importar los modelos ONNX que acabamos de crear.

      //+------------------------------------------------------------------+
      //|                                                MTF Channel 2.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"
      
      //+------------------------------------------------------------------+
      //| ONNX Resources                                                   |
      //+------------------------------------------------------------------+
      #resource "\\Files\\eurusd_ma_5_model.onnx"         as const uchar eurusd_ma_5_buffer[];
      #resource "\\Files\\eurusd_ma_60_model.onnx"        as const uchar eurusd_ma_60_buffer[];
      #resource "\\Files\\eurusd_ma_5_height_model.onnx"  as const uchar eurusd_ma_5_height_buffer[];
      #resource "\\Files\\eurusd_ma_60_height_model.onnx" as const uchar eurusd_ma_60_height_buffer[];

      A continuación, necesitamos crear algunas variables nuevas asociadas a nuestros modelos.

      //+------------------------------------------------------------------+
      //| Global varaibles                                                 |
      //+------------------------------------------------------------------+
      int     bias = 0;
      int     state = 0;
      int     confirmation = 0;
      int     last_cross_over_state = 0;
      int     atr_handler,ma_fast,ma_slow;
      int     last_trade_state,current_state;
      long    ma_5_model;
      long    ma_60_model;
      long    ma_5_height_model;
      long    ma_60_height_model;
      double  channel_high = 0;
      double  channel_low  = 0;
      double  o,h,l,c;
      double  bias_level = 0;
      double  vol,bid,ask,initial_sl;
      double  atr[],ma_f[],ma_s[];
      double  bo_h,bo_l;
      vectorf ma_5_forecast = vectorf::Zeros(1);
      vectorf ma_60_forecast = vectorf::Zeros(1);
      vectorf ma_5_height_forecast = vectorf::Zeros(1);
      vectorf ma_60_height_forecast = vectorf::Zeros(1);
      

      Debemos ampliar la rutina de inicialización para que ahora configure nuestros modelos ONNX por nosotros.

      //+---------------------------------------------------------------+
      //| Load our technical indicators and market data                 |
      //+---------------------------------------------------------------+
      void setup(void)
        {
      //--- Reset our last trade state
         last_trade_state = 0;
      //--- Mark the current high and low
         channel_high = iHigh("EURUSD",PERIOD_M30,1);
         channel_low  = iLow("EURUSD",PERIOD_M30,1);
         ObjectCreate(0,"Channel High",OBJ_HLINE,0,0,channel_high);
         ObjectCreate(0,"Channel Low",OBJ_HLINE,0,0,channel_low);
      //--- Our trading volums
         vol = lot_multiple * SymbolInfoDouble("EURUSD",SYMBOL_VOLUME_MIN);
      //--- Our technical indicators
         atr_handler  = iATR("EURUSD",PERIOD_CURRENT,14);
         ma_fast      = iMA("EURUSD",PERIOD_CURRENT,ma_f_period,0,MODE_EMA,PRICE_CLOSE);
         ma_slow      = iMA("EURUSD",PERIOD_CURRENT,ma_s_period,0,MODE_EMA,PRICE_CLOSE);
      //--- Setup our ONNX models
      //--- Define our ONNX model
         ulong input_shape [] = {1,5};
         ulong output_shape [] = {1,1};
      
      //--- Create the model
         ma_5_model = OnnxCreateFromBuffer(eurusd_ma_5_buffer,ONNX_DEFAULT);
         ma_60_model = OnnxCreateFromBuffer(eurusd_ma_60_buffer,ONNX_DEFAULT);
         ma_5_height_model = OnnxCreateFromBuffer(eurusd_ma_5_height_buffer,ONNX_DEFAULT);
         ma_60_height_model = OnnxCreateFromBuffer(eurusd_ma_60_height_buffer,ONNX_DEFAULT);
      
      //--- Store our models in a list
         long onnx_models[] = {ma_5_model,ma_5_height_model,ma_60_model,ma_60_height_model};
      
      //--- Loop over the models and set them up
         for(int i = 0; i < 4; i++)
           {
            if(onnx_models[i] == INVALID_HANDLE)
              {
               Comment("Failed to load AI module correctly: Invalid handle");
              }
      
            //--- Validate I/O
            if(!OnnxSetInputShape(onnx_models[i],0,input_shape))
              {
               Comment("Failed to set input shape correctly:  Wrong input shape ",GetLastError()," Actual shape: ",OnnxGetInputCount(ma_5_model));
              }
      
            if(!OnnxSetOutputShape(onnx_models[i],0,output_shape))
              {
               Comment("Failed to load AI module correctly: Wrong output shape ",GetLastError()," Actual shape: ",OnnxGetOutputCount(ma_5_model));
              }
           }
        }
      

      Si nuestro sistema ya no se utiliza, debemos liberar los recursos que ya no estamos utilizando.

      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
      //--- Free the resources we don't need
         IndicatorRelease(atr_handler);
         IndicatorRelease(ma_fast);
         IndicatorRelease(ma_slow);
         OnnxRelease(ma_5_model);
         OnnxRelease(ma_5_height_model);
         OnnxRelease(ma_60_model);
         OnnxRelease(ma_60_height_model);
        }

      Cuando recibimos precios actualizados, la única gran diferencia aquí es que también trataremos de obtener una previsión de nuestros modelos de IA.

      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
      //--- Keep track of time
         static datetime timestamp;
         datetime time = iTime(Symbol(),PERIOD_CURRENT,0);
         if(timestamp != time)
           {
            //--- Time Stamp
            timestamp = time;
            //--- Update system variables
            update();
            //--- Make a new prediction
            model_predict();
            if(PositionsTotal() == 0)
              {
               state = 0;
               find_setup();
              }
           }
      
      //--- If we have positions open
         if(PositionsTotal() > 0)
            manage_setup();
        }
      
      

      Tenemos que definir la función responsable de obtener una predicción de nuestros modelos ONNX en MQL5.

      //+------------------------------------------------------------------+
      //| Get a prediction from our model                                  |
      //+------------------------------------------------------------------+
      void model_predict(void)
        {
         //--- Moving average inputs
         float  a = (float) ma_f[0];
         float  b = (float) ma_s[0];
      
         //--- Price quotes
         float op = (float) iOpen("EURUSD",PERIOD_H1,0);
         float hi = (float) iHigh("EURUSD",PERIOD_H1,0);
         float lo = (float) iLow("EURUSD",PERIOD_H1,0);
         float cl = (float) iClose("EURUSD",PERIOD_H1,0);
      
         //--- ONNX inputs
         vectorf fast_inputs = {op,hi,lo,cl,a};
         vectorf slow_inputs = {op,hi,lo,cl,b};
      
         Print("Fast inputs: ",fast_inputs);
         Print("Slow inputs: ",slow_inputs);
      
         //--- Inference
         OnnxRun(ma_5_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_forecast);
         OnnxRun(ma_5_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_5_height_forecast);
         OnnxRun(ma_60_model,ONNX_DEFAULT,slow_inputs,ma_60_forecast);
         OnnxRun(ma_60_height_model,ONNX_DATA_TYPE_FLOAT,fast_inputs,ma_60_height_forecast);
        }
      

      El último cambio que hemos realizado afecta a la forma en que nuestra estrategia seleccionará sus operaciones. En lugar de lanzarse sin más, nuestra estrategia ahora realizará sus operaciones basándose en la relación que ha aprendido entre el precio y la media móvil. Nuestra aplicación de trading ahora tiene la flexibilidad de comprar y vender incluso si va en contra de la tendencia que creemos que hay en el mercado.

      Tenga en cuenta que se está llamando a una nueva función, valid setup, que simplemente devuelve verdadero si nuestras condiciones de ruptura son verdaderas.

      //+---------------------------------------------------------------+ //| Find a setup | //+---------------------------------------------------------------+ void find_setup(void) { //--- I have skipped parts of the code that remained the same    if(valid_setup())      {       //--- Both models are forecasting rising prices       if((c < (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c < (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != 1)            {             Trade.Buy(vol,"EURUSD",ask,0,0,"Volatility Doctor");             initial_sl = channel_low;             last_trade_state = 1;             last_cross_over_state = current_state;            }         }       //--- Both models are forecasting falling prices       if((c > (ma_60_forecast[0] + ma_60_height_forecast[0])) && (c > (ma_5_forecast[0] + ma_5_height_forecast[0])))         {          if(last_trade_state != -1)            {             Trade.Sell(vol,"EURUSD",bid,0,0,"Volatility Doctor");             initial_sl = channel_high;             last_trade_state = -1;             last_cross_over_state = current_state;            }         }      }

      Comprueba si hemos salido del canal. Si es así, la función devolverá verdadero; de lo contrario, falso.

      //+---------------------------------------------------------------+
      //| Do we have a valid setup?                                     |
      //+---------------------------------------------------------------+
      bool valid_setup(void)
        {
         return(((confirmation == 1) && (bias == -1)  && (current_state != last_cross_over_state)) || ((confirmation == 1) && (bias == 1) && (current_state != last_cross_over_state)));
        }
      

      Creo que a estas alturas ya estás familiarizado con los ajustes que especificaremos para nuestra prueba retrospectiva. Recuerde que es importante mantener la coherencia en estos ajustes para poder aislar los cambios en la rentabilidad asociados a los cambios que estamos realizando en nuestras reglas de negociación.

      Nuestra configuración de entrada

      Figura 20: Algunos de los ajustes que utilizaremos para realizar pruebas retrospectivas de nuestra última estrategia de negociación.

      Recordemos que nuestro modelo solo se entrenó hasta 2019, pero nuestra prueba comienza en 2020. Por lo tanto, estamos simulando con precisión lo que habría ocurrido realmente si hubiéramos diseñado este sistema en el pasado.

      Nuestro segundo lote de ajustes para la tercera prueba

      Figura 21: El segundo conjunto de ajustes que utilizaremos para realizar pruebas retrospectivas de nuestra última estrategia de negociación.

      Una vez más, nuestra configuración es la misma en las tres pruebas.

      Configuración de nuestra aplicación

      Figura 22: Los ajustes que utilizaremos para controlar nuestra aplicación en la última prueba.

      Ahora podemos ver nuestra aplicación de trading de ruptura basada en modelos en acción en el EURUSD. Recuerde que ninguno de estos datos se mostró a los modelos cuando se les entrenó.

      Nuestro sistema de IA en acción

      Figura 23: Nuestra versión final basada en modelos de la estrategia de ruptura en acción.

      En la figura 23 que aparece a continuación podemos ver que finalmente hemos logrado rectificar la pendiente negativa característica que tenía nuestro modelo desde el principio, y ahora estamos obteniendo más beneficios.

      El balance de nuestra cuenta a lo largo del tiempo

      Figura 24: Resultados de las pruebas retrospectivas de nuestra nueva estrategia basada en modelos.

      Nuestro objetivo era aumentar el beneficio medio y reducir la proporción de operaciones con pérdidas, y lo conseguimos. Nuestra pérdida bruta fue de 498 dólares en la primera prueba, 403 dólares en la segunda prueba y ahora es de 298 dólares. Al mismo tiempo, nuestro beneficio bruto fue de 378 dólares en la primera prueba y es de 341 dólares en esta prueba final. Por lo tanto, está claro que los cambios que hemos realizado han reducido nuestras pérdidas brutas, al tiempo que han mantenido prácticamente sin cambios los beneficios brutos. En nuestro primer sistema, el 70% de todas nuestras operaciones no eran rentables. Sin embargo, con nuestro nuevo sistema, solo el 55% de todas nuestras operaciones fueron poco rentables.

      Análisis detallado de nuestro sistema de negociación basado en modelos

      Figura 25: Resultados detallados de las pruebas retrospectivas de nuestra estrategia basada en modelos.

      Conclusión

      Las rupturas son potencialmente el mejor momento del día para operar. El reto que supone identificarlos correctamente no debe tomarse a la ligera. En este artículo hemos trabajado juntos para desarrollar nuestra propia estrategia de trading de ruptura. Agregamos más filtros a nuestra estrategia en un intento de hacerla más rentable. Puede ser que las estrategias de ruptura no sean ideales para el mercado EURUSD y que necesitemos abordar este mercado desde un ángulo diferente. Sin embargo, desarrollar con éxito una estrategia de trading de ruptura requerirá más tiempo y esfuerzo del que hemos compartido en este artículo, pero puede que valga la pena considerar las ideas que hemos compartido aquí en su camino hacia el éxito.

      Nombre del archivo 
      Descripción
      MQL5 EURUSD AI
      Cuaderno Jupyter utilizado para construir nuestro modelo del mercado EURUSD.
      EURUSD MA 60 Model
      Modelo ONNX utilizado para pronosticar el promedio móvil de 60 períodos.
      EURUSD MA 60 Height Model Modelo ONNX utilizado para pronosticar la diferencia entre el precio de cierre futuro y la futura media móvil de 60 días.
      EURUSD MA 5 Model
      Modelo ONNX destinado a pronosticar el promedio móvil de 5 períodos.
      EURUSD MA 5 Height Model Modelo ONNX utilizado para pronosticar la diferencia entre el precio de cierre futuro y la media móvil futura de 5 días. 
      MTF Channel 2
      La primera implementación de nuestra estrategia de ruptura.
      MTF Channel 2 EURUSD
      La segunda implementación de nuestra estrategia de ruptura que utilizó la confirmación de pares de referencia.
      MTF Channel 2 EURUSD AI
      La tercera implementación de nuestra estrategia de ruptura basada en modelos.





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

      Aliaksandr Kazunka
      Aliaksandr Kazunka | 6 abr 2025 en 04:23
      ¡Hola! ¿Por qué utilizamos medias móviles de 5 y 60 periodos? ¿No sería mejor realizar primero una optimización y seleccionar los mejores periodos basándonos en datos históricos?
      Gamuchirai Zororo Ndawana
      Gamuchirai Zororo Ndawana | 10 jul 2025 en 10:07
      Aliaksandr Kazunka medias móviles de 5 y 60 periodos ? ¿No sería mejor optimizar primero y seleccionar los mejores periodos en función de los datos históricos?

      Hola Aliasandr, brillante pregunta, ¡porque tienes razón! Sería mejor optimizar primero y seleccionar el mejor período basado en datos históricos. Pero esa es una pregunta diferente por derecho propio, y merece atención a los detalles.

      Hemos cubierto cómo utilizar el aprendizaje automático para la selección de períodos utilizando el rango porcentual de William, y también hemos cubierto cómo utilizar todos los períodos a la vez utilizando el aprendizaje múltiple, cada uno en sus propios artículos.

      Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
      En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
      Algoritmo de viaje evolutivo en el tiempo — Time Evolution Travel Algorithm (TETA) Algoritmo de viaje evolutivo en el tiempo — Time Evolution Travel Algorithm (TETA)
      Se trata de un algoritmo propio. En este artículo, le presentaremos el Algoritmo de viaje evolutivo en el tiempo (TETA), inspirado en el concepto de universos paralelos y flujos temporales. La idea básica del algoritmo es que, si bien no es posible viajar en el tiempo en el sentido habitual, podemos elegir una secuencia de acontecimientos que generen realidades distintas.
      Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
      En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
      Algoritmo de trading evolutivo con aprendizaje por refuerzo y extinción de individuos no rentables (ETARE) Algoritmo de trading evolutivo con aprendizaje por refuerzo y extinción de individuos no rentables (ETARE)
      Hoy le presentamos un innovador algoritmo comercial que combina algoritmos evolutivos con aprendizaje profundo por refuerzo para la negociación de divisas. El algoritmo utiliza un mecanismo de extinción de individuos ineficaces para optimizar la estrategia comercial.