Automatización de estrategias de trading en MQL5 (Parte 20): Estrategia multisímbolo con CCI y AO
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:
- Hoja de ruta estratégica y arquitectura
- Implementación en MQL5
- Backtesting y optimización
- 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.

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.

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.

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:

Informe de backtesting:

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
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Repetición y simulación de mercado: Gran final
Particularidades del trabajo con números del tipo double en MQL4
Del básico al intermedio: Sobrecarga de operadores (V)
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso