English Русский 中文 Deutsch 日本語 Português
preview
Automatización de estrategias de trading en MQL5 (Parte 20): Estrategia multisímbolo con CCI y AO

Automatización de estrategias de trading en MQL5 (Parte 20): Estrategia multisímbolo con CCI y AO

MetaTrader 5Trading |
27 0
Allan Munene Mutiiria
Allan Munene Mutiiria

Introducción

En nuestro artículo anterior (Parte 19), analizamos la estrategia de scalping «Trend Bounce» de Envelopes, centrándonos en la ejecución de las operaciones y la gestión del riesgo para completar su automatización en MetaQuotes Language 5 (MQL5). En la parte 20, presentamos una estrategia de trading sobre múltiples símbolos que utiliza el Commodity Channel Index (CCI) y el Oscilador Awesome (AO) para captar reversiones de tendencia en varios pares de divisas. Trataremos los siguientes temas:

  1. Hoja de ruta estratégica y arquitectura
  2. Implementación en MQL5
  3. Backtesting y optimización
  4. Conclusión

Al final, tendrás un sistema de trading MQL5 robusto para operar con múltiples símbolos, listo para su optimización e implementación. ¡Comencemos!


Hoja de ruta estratégica y arquitectura

En la parte 19, desarrollamos la estrategia de scalping «Rebote de tendencia con Envelopes», centrándonos en la ejecución de las operaciones y la gestión del riesgo para actuar en función de las señales generadas por las interacciones del precio con el indicador Envelopes, confirmadas por filtros de tendencia. Ahora, en la parte 20, pasamos a una estrategia de trading sobre múltiples símbolos que utiliza el Commodity Channel Index (CCI) y el Awesome Oscillator (AO) para identificar cambios de tendencia en varios pares de divisas en dos marcos temporales (M5 para las señales y H1 para la confirmación de la tendencia). Nuestra hoja de ruta para este artículo se centra en el diseño de un sistema escalable que procese señales de forma eficiente, ejecute operaciones y gestione el riesgo en múltiples símbolos.

Nuestro diseño arquitectónico hace hincapié en la modularidad y la solidez, y utiliza una estructura basada en clases en MQL5 para organizar los componentes de la estrategia. Nuestro objetivo es crear una clase unificada que se encargue de los cálculos de los indicadores (CCI y AO), de la generación de señales basadas en umbrales predefinidos y de la ejecución de operaciones con ajustes de stop-loss y take-profit, al tiempo que se garantiza que no haya órdenes abiertas para un símbolo antes de realizar nuevas operaciones. Este diseño incorpora medidas de seguridad, como comprobaciones de spread, y permite operar al aparecer una nueva barra o tick a tick, lo que ofrece flexibilidad para diversas condiciones de mercado y, en última instancia, proporciona un sistema de negociación coherente para múltiples símbolos. En pocas palabras, esto es lo que pretendemos conseguir.

PLAN ESTRATÉGICO


Implementación en MQL5

Para crear el programa en MQL5, abre el MetaEditor, ve al Navegador, busca la carpeta «Indicadores», haz clic en la pestaña «Nuevo» y sigue las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, empezaremos por declarar algunas estructuras y clases que vamos a utilizar, ya que queremos aplicar un enfoque de programación orientada a objetos (POO).

//+------------------------------------------------------------------+
//|                                          MultiSymbolCCIAO_EA.mq5 |
//|                           Copyright 2025, Allan Munene Mutiiria. |
//|                                   https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, Allan Munene Mutiiria."
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property strict
#property description "Multi-symbol trading strategy using CCI and AO indicators"

#include <Trade/Trade.mqh> //--- Include the Trade library for trading operations

//+------------------------------------------------------------------+
//| Input Parameters Structure                                       | //--- Define a structure for trading parameters
//+------------------------------------------------------------------+
struct TradingParameters {
   string            symbols;                  //--- Store comma-separated symbol list
   int               per_signal_cci;           //--- Set period for CCI signal
   int               per_trend_cci;            //--- Set period for CCI trend
   ENUM_APPLIED_PRICE price_cci;               //--- Specify applied price for CCI
   int               cci_signal_buy_value;     //--- Define CCI buy signal threshold
   int               cci_signal_sell_value;    //--- Define CCI sell signal threshold
   ENUM_TIMEFRAMES   tf_signal;                //--- Set timeframe for signal
   ENUM_TIMEFRAMES   tf_trend;                 //--- Set timeframe for trend
   bool              use_ao_for_trend;         //--- Enable AO for trend confirmation
   int               take;                     //--- Set take-profit in points
   int               stop;                     //--- Set stop-loss in points
   double            lots;                     //--- Specify trade lot size
   int               slip;                     //--- Set maximum slippage
   int               max_spread;               //--- Define maximum allowed spread
   int               magic;                    //--- Set magic number for trades
   bool              trade_anytime;            //--- Allow trading at any time
   string            comment;                  //--- Store trade comment
   int               tester_max_balance;       //--- Set tester balance limit
   bool              debug_mode;               //--- Enable debug mode
};

//+------------------------------------------------------------------+
//| Symbol Data Structure                                            | //--- Define a structure for symbol-specific data
//+------------------------------------------------------------------+
struct SymbolData {
   string            name;                     //--- Store symbol name
   datetime          last_bar_time;            //--- Track last bar timestamp
   int               cci_signal_handle;        //--- Hold CCI signal indicator handle
   int               cci_trend_handle;         //--- Hold CCI trend indicator handle
   int               ao_signal_handle;         //--- Hold AO signal indicator handle
   int               ao_trend_handle;          //--- Hold AO trend indicator handle
   double            cci_signal_data[];        //--- Store CCI signal data
   double            cci_trend_data[];         //--- Store CCI trend data
   double            ao_signal_data[];         //--- Store AO signal data
   double            ao_trend_data[];          //--- Store AO trend data
};

Comenzamos la implementación de la estrategia de trading multisímbolo CCI y AO en MQL5 configurando los componentes básicos para una ejecución sólida de las operaciones. Incluimos la biblioteca «Trade.mqh» para permitir las operaciones de trading a través de la clase «CTrade», que ofrece métodos para abrir y cerrar posiciones. Esta incorporación garantiza que dispongamos de las funciones de negociación esenciales necesarias para ejecutar órdenes de compra y venta en múltiples símbolos. 

A continuación, creamos la estructura «TradingParameters» para organizar todos los parámetros de entrada de la estrategia. Esta estructura contiene variables fundamentales como «symbols», que corresponde a la lista de pares de divisas separada por comas; «per_signal_cci» y «per_trend_cci», que corresponden a los períodos del indicador CCI; y «price_cci», que corresponde al tipo de precio aplicado. También definimos «cci_signal_buy_value» y «cci_signal_sell_value» para establecer los umbrales de las señales, «tf_signal» y «tf_trend» para los marcos temporales, y variables de gestión de riesgos como «take», «stop», «lots» y «max_spread». Además, «magic» garantiza una identificación única de cada operación, «trade_anytime» controla el momento de la operación y «debug_mode» activa el registro de diagnóstico, lo que proporciona una configuración centralizada para la estrategia.

Por último, definimos la estructura «SymbolData» para gestionar los datos de cada símbolo en el sistema de negociación. Esta estructura contiene «name» como identificador del símbolo, «last_bar_time» para registrar la marca de tiempo de la última barra, y manejadores de indicadores como «cci_signal_handle» y «ao_trend_handle» para los indicadores CCI y AO, tanto en los marcos temporales de señales como en los de tendencia. También incluimos matrices como «cci_signal_data» y «ao_trend_data» para almacenar los valores de los indicadores, lo que permite una gestión eficiente de los datos para el procesamiento de múltiples símbolos. Estas estructuras sientan las bases para un sistema de negociación modular y escalable. Lo siguiente que tenemos que hacer es declarar una clase «trading» para la lógica de control.

//+------------------------------------------------------------------+
//| Trading Strategy Class                                           | //--- Implement a class for trading strategy logic
//+------------------------------------------------------------------+
class CTradingStrategy {
private:
   CTrade            m_trade;                  //--- Initialize trade object for trading operations
   TradingParameters m_params;                 //--- Store trading parameters
   SymbolData        m_symbols[];              //--- Store array of symbol data
   int               m_array_size;             //--- Track number of symbols
   datetime          m_last_day;               //--- Store last day timestamp
   bool              m_is_new_day;             //--- Indicate new day detection
   int               m_candle_shift;           //--- Set candle shift for signal calculation
   const int         CCI_TREND_BUY_VALUE;      //--- Define constant for CCI trend buy threshold
   const int         CCI_TREND_SELL_VALUE;     //--- Define constant for CCI trend sell threshold
}

Aquí implementamos la lógica principal de la estrategia creando la clase «CTradingStrategy», que encapsula toda la funcionalidad de negociación de forma modular y organizada. Definimos variables miembro privadas para gestionar el estado y las operaciones de la estrategia, empezando por «m_trade», una instancia de la clase «CTrade» de la biblioteca Trade, encargada de gestionar tareas de ejecución de operaciones como la apertura y el cierre de posiciones. A continuación, incluimos «m_params», una instancia de la estructura «TradingParameters», para almacenar todos los ajustes de configuración, como las listas de símbolos, los períodos de los indicadores y los parámetros de riesgo, lo que garantiza un acceso centralizado a los datos definidos por el usuario.

También declaramos «m_symbols», una matriz de la estructura «SymbolData», para almacenar los datos de cada símbolo, incluidos los manejadores de indicadores y los búferes de datos, lo que facilita el procesamiento de varios símbolos. Para llevar la cuenta del número de símbolos, utilizamos «m_array_size», mientras que «m_last_day» y «m_is_new_day» se encargan de las actualizaciones diarias de las marcas de tiempo para detectar nuevos días de negociación. Además, configuramos «m_candle_shift» para controlar si las señales se generan en la barra actual o en la anterior, en función del parámetro «trade_anytime». Por último, definimos las constantes «CCI_TREND_BUY_VALUE» y «CCI_TREND_SELL_VALUE» para establecer umbrales fijos del CCI que permitan confirmar la tendencia, lo que garantiza una lógica de señales coherente en toda la estrategia. Ahora podemos incluir más métodos con el modificador de acceso «private», tal y como se muestra a continuación, para facilitar su uso.

void PrintDebug(string text) {              //--- Define method to print debug messages
   if(m_params.debug_mode && !MQLInfoInteger(MQL_OPTIMIZATION)) { //--- Check debug mode and optimization status
      Print(text);                          //--- Output debug message
   }
}

void PrintMessage(string text) {            //--- Define method to print informational messages
   if(!MQLInfoInteger(MQL_OPTIMIZATION)) {  //--- Check if not in optimization mode
      Print(text);                          //--- Output message
   }
}

void PrepareSymbolsList() {                 //--- Define method to prepare symbol list
   string symbols_array[];                  //--- Initialize temporary array for symbols
   ushort sep = StringGetCharacter(",", 0); //--- Get comma separator character
   m_array_size = StringSplit(m_params.symbols, sep, symbols_array); //--- Split symbols string into array
   ArrayResize(m_symbols, m_array_size);    //--- Resize symbol data array
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      m_symbols[i].name = symbols_array[i]; //--- Set symbol name
      m_symbols[i].last_bar_time = 0;       //--- Initialize last bar time
      SymbolSelect(m_symbols[i].name, true); //--- Ensure symbol is in market watch
   }
}

Implementamos métodos de utilidad dentro de la clase «CTradingStrategy» para gestionar la depuración y la preparación de símbolos para la estrategia CCI y AO con múltiples símbolos. Creamos la función «PrintDebug» para mostrar mensajes de diagnóstico cuando «m_params.debug_mode» está activado y el EA no se encuentra en modo de optimización, utilizando Print para registrar el parámetro «text» con fines de resolución de problemas. Del mismo modo, definimos la función «PrintMessage» para registrar mensajes informativos, asegurándonos de que la salida solo se produzca fuera del modo de optimización mediante la comprobación de «MQLInfoInteger(MQL_OPTIMIZATION)» y utilizando «Print» como valor para el parámetro «text».

Además, desarrollamos la función «PrepareSymbolsList» para inicializar la matriz de datos de símbolos. Declaramos una variable temporal «symbols_array» para almacenar los símbolos resultantes de la división, utilizamos StringGetCharacter para obtener el separador (la coma) para «sep» y aplicamos StringSplit para dividir «m_params.symbols» y asignar los resultados a «symbols_array». Cambiamos el tamaño de «m_symbols» utilizando ArrayResize en función de «m_array_size» y realizamos un bucle para asignar un símbolo a cada «m_symbols[i].name», inicializamos «m_symbols[i]. last_bar_time» a cero y llamamos a SymbolSelect para asegurarnos de que cada símbolo esté disponible en Observación del mercado (Market Watch) para operar. A continuación, debemos inicializar y actualizar los valores de los indicadores.

bool InitializeIndicators(int index) {      //--- Define method to initialize indicators
   m_symbols[index].cci_signal_handle = iCCI(m_symbols[index].name, m_params.tf_signal, m_params.per_signal_cci, m_params.price_cci); //--- Create CCI signal indicator
   if(m_symbols[index].cci_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].cci_trend_handle = iCCI(m_symbols[index].name, m_params.tf_trend, m_params.per_trend_cci, m_params.price_cci); //--- Create CCI trend indicator
   if(m_symbols[index].cci_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF CCI TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_signal_handle = iAO(m_symbols[index].name, m_params.tf_signal); //--- Create AO signal indicator
   if(m_symbols[index].ao_signal_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO SIGNAL FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   m_symbols[index].ao_trend_handle = iAO(m_symbols[index].name, m_params.tf_trend); //--- Create AO trend indicator
   if(m_symbols[index].ao_trend_handle == INVALID_HANDLE) { //--- Check for invalid handle
      Print("INITIALIZATION OF AO TREND FAILED: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   ArraySetAsSeries(m_symbols[index].cci_signal_data, true); //--- Set CCI signal data as series
   ArraySetAsSeries(m_symbols[index].cci_trend_data, true); //--- Set CCI trend data as series
   ArraySetAsSeries(m_symbols[index].ao_signal_data, true); //--- Set AO signal data as series
   ArraySetAsSeries(m_symbols[index].ao_trend_data, true); //--- Set AO trend data as series
   return true;                             //--- Return success
}
   
bool UpdateIndicatorData(int index) {       //--- Define method to update indicator data
   if(CopyBuffer(m_symbols[index].cci_signal_handle, 0, 0, 3, m_symbols[index].cci_signal_data) < 3) { //--- Copy CCI signal data
      Print("UNABLE TO COPY CCI SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].cci_trend_handle, 0, 0, 3, m_symbols[index].cci_trend_data) < 3) { //--- Copy CCI trend data
      Print("UNABLE TO COPY CCI TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_signal_handle, 0, 0, 3, m_symbols[index].ao_signal_data) < 3) { //--- Copy AO signal data
      Print("UNABLE TO COPY AO SIGNAL DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   if(CopyBuffer(m_symbols[index].ao_trend_handle, 0, 0, 3, m_symbols[index].ao_trend_data) < 3) { //--- Copy AO trend data
      Print("UNABLE TO COPY AO TREND DATA: ", m_symbols[index].name); //--- Log error
      return false;                         //--- Return failure
   }
   return true;                             //--- Return success
}

Aquí implementamos la configuración y las actualizaciones de los indicadores en la clase «CTradingStrategy». Creamos la función «InitializeIndicators» para establecer «m_symbols[index].cci_signal_handle» y «m_symbols[index].cci_trend_handle» mediante la función iCCI, y «m_symbols[index].ao_signal_handle» y «m_symbols[index]. ao_trend_handle» mediante la función iAO para un símbolo en «index», registrando los errores con «Print» si los manejadores son INVALID_HANDLE. Configuramos los conjuntos de datos como series temporales mediante ArraySetAsSeries.

También desarrollamos la función «UpdateIndicatorData» para copiar tres puntos de datos en «m_symbols[index].cci_signal_data», «m_symbols[index].cci_trend_data», «m_symbols[index].ao_signal_data» y «m_symbols[index]. ao_trend_data» mediante la función CopyBuffer, registrando los errores con «Print» si no se recuperan datos suficientes. A continuación, tenemos que realizar un recuento de pedidos para poder llevar el control del número de posiciones abiertas.

int CountOrders(string symbol, int magic, ENUM_POSITION_TYPE type) { //--- Define method to count orders
   int count = 0;                           //--- Initialize order counter
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket)) {   //--- Select position by ticket
         if(PositionGetInteger(POSITION_MAGIC) == magic && //--- Check magic number
            PositionGetString(POSITION_SYMBOL) == symbol && //--- Check symbol
            PositionGetInteger(POSITION_TYPE) == type) { //--- Check position type
            count++;                        //--- Increment counter
         }
      }
   }
   return count;                            //--- Return order count
}

long OpenOrder(string symbol, ENUM_ORDER_TYPE type, double price, double sl, double tp, double lots, int magic, string comment) { //--- Define method to open orders
   long ticket = m_trade.PositionOpen(symbol, type, lots, price, sl, tp, comment); //--- Execute position open
   if(ticket < 0) {                         //--- Check for order failure
      PrintMessage(StringFormat("Info - OrderSend %s %d_%s_%.5f error %.5f_%.5f_%.5f_#%d", //--- Log error
         comment, type, symbol, price, price, sl, tp, GetLastError()));
   } else {                                 //--- Handle successful order
      PrintMessage(StringFormat("Info - OrderSend done. Comment:%s, Type:%d, Sym:%s, Price:%.5f, SL:%.5f, TP:%.5f", //--- Log success
         comment, type, symbol, price, sl, tp));
   }
   return ticket;                           //--- Return order ticket
}

Aquí implementamos las funciones de gestión de operaciones dentro de la clase «CTradingStrategy» para gestionar el recuento y la ejecución de órdenes. Creamos la función «CountOrders» para contabilizar las posiciones abiertas de un «símbolo» y un número «mágico» determinados, de tipo ENUM_POSITION_TYPE. Inicializamos «count» en cero, recorremos las posiciones utilizando «PositionsTotal» y PositionGetTicket, y utilizamos PositionSelectByTicket para comprobar si «PositionGetInteger(POSITION_MAGIC)», «PositionGetString(POSITION_SYMBOL)» y «PositionGetInteger(POSITION_TYPE)» coinciden con los datos introducidos, incrementando «count» por cada coincidencia antes de devolverlo.

También desarrollamos la función «OpenOrder» para ejecutar operaciones con un «símbolo» y un «tipo» específicos, con los parámetros «precio», «sl» (stop-loss), «tp» (take-profit), «lotes», «magic» y «comentario». Llamamos a «m_trade.PositionOpen» para abrir la posición, almacenamos el resultado en «ticket» y utilizamos «PrintMessage» con StringFormat para registrar los errores si «ticket» es negativo, incluyendo GetLastError, o para registrar los detalles del éxito si se ha realizado la orden, devolviendo «ticket» para su seguimiento. Una vez hecho esto, podemos definir una clase pública desde la que podamos inicializar los miembros.

public:
   CTradingStrategy() : CCI_TREND_BUY_VALUE(-114), CCI_TREND_SELL_VALUE(134) { //--- Initialize constructor with constants
      m_last_day = 0;                          //--- Set initial last day
      m_is_new_day = true;                     //--- Set new day flag
      m_array_size = 0;                        //--- Set initial array size
   }
   
   bool Init() {                               //--- Define initialization method
      m_params.symbols = "EURUSDm,GBPUSDm,AUDUSDm"; //--- Set default symbols
      m_params.per_signal_cci = 20;            //--- Set CCI signal period
      m_params.per_trend_cci = 24;             //--- Set CCI trend period
      m_params.price_cci = PRICE_TYPICAL;      //--- Set CCI applied price
      m_params.cci_signal_buy_value = -90;     //--- Set CCI buy signal threshold
      m_params.cci_signal_sell_value = 130;    //--- Set CCI sell signal threshold
      m_params.tf_signal = PERIOD_M5;          //--- Set signal timeframe
      m_params.tf_trend = PERIOD_H1;           //--- Set trend timeframe
      m_params.use_ao_for_trend = false;       //--- Disable AO trend by default
      m_params.take = 200;                     //--- Set take-profit
      m_params.stop = 300;                     //--- Set stop-loss
      m_params.lots = 0.01;                    //--- Set lot size
      m_params.slip = 5;                       //--- Set slippage
      m_params.max_spread = 20;                //--- Set maximum spread
      m_params.magic = 123456789;              //--- Set magic number
      m_params.trade_anytime = false;          //--- Disable trade anytime
      m_params.comment = "EA_AO_BP";           //--- Set trade comment
      m_params.tester_max_balance = 0;         //--- Set tester balance limit
      m_params.debug_mode = false;             //--- Disable debug mode
      
      m_candle_shift = m_params.trade_anytime ? 0 : 1; //--- Set candle shift based on trade mode
      m_trade.SetExpertMagicNumber(m_params.magic); //--- Set magic number for trade object
      
      PrepareSymbolsList();                    //--- Prepare symbol list
      for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
         if(!InitializeIndicators(i)) {         //--- Initialize indicators
            return false;                      //--- Return failure on error
         }
      }
      PrintMessage("Current Spread on " + Symbol() + ": " + //--- Log current spread
         IntegerToString((int)SymbolInfoInteger(Symbol(), SYMBOL_SPREAD)));
      return true;                             //--- Return success
   }

En el modificador de acceso público, implementamos la lógica de inicialización. Definimos el constructor «CTradingStrategy» para inicializar «CCI_TREND_BUY_VALUE» y «CCI_TREND_SELL_VALUE» como constantes con los valores -114 y 134, respectivamente, y establecemos «m_last_day» en cero, «m_is_new_day» a «true» y «m_array_size» a cero para la gestión del estado inicial.

También creamos la función «Init» para configurar los parámetros y los recursos de la estrategia. Asignamos valores por defecto a los miembros de «m_params», incluyendo «m_params.symbols» para los pares de divisas, «m_params.per_signal_cci» y «m_params.per_trend_cci» para los períodos del CCI, «m_params.price_cci» como «PRICE_TYPICAL», y umbrales como «m_params.cci_signal_buy_value» y «m_params.cci_signal_sell_value».

Establecemos «m_params.tf_signal» en PERIOD_M5, «m_params.tf_trend» en «PERIOD_H1» y los parámetros de riesgo, como «m_params.take», «m_params.stop» y «m_params.lots». Configuramos «m_candle_shift» en función de «m_params.trade_anytime», llamamos a «m_trade.SetExpertMagicNumber» con «m_params.magic» e invocamos «PrepareSymbolsList» para configurar los símbolos. Recorremos «m_array_size» para llamar a «InitializeIndicators» para cada símbolo, devolviendo «false» en caso de error, y registramos el spread actual mediante «PrintMessage» antes de devolver «true» si la inicialización se completa correctamente.

Por último, tras inicializarlo todo, podemos definir el controlador de eventos OnTick dentro de nuestra clase, de modo que solo podamos invocar este método desde el controlador de eventos real. Por eso es necesario que se haga público aquí. También puedes configurarlo como virtual, pero, por ahora mantendremos una implementación simple.

void OnTick() {                             //--- Define tick handling method
   datetime new_day = iTime(Symbol(), PERIOD_D1, 0); //--- Get current day timestamp
   m_is_new_day = (m_last_day != new_day);  //--- Check for new day
   if(m_is_new_day) {                       //--- Handle new day
      m_last_day = new_day;                 //--- Update last day
   }
   
   for(int i = 0; i < m_array_size; i++) {  //--- Iterate through symbols
      bool is_new_bar = false;              //--- Initialize new bar flag
      bool buy_signal = false, sell_signal = false; //--- Initialize signal flags
      bool buy_trend = false, sell_trend = false; //--- Initialize trend flags
      
      datetime new_time = iTime(m_symbols[i].name, m_params.tf_signal, 0); //--- Get current bar time
      if(!m_params.trade_anytime && m_symbols[i].last_bar_time != new_time) { //--- Check for new bar
         is_new_bar = true;                 //--- Set new bar flag
         m_symbols[i].last_bar_time = new_time; //--- Update last bar time
      }
      
      if(!UpdateIndicatorData(i)) continue; //--- Update indicators, skip on failure
      
      double ask = SymbolInfoDouble(m_symbols[i].name, SYMBOL_ASK); //--- Get ask price
      double bid = SymbolInfoDouble(m_symbols[i].name, SYMBOL_BID); //--- Get bid price
      double point = SymbolInfoDouble(m_symbols[i].name, SYMBOL_POINT); //--- Get point value
      long spread = SymbolInfoInteger(Symbol(), SYMBOL_SPREAD); //--- Get current spread
      
      int total_orders = CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_BUY) + //--- Count buy orders
                        CountOrders(m_symbols[i].name, m_params.magic, POSITION_TYPE_SELL); //--- Count sell orders
      
      // Generate signals
      buy_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] < m_params.cci_signal_buy_value && //--- Check CCI buy signal condition
                  m_symbols[i].cci_signal_data[m_candle_shift] > m_params.cci_signal_buy_value && //--- Confirm CCI buy signal
                  m_symbols[i].ao_signal_data[m_candle_shift+1] < m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO buy signal
                  
      sell_signal = m_symbols[i].cci_signal_data[m_candle_shift+1] > m_params.cci_signal_sell_value && //--- Check CCI sell signal condition
                   m_symbols[i].cci_signal_data[m_candle_shift] < m_params.cci_signal_sell_value && //--- Confirm CCI sell signal
                   m_symbols[i].ao_signal_data[m_candle_shift+1] > m_symbols[i].ao_signal_data[m_candle_shift]; //--- Confirm AO sell signal
      
      buy_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] < m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend buy condition
                 m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                 m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                 (!m_params.use_ao_for_trend || //--- Check AO trend condition
                  (m_symbols[i].ao_trend_data[m_candle_shift+1] < m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend buy
                  
      sell_trend = m_symbols[i].cci_trend_data[m_candle_shift+1] > m_symbols[i].cci_signal_data[m_candle_shift] && //--- Check CCI trend sell condition
                  m_symbols[i].cci_trend_data[m_candle_shift] > CCI_TREND_BUY_VALUE && //--- Confirm CCI trend buy threshold
                  m_symbols[i].cci_trend_data[m_candle_shift] < CCI_TREND_SELL_VALUE && //--- Confirm CCI trend sell threshold
                  (!m_params.use_ao_for_trend || //--- Check AO trend condition
                   (m_symbols[i].ao_trend_data[m_candle_shift+1] > m_symbols[i].ao_trend_data[m_candle_shift])); //--- Confirm AO trend sell
      
      // Execute trades
      if(spread < m_params.max_spread && total_orders == 0 && //--- Check spread and open orders
         (m_params.trade_anytime || is_new_bar)) { //--- Check trade timing
         if(buy_signal && buy_trend) {         //--- Check buy conditions
            double sl = m_params.stop == 0 ? 0 : ask - m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : ask + m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_BUY, ask, sl, tp, m_params.lots, //--- Open buy order
                     m_params.magic, "Open BUY " + m_params.comment);
         }
         if(sell_signal && sell_trend) {       //--- Check sell conditions
            double sl = m_params.stop == 0 ? 0 : bid + m_params.stop * point; //--- Calculate stop-loss
            double tp = m_params.take == 0 ? 0 : bid - m_params.take * point; //--- Calculate take-profit
            OpenOrder(m_symbols[i].name, ORDER_TYPE_SELL, bid, sl, tp, m_params.lots, //--- Open sell order
                     m_params.magic, "Open SELL " + m_params.comment);
         }
      }
      
      // Debug output
      if((m_params.trade_anytime || is_new_bar) && (buy_signal || sell_signal)) { //--- Check debug conditions
         PrintDebug(StringFormat("Debug - IsNewBar: %b - candle_shift: %d - buy_signal: %b - " //--- Log debug information
                               "sell_signal: %b - buy_trend: %b - sell_trend: %b",
                               is_new_bar, m_candle_shift, buy_signal, sell_signal, 
                               buy_trend, sell_trend));
      }
   }
}

Aquí implementamos la lógica de negociación de la estrategia CCI y AO para múltiples símbolos creando la función OnTick en la clase «CTradingStrategy». Recuperamos la marca de tiempo del día actual con iTime y la almacenamos en «new_day», y actualizamos «m_is_new_day» y «m_last_day» para realizar un seguimiento de los cambios diarios. Para cada símbolo de «m_array_size», inicializamos las variables «is_new_bar», «buy_signal», «sell_signal», «buy_trend» y «sell_trend», y utilizamos «iTime» para detectar nuevas barras, actualizando «m_symbols[i].last_bar_time» si «m_params.trade_anytime» es falso.

Llamamos a «UpdateIndicatorData» para actualizar los indicadores, omitiendo la operación en caso de error, y recuperamos «ask», «bid», «point» y «spread» mediante SymbolInfoDouble y SymbolInfoInteger. Calculamos «total_orders» utilizando «CountOrders» para las posiciones de compra y venta. Establecemos la «señal de compra» comparando «m_symbols[i].cci_signal_data» con «m_params.cci_signal_buy_value» y «m_symbols[i].ao_signal_data» para confirmarla, y la «señal de venta» con «m_params.cci_signal_sell_value». Determinamos «buy_trend» y «sell_trend» utilizando «m_symbols[i].cci_trend_data», «CCI_TREND_BUY_VALUE», «CCI_TREND_SELL_VALUE» y, opcionalmente, «m_symbols[i].ao_trend_data».

Si «spread» es inferior a «m_params.max_spread», «total_orders» es cero y se permite operar, calculamos «sl» y «tp» utilizando «m_params.stop» y «m_params.take», y a continuación llamamos a «OpenOrder» para las operaciones de compra o venta. Registramos los estados de las señales mediante «PrintDebug» y StringFormat cuando se activa la depuración, lo que garantiza un seguimiento eficaz de las operaciones. Para cerrar las posiciones, implementamos la siguiente función.

bool CloseAllTrades() {                     //--- Define method to close all trades
   for(int i = PositionsTotal() - 1; i >= 0; i--) { //--- Iterate through positions
      ulong ticket = PositionGetTicket(i);   //--- Get position ticket
      if(PositionSelectByTicket(ticket) &&   //--- Select position
         PositionGetInteger(POSITION_MAGIC) == m_params.magic) { //--- Check magic number
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- Get position type
         if(type == POSITION_TYPE_BUY || type == POSITION_TYPE_SELL) { //--- Check position type
            m_trade.PositionClose(ticket);   //--- Close position
            PrintMessage("Position close " + IntegerToString(ticket)); //--- Log closure
         }
      }
   }
   for(int i = OrdersTotal() - 1; i >= 0; i--) { //--- Iterate through orders
      ulong ticket = OrderGetTicket(i);      //--- Get order ticket
      if(OrderSelect(ticket) && OrderGetInteger(ORDER_MAGIC) == m_params.magic) { //--- Select order and check magic
         ENUM_ORDER_TYPE type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); //--- Get order type
         if(type >= ORDER_TYPE_BUY_STOP && type <= ORDER_TYPE_SELL_LIMIT) { //--- Check order type
            m_trade.OrderDelete(ticket);   //--- Delete order
            PrintMessage("Order delete " + IntegerToString(ticket)); //--- Log deletion
         }
      }
   }
   return true;                             //--- Return success
}

Aquí implementamos la funcionalidad de cierre de operaciones para la estrategia CCI y AO con múltiples símbolos mediante la creación de la función «CloseAllTrades» dentro de la clase «CTradingStrategy». Recorremos todas las posiciones abiertas utilizando PositionsTotal y recuperamos el ticket de cada posición con PositionGetTicket, asignándolo a la variable «ticket». Para cada posición seleccionada mediante «PositionSelectByTicket», comprobamos si «PositionGetInteger(POSITION_MAGIC)» coincide con «m_params.magic», y si «PositionGetInteger(POSITION_TYPE)» es «POSITION_TYPE_BUY» o «POSITION_TYPE_SELL», llamamos a «m_trade. PositionClose» para cerrarla y registramos la acción con «PrintMessage» y IntegerToString.

Además, recorremos las órdenes pendientes utilizando OrdersTotal y obtenemos el ticket de cada orden mediante «OrderGetTicket», que almacenamos en «ticket». Si OrderSelect se ejecuta correctamente y «OrderGetInteger(ORDER_MAGIC)» es igual a «m_params.magic», comprobamos si OrderGetInteger(ORDER_TYPE) está comprendido entre «ORDER_TYPE_BUY_STOP» y «ORDER_TYPE_SELL_LIMIT»; a continuación, utilizamos «m_trade.OrderDelete» para eliminar la orden y la registramos con «PrintMessage» e «IntegerToString». Devolvemos «true» para indicar que se han cerrado correctamente todas las operaciones y órdenes pertinentes. ¡Y eso es todo! Ahora solo nos queda utilizar las clases.

//+------------------------------------------------------------------+
//| Global Variables and Functions                                   | //--- Define global variables and functions
//+------------------------------------------------------------------+
CTradingStrategy g_strategy;                   //--- Initialize global strategy object

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                 //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED; //--- Initialize strategy and return status
}

Finalizamos la implementación de la estrategia CCI y AO multisímbolo definiendo las funciones globales y principales del asesor experto. Declaramos «g_strategy» como una instancia global de la clase «CTradingStrategy» para gestionar todas las operaciones de negociación en todos los símbolos, lo que garantiza un acceso centralizado a la lógica y al estado de la estrategia a lo largo de todo el ciclo de vida del EA.

También creamos la función «OnInit» para gestionar la inicialización del EA. Llamamos a la función «Init» de «g_strategy» para configurar los parámetros, los símbolos y los indicadores, y devolvemos INIT_SUCCEEDED si se realiza correctamente o «INIT_FAILED» si se produce un error, lo que garantiza un estado de inicialización adecuado para la plataforma MetaTrader 5. Al iniciar el programa, obtenemos el siguiente resultado.

INIT DEL EA

En la imagen podemos ver que disponemos de todos los datos correspondientes a los tres símbolos que hemos seleccionado. Ahora podemos pasar a llamar al controlador de eventos OnTick para encargarse de la lógica principal, y con eso ya estará todo hecho.

//+------------------------------------------------------------------+
//| Expert Advisor Functions                                         | //--- Define EA core functions
//+------------------------------------------------------------------+
int OnInit() {                                                         //--- Define initialization function
   return g_strategy.Init() ? INIT_SUCCEEDED : INIT_FAILED;            //--- Initialize strategy and return status
}

//+------------------------------------------------------------------+

Simplemente llamamos a la función «Init» del objeto global «g_strategy», una instancia de la clase «CTradingStrategy», para inicializar los parámetros de negociación, las listas de símbolos y los indicadores. Devolvemos INIT_SUCCEEDED si la inicialización se realiza con éxito o «INIT_FAILED» si se produce un error, lo que garantiza que la plataforma MetaTrader 5 reciba el estado adecuado para continuar o detener la ejecución. Tras la compilación, obtenemos el siguiente resultado.

RESULTADO FINAL DE LAS OPERACIONES

En la imagen podemos ver que podemos abrir operaciones basándonos en señales confirmadas para los respectivos símbolos y gestionarlas de forma individual. Lo que queda por hacer es realizar un backtest del programa, y eso se trata en la siguiente sección.


Backtesting y optimización

Tras realizar un exhaustivo análisis retrospectivo, hemos obtenido los siguientes resultados.

Gráfico de backtest:

GRÁFICO

Informe de backtesting:

INFORME


Conclusión

En conclusión, hemos desarrollado un programa MQL5 que automatiza la estrategia «Multi-Symbol CCI y AO», ejecutando operaciones en múltiples pares de divisas mediante la clase «CTradingStrategy» para la generación de señales con los indicadores CCI y AO, y una gestión sólida de las operaciones a través de la biblioteca «CTrade». Gracias a sus componentes modulares y a controles de riesgo como la validación de diferenciales y la configuración de órdenes stop-loss, este sistema ofrece un marco escalable que puedes personalizar ajustando los parámetros o integrando filtros adicionales.

Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. El trading conlleva riesgos financieros importantes, y la volatilidad del mercado puede dar lugar a pérdidas. Es fundamental realizar pruebas retrospectivas exhaustivas y llevar a cabo una gestión cuidadosa del riesgo antes de poner en marcha este programa en los mercados reales.

Aprovechando las habilidades y los conceptos presentados, podrás perfeccionar este sistema de negociación con múltiples símbolos o adaptar su arquitectura para crear nuevas estrategias, ampliando así tus conocimientos sobre la negociación algorítmica en MQL5.

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

Archivos adjuntos |
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.
Repetición y simulación de mercado: Gran final Repetición y simulación de mercado: Gran final
Sé que muchos podrían haber imaginado que publicaría más artículos para explicar otros aspectos del sistema. Los elementos que faltan son sencillos de implementar. Aun así, su desarrollo te permitirá comprobar hasta qué punto estás realmente preparado.
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.
Del básico al intermedio: Sobrecarga de operadores (V) Del básico al intermedio: Sobrecarga de operadores (V)
En este artículo vemos cómo manipular un código para implementar una solución completamente diferente de lo que muchos creen posible en MQL5. Una observación importante: para comprender adecuadamente este contenido, es necesario haber asimilado correctamente los conceptos tratados en los artículos anteriores.