Desarrollo de asesores expertos autooptimizables en MQL5 (Parte 8): Análisis de múltiples estrategias (2)
En esta serie, exploramos formas creativas de combinar diferentes estrategias de trading en un conjunto único y coherente. El objetivo es superar las limitaciones de las estrategias individuales fusionándolas en algo más potente.
En nuestra última conversación, creamos una superclase que serviría de base para todas nuestras estrategias de trading. Esta superclase nos permitió implementar nuestra primera estrategia, un cruce de medias móviles, en MQL5. Posteriormente, comparamos nuestra clase de estrategia flexible con una versión codificada de forma rígida de la misma estrategia para verificar que el rendimiento coincidiera.
Utilizando el probador de estrategias de MetaTrader 5, pudimos encontrar una configuración de parámetros sólida. Encontrar buenos parámetros por cuenta propia puede resultar complicado. Por eso, el optimizador genético de MetaTrader 5 es una herramienta tan valiosa. Ayuda a automatizar el proceso, ahorrando tiempo y esfuerzo.
También utilizamos técnicas de pruebas prospectivas para filtrar los conjuntos de parámetros estables de nuestros resultados. Esto confirmó que nuestra implementación fue precisa y eficiente.
Hoy vamos un paso más allá. Crearemos una segunda estrategia basada en el Índice de Fuerza Relativa (RSI) y luego la combinaremos con nuestra estrategia de cruce de medias móviles. Al combinarlas, nuestro objetivo es crear una estrategia combinada más sólida y potencialmente más rentable. También utilizaremos el probador de estrategias de MetaTrader 5 para optimizar esta nueva estrategia combinada. Pero antes de profundizar, es importante hablar de un concepto clave: la minimización de parámetros.
A medida que añadimos más componentes a nuestra estrategia, el número de parámetros puede aumentar rápidamente. Una estrategia con demasiados elementos variables se vuelve más difícil, y a veces casi imposible, de optimizar de manera efectiva. Por eso es crucial limitar el número de parámetros siempre que sea posible. A menudo, fijar ciertos parámetros a valores constantes nos permite centrarnos en optimizar los que tienen mayor impacto.
Para poner las cosas en perspectiva, nuestra estrategia original de media móvil logró un índice de Sharpe de 1,29 en las pruebas prospectivas, con una ganancia de 133,51 dólares en 101 operaciones. En cambio, la nueva estrategia basada en el RSI que vamos a desarrollar hoy logró un índice de Sharpe de 2,68 y una ganancia de 214,08 dólares, con tan solo 52 operaciones.
Eso significa que la nueva estrategia no solo generó más dinero, sino que lo hizo con menos operaciones, menor exposición al mercado y menos riesgo. Este es precisamente el tipo de rendimiento que buscamos al diseñar estrategias de trading.
Trabajando en estrecha colaboración con la herramienta de prueba de estrategias y utilizándola con criterio, podemos descubrir configuraciones más rentables. Dicho esto, es importante gestionar las expectativas. El probador de estrategias de MetaTrader 5 no puede corregir mágicamente una mala estrategia. Si una estrategia tiene fallos fundamentales, ninguna optimización la hará rentable.
Incluso con una buena estrategia, la optimización no garantiza mejores resultados, pero puede mejorar significativamente tus posibilidades si se utiliza correctamente. Con una base sólida y las herramientas adecuadas, no hay límite al rendimiento que podemos obtener.
Primeros pasos en MQL5
Comenzamos creando primero la clase para nuestra estrategia de punto medio RSI. El primer objetivo que debemos alcanzar es cargar las dependencias que requiere nuestra clase de estrategia. La primera dependencia es la clase RSI que creamos para nuestros indicadores de búfer único. Además, también necesitaremos la superclase para todas nuestras estrategias, también llamada clase padre.
//+------------------------------------------------------------------+ //| RSIMidPoint.mqh | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <VolatilityDoctor\Indicators\RSI.mqh> #include <VolatilityDoctor\Strategies\Parent\Strategy.mqh>
Ahora que hemos cargado nuestras dependencias, podemos comenzar a definir la clase de estrategia de punto medio del RSI y sus miembros. Tenga en cuenta que esta estrategia de punto medio hereda de la clase base para todas nuestras estrategias, que denotamos utilizando la sintaxis de dos puntos. A partir de ahí, definimos los miembros de nuestra clase de estrategia de punto medio. Observe que solo tenemos que definir unos pocos miembros. Además, tenga en cuenta que todos los métodos virtuales que creamos en la clase de estrategia principal deben volver a aparecer como virtuales en nuestra clase de punto medio RSI.
class RSIMidPoint : public Strategy { private: //--- The instance of the RSI used in this strategy RSI *my_rsi; public: //--- Class constructor RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price); //--- Class destructor ~RSIMidPoint(); //--- Class overrides virtual bool Update(void); virtual bool BuySignal(void); virtual bool SellSignal(void); };
Ahora podemos empezar a considerar cómo se implementará cada método específicamente para nuestra estrategia RSI. El método de actualización solo necesita actualizar el valor del indicador RSI y luego asegurarse de que la lectura actual del indicador RSI no sea cero. Si ese es el caso, entonces todo salió bien; de lo contrario, algo salió mal.
//+------------------------------------------------------------------+ //| Our strategy update method | //+------------------------------------------------------------------+ bool RSIMidPoint::Update(void) { //--- Set the indicator value my_rsi.SetIndicatorValues(Strategy::GetIndicatorBufferSize(),true); //--- Check readings are valid if(my_rsi.GetCurrentReading() != 0) return(true); //--- Something went wrong return(false); }
A continuación, analizaremos los casos que justifican nuestras señales de compra y venta. Nuestras señales de compra se generan cuando la lectura del RSI está por debajo de 50, mientras que, por otro lado, nuestras señales de venta se generan cuando el RSI está por encima de 50.
//+------------------------------------------------------------------+ //| Check for our buy signal | //+------------------------------------------------------------------+ bool RSIMidPoint::BuySignal(void) { //--- Buy signals when the RSI is below 50 return(my_rsi.GetCurrentReading()<50); } //+------------------------------------------------------------------+ //| Check for our sell signal | //+------------------------------------------------------------------+ bool RSIMidPoint::SellSignal(void) { //--- Sell signals when the RSI is above 50 return(my_rsi.GetCurrentReading()>50); }
Por último, debemos considerar el constructor y el destructor de la clase. El constructor de la clase recibirá los detalles requeridos por nuestro indicador RSI. En concreto, nuestro constructor requiere que el usuario especifique sobre qué símbolo, qué marco temporal, qué período y sobre qué precio se debe aplicar el RSI. Una vez especificados todos esos detalles, cargamos la información y creamos una nueva instancia de nuestro indicador RSI.
Sin embargo, tenga en cuenta que esta versión particular del indicador RSI que utilizamos no es la misma que viene preinstalada en MetaTrader 5. Se trata de un tipo personalizado que hemos definido y que cuenta con muchas otras funciones útiles que utilizaremos. No obstante, la funcionalidad general es la misma, y los lectores pueden implementar algunos de estos métodos desde cero si lo necesitan.
//+------------------------------------------------------------------+ //| Our class constructor | //+------------------------------------------------------------------+ RSIMidPoint::RSIMidPoint(string user_symbol,ENUM_TIMEFRAMES user_timeframe,int user_period,ENUM_APPLIED_PRICE user_price) { my_rsi = new RSI(user_symbol,user_timeframe,user_period,user_price); Print("RSI-Mid-Point Strategy Loaded."); }
Finalmente, en el destructor de nuestra clase, eliminamos el puntero al objeto RSI personalizado que creamos.
//+------------------------------------------------------------------+ //| Our class destructor | //+------------------------------------------------------------------+ RSIMidPoint::~RSIMidPoint() { delete my_rsi; } //+------------------------------------------------------------------+
Ahora que hemos definido la clase que encapsula nuestra estrategia del punto medio del RSI, debemos asegurarnos de que la clase se haya construido sin errores. Por lo tanto, primero debemos establecer un nivel de rendimiento de referencia utilizando una versión codificada de forma rígida de la estrategia para poder probar nuestra clase y ver si puede recuperar los mismos niveles de rendimiento que se obtuvieron con la versión codificada de forma rígida.
Si estas dos estrategias son equivalentes, entonces deberían tener los mismos niveles de rentabilidad y producir las mismas estadísticas cuando se prueban durante el mismo período. Así pues, al definir nuestra línea base, lo primero que debemos hacer es enumerar las constantes del sistema que necesitamos. Por ejemplo, el precio sobre el que se debe aplicar el RSI, el período del RSI y el marco temporal. Es necesario fijar todas estas constantes en ambas pruebas para garantizar que las comparaciones sean justas.
//+------------------------------------------------------------------+ //| MSA Test 2 Baseline.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| Define system constants | //+------------------------------------------------------------------+ #define RSI_PRICE PRICE_CLOSE #define RSI_PERIOD 15 #define RSI_TIME_FRAME PERIOD_D1 #define HOLDING_PERIOD 5
A continuación, cargaremos las dependencias que necesitamos. En este caso, solo necesitamos unas tres bibliotecas.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Time\Time.mqh>
Además, también necesitaremos definir algunas variables globales. Observe que podemos dividir nuestras variables globales en dos tipos: tipos personalizados y tipos del sistema. Los tipos personalizados son definidos por el usuario. En todas las instalaciones de MetaTrader 5 están disponibles tipos de datos definidos por el sistema, como por ejemplo los números de doble precisión y los números de coma flotante. Esta es una forma de agrupar nuestras variables globales, simplemente para que nuestro código sea más fácil de mantener.
//+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; //--- System Types double rsi[],ma_close[]; int rsi_handler; int position_timer;
A partir de ahí, pasamos a construir el proceso de inicialización para nuestro asesor experto. Cuando se inicializa nuestro asesor experto, observe que crearemos nosotros mismos el manejador del RSI y, a continuación, también crearemos instancias dinámicas de algunos de los tipos personalizados que necesitamos. Necesitamos estos tipos personalizados, en particular, para controlar el tiempo y obtener información operativa esencial, como el volumen mínimo permitido. Y a partir de ahí, nos aseguraremos de que el manejador del RSI se haya cargado de forma segura.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Our technical indicator rsi_handler = iRSI(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),RSI_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME); //--- Safety checks if(rsi_handler == INVALID_HANDLE) return(false); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit Scope
Cuando ya no utilicemos nuestro asesor experto, liberaremos el indicador que ya no utilizamos y también eliminaremos los objetos dinámicos que hayamos creado.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the indicators and dynamic objects IndicatorRelease(rsi_handler); delete TradeTime; delete TradeInformation; } //--- End of Deinit Scope
Durante la función OnTick, primero comprobaremos si se ha formado una nueva vela diaria, utilizando la biblioteca que hemos escrito para ello. Si se ha formado una nueva vela, actualizaremos nuestros indicadores técnicos y comprobaremos si tenemos alguna posición abierta. Si no tenemos ninguna posición abierta, nos aseguraremos de que el tiempo de la posición se haya reiniciado y, a continuación, comprobaremos si hay alguna señal de trading. De lo contrario, si tenemos operaciones abiertas, comprobaremos si nuestra operación ha llegado a su vencimiento y debe cerrarse. De lo contrario, seguiremos manteniendo la posición.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check if a new daily candle has formed if(TradeTime.NewCandle()) { //--- Update our technical indicators Update(); //--- If we have no open positions if(PositionsTotal() == 0) { //--- Reset the position timer position_timer = 0; //--- Check for a trading signal CheckSignal(); } //--- Otherwise else { //--- The position has reached maturity if(position_timer == HOLDING_PERIOD) Trade.PositionClose(Symbol()); //--- Otherwise keep holding else position_timer++; } } } //--- End of OnTick Scope
También debemos considerar la implementación de algunos de los métodos que hemos comentado. Por ejemplo, el método de actualización simplemente llama a copy buffer, y copia los valores del indicador desde nuestro manejador del RSI en la matriz que especificamos para este propósito. Solo estamos copiando la lectura actual del indicador RSI. No necesitamos las lecturas anteriores.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Call the CopyBuffer method to get updated indicator values CopyBuffer(rsi_handler,0,0,1,rsi); } //--- End of Update Scope
Nuevamente, el método de señal de verificación (el método de señal de verificación simplemente busca las señales de trading que definimos anteriormente) indica que nuestro RSI comprará cuando la lectura esté por debajo de 50; de lo contrario, venderemos si nuestra lectura está por encima de 50.
//+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Buy signals when the RSI is below 50 if(rsi[0] < 50) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Sell signals when the RSI is above 50 else if(rsi[0] > 50) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope
Por último, unificaremos las constantes del sistema que definimos al principio.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef RSI_PRICE #undef RSI_PERIOD #undef RSI_TIME_FRAME #undef HOLDING_PERIOD //+------------------------------------------------------------------+
Para comenzar con nuestra prueba retrospectiva, primero seleccionaremos el período de entrenamiento. Nuestra prueba se desarrollará desde el 1 de enero de 2022 hasta el 1 de mayo de 2025. Tenga en cuenta que realizaremos este ejercicio en el marco temporal diario.

Figura 1: Los días del backtest que seleccionamos para nuestra medición de referencia.
Nuestra simulación nos proporcionará pruebas sólidas si configuramos nuestros retrasos en "Retraso aleatorio". Esto simula la latencia del mercado real, el deslizamiento y otros factores que generan tensión al operar con cuentas reales.

Figura 2: Configuración de la prueba que utilizamos para nuestra prueba retrospectiva.
La curva de capital que se muestra en la Figura 3 a continuación es el objetivo que debe reproducir la clase de estrategia de negociación RSI.

Figura 3: Curva de capital generada por la versión programada de nuestra estrategia de negociación.
Podemos consultar estadísticas detalladas sobre el rendimiento de nuestra estrategia de referencia. Queremos que nuestra clase obtenga las mismas estadísticas al ser evaluada durante el mismo período, para demostrar que el método es sólido.

Figura 4: Estadísticas detalladas del rendimiento de nuestra estrategia de negociación.
A continuación, tenemos que aplicar la misma prueba para determinar si nuestra clase de estrategia RSI es válida. Por lo tanto, la mayoría de los elementos globales definidos previamente, como las constantes del sistema y las variables globales, permanecerán prácticamente sin cambios. Las principales modificaciones que introduciremos en la aplicación incluyen la inicialización de nuestra clase de estrategia durante la fase de inicialización del Asesor Experto.
//+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Strategies\RSIMidPoint.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; TradeInfo *TradeInformation; Time *TradeTime; RSIMidPoint *RSIMid; //--- System Types int position_timer; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),RSI_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),RSI_TIME_FRAME); RSIMid = new RSIMidPoint(Symbol(),RSI_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit ScopeAdemás, debemos actualizar la función OnDeinit para garantizar que el objeto recién creado se elimine correctamente.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the dynamic objects delete TradeTime; delete TradeInformation; delete RSIMid; } //--- End of Deinit ScopePor último, debemos asegurarnos de que el método CheckSignal se invoque correctamente.
//+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Long positions when RSI is below 50 if(RSIMid.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short positions when the RSI is above 50 else if(RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- End of CheckSignal Scope
Ahora procederemos a probar la clase de estrategia RSI que hemos creado, durante el mismo período que utilizamos en nuestra prueba de referencia anterior.

Figura 5: Selección de las fechas de prueba para nuestra versión de clase de la estrategia de negociación RSI.
Ambas pruebas se realizaron bajo condiciones de "Retraso aleatorio" como se especifica en la Figura 2, y produjeron curvas de capital similares bajo la configuración de la prueba retrospectiva.

Figura 6: La curva de capital obtenida por la versión de clase de nuestra estrategia de negociación coincide con la línea base.
Además, ambas pruebas obtuvieron las mismas estadísticas en el análisis detallado. Esto indica que hemos implementado la clase correctamente y sin errores. Por lo tanto, procederemos ahora a fusionar las clases de estrategias de Media Móvil e Índice de Fuerza Relativa.

Figura 7: Estadísticas detalladas que analizan el rendimiento de la versión de clase de nuestra estrategia de negociación en comparación con la línea base.
Ahora que hemos comprobado que nuestras versiones de la estrategia de negociación, tanto la basada en clases como la codificada, producen resultados idénticos, podemos proceder a fusionar la estrategia de cruce de medias móviles con la estrategia basada en el RSI para crear una nueva estrategia combinada. Primero, debemos seleccionar algunos parámetros que mantendremos fijos. Es importante controlar el crecimiento del espacio de parámetros, ya que un espacio de parámetros demasiado grande puede hacer que una optimización significativa sea prácticamente imposible.
//+------------------------------------------------------------------+ //| MSA Test 1.mq5 | //| Gamuchirai Ndawana | //| https://www.mql5.com/en/users/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Ndawana" #property link "https://www.mql5.com/en/users/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ //--- Fix any parameters that can afford to remain fixed #define MA_SHIFT 0 #define MA_TYPE MODE_EMA #define RSI_PRICE PRICE_CLOSE
Además, ahora que estamos ejecutando dos estrategias simultáneamente, debemos definir cómo interactuarán estas estrategias dentro de MQL5. Para gestionar esto, podemos utilizar una enumeración para definir la lógica que puede existir en varios estados relacionados.
En consecuencia, hemos creado un enumerador llamado Modo Estrategia, que tiene tres configuraciones posibles. La primera configuración, representada por 0, es la política de votación. Según esta política, una operación solo se realiza si tanto la estrategia de media móvil como la del RSI coinciden en la misma dirección. En otras palabras, ambas estrategias deben indicar una posición larga para que se abra una operación de compra.
El primer modo alternativo asigna a la estrategia RSI la responsabilidad de abrir posiciones largas, mientras que la estrategia de media móvil gestiona las posiciones cortas. El modo final invierte esta asignación, otorgando la responsabilidad de las operaciones de compra a la estrategia de media móvil y la responsabilidad de las operaciones de venta a la estrategia RSI.
//+------------------------------------------------------------------+ //| User defined enumerator | //+------------------------------------------------------------------+ enum STRATEGY_MODE { MODE_ONE = 0, //Voting Policy MODE_TWO = 1, //RSI Buy & MA Sell MODE_THREE = 2 //MA Sell & RSI Buy };Al estructurar la estrategia de esta manera, hemos reducido el número total de parámetros de entrada a un conjunto manejable de cinco. A partir de ahí, cargamos las dependencias previamente comentadas e inicializamos las variables con las que ya estamos familiarizados.
//+------------------------------------------------------------------+ //| User Inputs | //+------------------------------------------------------------------+ input group "Moving Average Strategy Parameters" input int MA_PERIOD = 10;//Moving Average Period input group "RSI Strategy Parameters" input int RSI_PERIOD = 15;//RSI Period input group "Global Strategy Parameters" input ENUM_TIMEFRAMES STRATEGY_TIME_FRAME = PERIOD_D1;//Strategy Timeframe input int HOLDING_PERIOD = 5;//Position Maturity Period input STRATEGY_MODE USER_MODE = 0;//Operation Mode For Our Strategy //+------------------------------------------------------------------+ //| Dependencies | //+------------------------------------------------------------------+ #include <Trade\Trade.mqh> #include <VolatilityDoctor\Time\Time.mqh> #include <VolatilityDoctor\Trade\TradeInfo.mqh> #include <VolatilityDoctor\Strategies\OpenCloseMACrossover.mqh> #include <VolatilityDoctor\Strategies\RSIMidPoint.mqh> //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ //--- Custom Types CTrade Trade; Time *TradeTime; TradeInfo *TradeInformation; RSIMidPoint *RSIMid; OpenCloseMACrossover *MACross; //--- System Types int position_timer;
Durante la función de inicialización, cargamos las mismas clases que se utilizan a lo largo de todo el proceso de desarrollo. Cuando se finaliza el Asesor Experto, nos aseguramos de que todos los objetos creados dinámicamente se eliminen correctamente.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create dynamic instances of our custom types TradeTime = new Time(Symbol(),STRATEGY_TIME_FRAME); TradeInformation = new TradeInfo(Symbol(),STRATEGY_TIME_FRAME); MACross = new OpenCloseMACrossover(Symbol(),STRATEGY_TIME_FRAME,MA_PERIOD,MA_SHIFT,MA_TYPE); RSIMid = new RSIMidPoint(Symbol(),STRATEGY_TIME_FRAME,RSI_PERIOD,RSI_PRICE); //--- Everything was fine return(INIT_SUCCEEDED); } //--- End of OnInit Scope //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Delete the dynamic objects delete TradeTime; delete TradeInformation; delete MACross; delete RSIMid; } //--- End of Deinit Scope
En la función OnTick, aplicamos la misma lógica que presentamos anteriormente. Dado que esta lógica ya se ha tratado en profundidad, la función OnTick se omite en esta sección de la discusión. La función de actualización se ha modificado ligeramente, ya que ahora llama a dos métodos de actualización independientes, uno para cada estrategia. Por último, el método CheckSignal ha experimentado el cambio más significativo. Ahora, antes de iniciar cualquier operación, debe determinar primero el modo de estrategia actual.
//+------------------------------------------------------------------+ //| Update our technical indicators | //+------------------------------------------------------------------+ void Update(void) { //--- Update the strategy RSIMid.Update(); MACross.Update(); } //--- End of Update Scope //+------------------------------------------------------------------+ //| Check for a trading signal using our cross-over strategy | //+------------------------------------------------------------------+ void CheckSignal(void) { //--- Both Stratetgies Should Cast The Same Vote if(USER_MODE == 0) { //--- Long positions when the close moving average is above the open if(MACross.BuySignal() && RSIMid.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(MACross.SellSignal() && RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- RSI Opens All Long Positions & The Moving Average Opens Short Positions else if(USER_MODE == 1) { if(RSIMid.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(MACross.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } //--- RSI Opens All Short Positions & The Moving Average Opens Long Positions else if(USER_MODE == 2) { if(MACross.BuySignal()) { Trade.Buy(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetAsk(),0,0,""); return; } //--- Otherwise short else if(RSIMid.SellSignal()) { Trade.Sell(TradeInformation.MinVolume(),Symbol(),TradeInformation.GetBid(),0,0,""); return; } } } //--- End of CheckSignal Scope
Finalmente, una vez ejecutada toda la lógica, eliminamos la definición de todas las constantes del sistema para concluir el programa de forma limpia.
//+------------------------------------------------------------------+ //| Undefine system constants | //+------------------------------------------------------------------+ #undef MA_SHIFT #undef RSI_PRICE #undef MA_TYPE //+------------------------------------------------------------------+
Comencemos ahora a buscar la configuración de parámetros adecuada para nuestra aplicación. Seleccione la aplicación que acabamos de crear y, a continuación, establezca el campo "Adelante" en "1/2". Esto permitirá que nuestro optimizador genético utilice la mitad de los datos históricos que tenemos para aprender buenos parámetros, y la otra mitad permanecerá oculta para el optimizador y se utilizará para validar la configuración que el optimizador consideró viable.

Figura 8: Configuración de nuestra prueba directa.
Después de configurar "Retraso aleatorio" como lo hicimos en las 2 pruebas anteriores, recuerde que esta vez también queremos seleccionar un optimizador. Si dispone de mayor capacidad de procesamiento, no dude en probar también las demás optimizaciones que se ofrecen, como la opción "Lenta (repaso completo de parámetros)".

Figura 9: Seleccione la opción "Rápida (algoritmo genético)" en el campo Optimización.
Seleccione la pestaña “Parámetros de entrada” en la parte inferior de la pantalla para que aparezca el panel que controla los parámetros de nuestra estrategia que se van a probar. Seleccione la casilla de verificación junto a cada parámetro y, a continuación, elija una ubicación de inicio y una ubicación final para los valores que se van a buscar. Tenga en cuenta que el parámetro de paso también es crucial y debe tenerse en cuenta. Si elige un tamaño de paso de 1, sus resultados serán muy detallados; sin embargo, como consecuencia, el número total de pasos necesarios podría ser astronómico. En general, se recomiendan intervalos de 2, 4, 5 o incluso 10 para búsquedas especialmente grandes.

Figura 10: Selección de los intervalos de ajuste sobre los que se deben buscar nuestros parámetros.
Cuando inicie la prueba, los resultados de la prueba forward aparecerán como un diagrama de dispersión, donde cada punto del gráfico representa una prueba completa realizada con un conjunto único de parámetros de estrategia.

Figura 11: Podemos visualizar los resultados producidos por el probador de estrategias como un diagrama de dispersión bidimensional, con el beneficio representado en el eje Y.
Nuestra estrategia siguió siendo rentable incluso fuera de la muestra de entrenamiento que nuestro algoritmo genético utilizó para seleccionar estos ajustes. Este es un buen indicador de la posible estabilidad futura si decidimos aplicar el algoritmo a capital real.

Figura 12: La curva de capital generada por nuestra estrategia de negociación presenta una tendencia positiva fuera de la muestra.
Nuestro nuevo sistema es mucho más rentable que la versión anterior. Podemos ver claramente que la combinación "Voting Policy" parece ser la más estable.

Figura 13: El rendimiento de nuestra aplicación mejoró después de incorporarle una estrategia adicional.
Estos fueron los mejores resultados que obtuvimos de los resultados de Forward en nuestra primera discusión. Nuestro nuevo sistema está superando en rendimiento a la versión anterior que teníamos, la cual solo tenía en cuenta una estrategia.

Figura 14: Los mejores resultados que obtuvimos cuando nuestra aplicación solo tenía una estrategia.
Conclusión
Este artículo ha guiado al lector a través de los numerosos beneficios de aplicar los principios de la programación orientada a objetos (POO) en MQL5, combinados con la creatividad humana. Ha demostrado que existen múltiples maneras de integrar diferentes estrategias en un sistema cohesionado. La programación orientada a objetos es una herramienta potente y versátil para este tipo de proyectos creativos, ya que permite un desarrollo estructurado, controlado y predecible.
Partiendo de los conceptos fundamentales presentados aquí, los lectores ahora están capacitados para perfeccionar y optimizar sus propias estrategias de trading. Gracias al uso del Probador de Estrategias integrado en MetaTrader 5, los lectores pueden comenzar a probar y mejorar sus implementaciones.
Este debate también ha puesto de relieve la importancia de minimizar el número de parámetros de entrada en una estrategia. Una estrategia con demasiados parámetros resulta cada vez más difícil de optimizar eficazmente. Si bien es posible delegar parte de la carga computacional a los servicios de MQL5 Cloud Network, mantener la simplicidad en el diseño suele conducir a mejores resultados y estrategias más sólidas.
Al aplicar los principios analizados en este artículo, los lectores deberían disponer ahora de conocimientos prácticos para crear sus propios sistemas de negociación basados en conjuntos de modelos. Esperamos contar con su continua participación en futuras discusiones, donde introduciremos una tercera capa en nuestra estrategia de trading. Este siguiente paso nos ayudará a construir una base sólida para futuros modelos estadísticos.
| Nombre del archivo | Descripción del archivo |
|---|---|
| MSA Test 2 Baseline.mq5 | Versión de prueba codificada de nuestra estrategia de trading RSI. |
| MSA Test 2 Class.mq5 | Versión de prueba de nuestra clase de estrategia RSI. |
| MSA Test 2.ex5 | Versión completa de nuestra estrategia de trading. |
| MSA Test 2.mq5 | Código fuente de la aplicación de conjunto MA y RSI que desarrollamos. |
| RSIMidPoint.mqh | Clase MQL5 para nuestra estrategia de punto medio del RSI. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/18471
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.
Del básico al intermedio: Sobrecarga de operadores (IV)
Simulación de mercado: La unión hace la fuerza (II)
Simulación de mercado: La unión hace la fuerza (III)
Del básico al intermedio: Sobrecarga de operadores (III)
- 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