English Русский 中文 Deutsch 日本語
preview
Automatización de estrategias de trading en MQL5 (Parte 14): Estrategia Trade Layering con técnicas estadísticas basadas en MACD y RSI

Automatización de estrategias de trading en MQL5 (Parte 14): Estrategia Trade Layering con técnicas estadísticas basadas en MACD y RSI

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

Introducción

En nuestro artículo anterior (Parte 13), implementamos un algoritmo de trading del patrón Hombro-Cabeza-Hombro en MetaQuotes Language 5 (MQL5) para automatizar un patrón de reversión clásico para capturar los giros del mercado. En esta Parte 14 pasamos a desarrollar una estrategia de trade layering con el Moving Average Convergence Divergence (MACD) y el Relative Strength Indicator (RSI), mejorada con métodos estadísticos, para escalar posiciones de forma dinámica en mercados con tendencia. Trataremos los siguientes temas:

  1. Arquitectura de la estrategia
  2. Implementación en MQL5
  3. Prueba retrospectiva
  4. Conclusión

Al final de este artículo, tendrás un sólido Asesor Experto diseñado para realizar operaciones con precisión. ¡Empecemos!


Arquitectura de la estrategia

La estrategia de trade layering analizada en este artículo está diseñada para aprovechar tendencias de mercado sostenidas añadiendo posiciones de forma progresiva a medida que el precio se mueve en una dirección favorable, un método conocido habitualmente como "cascading" (añadir posiciones en secuencia conforme el precio avanza). A diferencia de las estrategias tradicionales de entrada única que apuntan a un objetivo fijo, este enfoque aprovecha el impulso mediante la superposición de operaciones adicionales cada vez que se alcanza un umbral de ganancias, lo que permite multiplicar eficazmente las ganancias potenciales y mantener al mismo tiempo un riesgo controlado. En esencia, la estrategia combina dos indicadores técnicos ampliamente conocidos, MACD y RSI, con una superposición estadística para garantizar que las entradas sean oportunas y sólidas, lo que la hace adecuada para mercados con un movimiento direccional claro.

Aprovecharemos las ventajas del MACD y el RSI para establecer una base sólida para las señales de trading, estableciendo reglas claras sobre cuándo iniciar el proceso de estratificación. Nuestro plan consiste en utilizar MACD para confirmar la dirección y la fuerza de la tendencia, asegurándonos de que solo entramos en operaciones cuando el mercado muestra una tendencia constante, mientras que RSI identificará los momentos óptimos de entrada detectando cambios desde niveles de precios extremos. Al integrar estos indicadores, nuestro objetivo es crear un mecanismo de activación fiable que inicie la operación inicial, que luego servirá como punto de partida para nuestra secuencia en cascada, lo que nos permitirá construir posiciones a medida que avanza la tendencia. A continuación se muestra una visualización de la estrategia.

PLAN ESTRATÉGICO

A continuación, mejoraremos esta configuración incorporando métodos estadísticos para aumentar la precisión de nuestra entrada y guiar el proceso de estratificación. Exploraremos cómo aplicar filtros estadísticos, como el análisis del comportamiento histórico del RSI, para validar las señales y garantizar que las operaciones solo se realicen en condiciones estadísticamente significativas. El plan se extiende luego a la definición de las reglas de estratificación, donde describiremos cómo se añade cada nueva operación cuando se alcanzan los objetivos de beneficio, junto con los ajustes de los niveles de riesgo para proteger las ganancias, lo que culmina en una estrategia dinámica que se adapta al impulso del mercado y mantiene al mismo tiempo una ejecución disciplinada. Comenzamos.


Implementación en MQL5

Para crear el programa en MQL5, abra MetaEditor, vaya al Navegador, localice la carpeta Indicadors, haga clic en la pestaña «Nuevo» y siga las instrucciones para crear el archivo. Una vez creado, en el entorno de programación, tendremos que declarar algunas variables globales que utilizaremos a lo largo del programa.

//+------------------------------------------------------------------+
//|                                  MACD-RSI LAYERING STRATEGY.mq5  |
//|      Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader. |
//|                           https://youtube.com/@ForexAlgo-Trader? |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, ALLAN MUNENE MUTIIRIA. #@Forex Algo-Trader"
#property link      "https://youtube.com/@ForexAlgo-Trader?"
#property description "MACD-RSI-based layering strategy with adjustable Risk:Reward and visual levels"
#property version   "1.0"

#include <Trade\Trade.mqh>//---- Includes the Trade.mqh library for trading operations
CTrade obj_Trade;//---- Declares a CTrade object for executing trade operations

int rsiHandle = INVALID_HANDLE;//---- Initializes RSI indicator handle as invalid
double rsiValues[];//---- Declares an array to store RSI values

int handleMACD = INVALID_HANDLE;//---- Initializes MACD indicator handle as invalid
double macdMAIN[];//---- Declares an array to store MACD main line values
double macdSIGNAL[];//---- Declares an array to store MACD signal line values

double takeProfitLevel = 0;//---- Initializes the take profit level variable
double stopLossLevel = 0;//---- Initializes the stop loss level variable
bool buySequenceActive = false;//---- Flag to track if a buy sequence is active
bool sellSequenceActive = false;//---- Flag to track if a sell sequence is active

// Inputs with clear names
input int stopLossPoints = 300;        // Initial Stop Loss (points)
input double tradeVolume = 0.01;       // Trade Volume (lots)
input int minStopLossPoints = 100;     // Minimum SL for cascading orders (points)
input int rsiLookbackPeriod = 14;      // RSI Lookback Period
input double rsiOverboughtLevel = 70.0; // RSI Overbought Threshold
input double rsiOversoldLevel = 30.0;   // RSI Oversold Threshold
input bool useStatisticalFilter = true; // Enable Statistical Filter
input int statAnalysisPeriod = 20;      // Statistical Analysis Period (bars)
input double statDeviationFactor = 1.0; // Statistical Deviation Factor
input double riskRewardRatio = 1.0;     // Risk:Reward Ratio

// Object names for visualization
string takeProfitLineName = "TakeProfitLine";//---- Name of the take profit line object for chart visualization
string takeProfitTextName = "TakeProfitText";//---- Name of the take profit text object for chart visualization


Comenzamos por establecer el marco esencial para nuestra estrategia multicapa de operaciones, empezando por incluir la biblioteca «Trade.mqh» y declarar un objeto «CTrade» denominado «obj_Trade» para gestionar todas las operaciones bursátiles. Inicializamos los manejadores de indicadores clave: «rsiHandle» para RSI y «handleMACD» para MACD, ambos establecidos inicialmente en INVALID_HANDLE, junto con matrices como «rsiValues», «macdMAIN» y «macdSIGNAL» para almacenar sus respectivos datos. Para realizar un seguimiento del estado de la estrategia, definimos variables como «takeProfitLevel» y «stopLossLevel» para los niveles de negociación, y los indicadores booleanos «buySequenceActive» y «sellSequenceActive» para supervisar si se está llevando a cabo una secuencia de compra o venta, lo que garantiza que el sistema sepa cuándo realizar operaciones en cascada.

A continuación, establecemos entradas configurables por el usuario para que la estrategia sea adaptable, incluyendo «stopLossPoints» para la distancia inicial de stop loss, «tradeVolume» para el tamaño del lote y «minStopLossPoints» para stops más ajustados en operaciones en cascada. Para los indicadores, establecemos «rsiLookbackPeriod» para definir la ventana de cálculo del RSI, «rsiOverboughtLevel» y «rsiOversoldLevel» como umbrales de entrada, y «riskRewardRatio» para controlar los objetivos de beneficio en relación con el riesgo. Para incorporar métodos estadísticos, introducimos «useStatisticalFilter» como un conmutador, junto con «statAnalysisPeriod» y «statDeviationFactor», lo que nos permitirá refinar las señales basándonos en el comportamiento estadístico del RSI, garantizando que las operaciones se ajusten a las desviaciones significativas del mercado.

Por último, nos preparamos para la retroalimentación visual definiendo «takeProfitLineName» y «takeProfitTextName» como nombres de objetos para las líneas y etiquetas de take profit basadas en gráficos, lo que mejora la capacidad del operador para supervisar los niveles en tiempo real. Una vez compilado el programa, vemos el siguiente resultado.

ENTRADAS DEL USUARIO

A continuación, pasamos al controlador de eventos de inicialización (OnInit), donde gestionamos las propiedades de inicialización.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){//---- Expert advisor initialization function
   rsiHandle = iRSI(_Symbol, _Period, rsiLookbackPeriod, PRICE_CLOSE);//---- Creates RSI indicator handle with specified parameters
   handleMACD = iMACD(_Symbol,_Period,12,26,9,PRICE_CLOSE);//---- Creates MACD indicator handle with standard 12,26,9 settings
   
   if(rsiHandle == INVALID_HANDLE){//---- Checks if RSI handle creation failed
      Print("UNABLE TO LOAD RSI, REVERTING NOW");//---- Prints error message if RSI failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   if(handleMACD == INVALID_HANDLE){//---- Checks if MACD handle creation failed
      Print("UNABLE TO LOAD MACD, REVERTING NOW");//---- Prints error message if MACD failed to load
      return(INIT_FAILED);//---- Returns initialization failure code
   }
   
   ArraySetAsSeries(rsiValues, true);//---- Sets RSI values array as a time series (latest data at index 0)
   
   ArraySetAsSeries(macdMAIN,true);//---- Sets MACD main line array as a time series
   ArraySetAsSeries(macdSIGNAL,true);//---- Sets MACD signal line array as a time series

   return(INIT_SUCCEEDED);//---- Returns successful initialization code
}

Aquí comenzamos a implementar nuestra estrategia de capas en la función OnInit, el punto de partida en el que inicializamos los componentes clave antes de que el EA interactúe con los datos del mercado. Configuramos el indicador RSI utilizando la función iRSI para asignar un identificador a «rsiHandle», pasando _Symbol para el gráfico actual, _Period para el intervalo de tiempo, «rsiLookbackPeriod» como ventana de observación y «PRICE_CLOSE» para utilizar los precios de cierre, seguido de la configuración del MACD con la función iMACD que almacena su identificador en «handleMACD» y usando los períodos estándar 12, 26 y 9 sobre «PRICE_CLOSE» para el análisis de tendencias.

Para garantizar la solidez, comprobamos si «rsiHandle» es igual a INVALID_HANDLE y, si es así, utilizamos la función Print para registrar «UNABLE TO LOAD RSI, REVERTING NOW» y devolvemos INIT_FAILED, repitiendo esto para «handleMACD» con «UNABLE TO LOAD MACD, REVERTING NOW» para detenerlo en caso de fallo. Una vez confirmado, configuramos «rsiValues», «macdMAIN» y «macdSIGNAL» como matrices de series temporales utilizando la función ArraySetAsSeries con true, alineando los datos más recientes en el índice cero, y luego devolvemos INIT_SUCCEEDED para indicar que la configuración se ha realizado correctamente y que está lista para operar. A continuación, podemos ir al controlador de eventos OnTick y definir nuestra lógica de negociación real.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){//---- Function called on each price tick
   double askPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits);//---- Gets and normalizes current ask price
   double bidPrice = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits);//---- Gets and normalizes current bid price
   
   if(CopyBuffer(rsiHandle, 0, 1, 3, rsiValues) < 3){//---- Copies 3 RSI values into array, checks if successful
      Print("INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK");//---- Prints error if insufficient RSI data
      return;//---- Exits function if data copy fails
   }
   
   if (!CopyBuffer(handleMACD,MAIN_LINE,0,3,macdMAIN))return;//---- Copies 3 MACD main line values, exits if fails
   if (!CopyBuffer(handleMACD,SIGNAL_LINE,0,3,macdSIGNAL))return;//---- Copies 3 MACD signal line values, exits if fails
}

Aquí, avanzamos en nuestra estrategia de capas mediante la implementación de la función OnTick, que se activa con cada actualización de precios para impulsar la toma de decisiones en tiempo real en el Asesor Experto. Comenzamos capturando los precios actuales del mercado, utilizando la función NormalizeDouble para establecer «askPrice» con SymbolInfoDouble para _Symbol y «SYMBOL_ASK», ajustado a la precisión «_Digits», y «bidPrice» con «SYMBOL_BID», lo que garantiza datos de precios precisos para los cálculos comerciales. Este paso sienta las bases para supervisar los movimientos de precios que activarán nuestra lógica de estratificación basada en las últimas condiciones del mercado.

A continuación, recopilamos los datos de los indicadores para analizar señales, comenzando con el RSI mediante la función CopyBuffer para cargar tres valores en «rsiValues» desde «rsiHandle», comenzando en el índice 1 con el búfer 0 y comprobando si se copian menos de 3 valores; si es así, utilizamos la función «Print» para registrar el mensaje «INSUFFICIENT RSI DATA FOR ANALYSIS, SKIPPING TICK» y volvemos para salir del tick, evitando tomar decisiones con datos incompletos. A continuación, aplicamos el mismo enfoque para MACD, utilizando «CopyBuffer» para rellenar «macdMAIN» con tres valores principales de «handleMACD» en MAIN_LINE, y «macdSIGNAL» con valores de línea de señal en SIGNAL_LINE, volviendo inmediatamente si alguno de ellos falla, asegurándonos de que solo procedemos con conjuntos completos de datos RSI y MACD. Sin embargo, tendremos que recopilar datos estadísticos e incorporarlos aquí también. Veamos las funciones.

//+------------------------------------------------------------------+
//| Calculate RSI Average                                            |
//+------------------------------------------------------------------+
double CalculateRSIAverage(int bars){//---- Function to calculate RSI average
   double sum = 0;//---- Initializes sum variable
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      sum += buffer[i];//---- Adds each RSI value to sum
   }
   return sum / bars;//---- Returns average RSI value
}

//+------------------------------------------------------------------+
//| Calculate RSI STDDev                                             |
//+------------------------------------------------------------------+
double CalculateRSIStandardDeviation(int bars){//---- Function to calculate RSI standard deviation
   double average = CalculateRSIAverage(bars);//---- Calculates RSI average
   double sumSquaredDiff = 0;//---- Initializes sum of squared differences
   double buffer[];//---- Declares buffer array for RSI values
   ArraySetAsSeries(buffer, true);//---- Sets buffer as time series
   if(CopyBuffer(rsiHandle, 0, 0, bars, buffer) < bars) return 0;//---- Copies RSI values, returns 0 if fails
   
   for(int i = 0; i < bars; i++){//---- Loops through specified number of bars
      double diff = buffer[i] - average;//---- Calculates difference from average
      sumSquaredDiff += diff * diff;//---- Adds squared difference to sum
   }
   return MathSqrt(sumSquaredDiff / bars);//---- Returns standard deviation
}

Implementamos dos funciones clave, «CalculateRSIAverage» y «CalculateRSIStandardDeviation», para introducir el análisis estadístico de los datos del RSI, lo que refuerza la precisión de las señales. En «CalculateRSIAverage», definimos una función que toma «bars» como entrada, inicializando «sum» a 0 y declarando una matriz «buffer», que configuramos como una serie temporal utilizando la función ArraySetAsSeries con true, asegurándonos de que los últimos valores RSI se alineen en el índice cero. A continuación, utilizamos la función CopyBuffer para cargar el número «bars» de valores RSI desde «rsiHandle» en «buffer», devolviendo 0 si se copian menos de «bars», y recorremos la matriz para añadir cada «buffer[i]» a «sum», devolviendo finalmente «sum/bars» como el promedio RSI para su uso en el filtrado estadístico.

A continuación, en «CalculateRSIStandardDeviation», partimos de esta base para calcular la desviación estándar del RSI durante el mismo periodo de «bars», comenzando por llamar a «CalculateRSIAverage» para almacenar el resultado en «average» y estableciendo «sumSquaredDiff» en 0 para las diferencias al cuadrado. Volvemos a declarar una matriz «buffer», la configuramos como una serie temporal con ArraySetAsSeries y utilizamos la función «CopyBuffer» para obtener los valores RSI de «rsiHandle», devolviendo 0 si la copia falla, lo que garantiza la integridad de los datos. Recorremos «buffer», calculando cada «diff» como «buffer[i] - average», sumando «diff * diff» a «sumSquaredDiff» y devolviendo la desviación estándar utilizando la función MathSqrt en «sumSquaredDiff/bars», lo que proporciona una medida estadística para refinar nuestras decisiones. Ahora podemos utilizar estos datos para realizar análisis estadísticos, pero tendremos que ejecutarlos una vez por barra, para evitar ambigüedades. Así que definamos una función para eso.

//+------------------------------------------------------------------+
//| Is New Bar                                                       |
//+------------------------------------------------------------------+
bool IsNewBar(){//---- Function to detect a new bar
   static int previousBarCount = 0;//---- Stores the previous bar count
   int currentBarCount = iBars(_Symbol, _Period);//---- Gets current number of bars
   if(previousBarCount == currentBarCount) return false;//---- Returns false if no new bar
   previousBarCount = currentBarCount;//---- Updates previous bar count
   return true;//---- Returns true if new bar detected
}

Aquí añadimos la función «IsNewBar» a nuestra estrategia para detectar nuevas barras, utilizando un «previousBarCount» estático establecido en 0 para rastrear el recuento de la última barra y la función iBars para obtener «currentBarCount» para _Symbol y _Period, devolviendo falso si no ha cambiado o verdadero después de actualizar «previousBarCount» cuando se forma una nueva barra. Con esta función, ahora podemos definir las reglas de negociación.

// Calculate statistical measures if enabled
double rsiAverage = useStatisticalFilter ? CalculateRSIAverage(statAnalysisPeriod) : 0;//---- Calculates RSI average if filter enabled
double rsiStdDeviation = useStatisticalFilter ? CalculateRSIStandardDeviation(statAnalysisPeriod) : 0;//---- Calculates RSI std dev if filter enabled

Implementamos mejoras estadísticas calculando «rsiAverage» utilizando la función «CalculateRSIAverage» con «statAnalysisPeriod» si «useStatisticalFilter» es verdadero; de lo contrario, lo establecemos en 0 y, de manera similar, calculamos «rsiStdDeviation» con la función «CalculateRSIStandardDeviation» o lo establecemos en 0 por defecto, lo que permite un filtrado de señales más preciso cuando se activa. A continuación, podemos utilizar los resultados para definir las condiciones comerciales. Comenzaremos con las condiciones de compra.

if(PositionsTotal() == 0 && IsNewBar()){//---- Checks for no positions and new bar
   // Buy Signal
   bool buyCondition = rsiValues[1] <= rsiOversoldLevel && rsiValues[0] > rsiOversoldLevel;//---- Checks RSI crossing above oversold
   if(useStatisticalFilter){//---- Applies statistical filter if enabled
      buyCondition = buyCondition && (rsiValues[0] < (rsiAverage - statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
   }
   
   buyCondition = macdMAIN[0] < 0 && macdSIGNAL[0] < 0;//---- Confirms MACD below zero for buy signal
}

Definimos la lógica de la señal de compra comprobando si PositionsTotal es 0 y utilizando la función «IsNewBar» para confirmar una nueva barra, asegurándonos de que las operaciones solo se activen en las aperturas de barras sin posiciones abiertas. Establecemos «buyCondition» en verdadero si «rsiValues[1]» está por debajo de «rsiOversoldLevel» y «rsiValues[0]» lo supera, y si «useStatisticalFilter» está habilitado, lo refinamos aún más exigiendo que «rsiValues[0]» sea menor que «rsiAverage» menos «statDeviationFactor» multiplicado por «rsiStdDeviation», lo que añade precisión estadística. Por último, confirmamos la señal con «macdMAIN[0]» y «macdSIGNAL[0]» ambos por debajo de cero, alineando la compra con una zona bajista del MACD para validar la tendencia. Si se cumplen las condiciones, procedemos a abrir las posiciones, inicializar las variables de seguimiento y dibujar los niveles confirmados en el gráfico, concretamente el nivel de take-profit. Por lo tanto, necesitaremos una función personalizada para dibujar los niveles.

//+------------------------------------------------------------------+
//| Draw TrendLine                                                   |
//+------------------------------------------------------------------+
void DrawTradeLevelLine(double price, bool isBuy){//---- Function to draw take profit line on chart
   // Delete existing objects first
   DeleteTradeLevelObjects();//---- Removes existing trade level objects
   
   // Create horizontal line
   ObjectCreate(0, takeProfitLineName, OBJ_HLINE, 0, 0, price);//---- Creates a horizontal line at specified price
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_COLOR, clrBlue);//---- Sets line color to blue
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_WIDTH, 2);//---- Sets line width to 2
   ObjectSetInteger(0, takeProfitLineName, OBJPROP_STYLE, STYLE_SOLID);//---- Sets line style to solid
   
   // Create text, above for buy, below for sell with increased spacing
   datetime currentTime = TimeCurrent();//---- Gets current time
   double textOffset = 30.0 * _Point;//---- Sets text offset distance from line
   double textPrice = isBuy ? price + textOffset : price - textOffset;//---- Calculates text position based on buy/sell
   
   ObjectCreate(0, takeProfitTextName, OBJ_TEXT, 0, currentTime + PeriodSeconds(_Period) * 5, textPrice);//---- Creates text object
   ObjectSetString(0, takeProfitTextName, OBJPROP_TEXT, DoubleToString(price, _Digits));//---- Sets text to price value
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_COLOR, clrBlue);//---- Sets text color to blue
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_FONTSIZE, 10);//---- Sets text font size to 10
   ObjectSetInteger(0, takeProfitTextName, OBJPROP_ANCHOR, isBuy ? ANCHOR_BOTTOM : ANCHOR_TOP);//---- Sets text anchor based on buy/sell
}

Aquí, implementamos la función «DrawTradeLevelLine» para visualizar los niveles de take profit, comenzando por llamar a «DeleteTradeLevelObjects» para borrar los objetos existentes y, a continuación, utilizando la función ObjectCreate para dibujar una línea horizontal en «price» con «takeProfitLineName» como OBJ_HLINE, con el estilo ObjectSetInteger para azul «clrBlue», ancho 2 y «STYLE_SOLID». Añadimos una etiqueta de texto obteniendo «currentTime» con «TimeCurrent», estableciendo «textOffset» en «30.0 * _Point» y calculando «textPrice» por encima o por debajo de «price» en función de «isBuy», creándola con «ObjectCreate» como «takeProfitTextName» en «OBJ_TEXT». Configuramos el texto utilizando ObjectSetString para mostrar «price» a través de DoubleToString con «_Digits», y «ObjectSetInteger» para el azul «clrBlue», tamaño 10 y «ANCHOR_BOTTOM» o «ANCHOR_TOP» dependiendo de «isBuy», lo que mejora la legibilidad del gráfico. Ahora podemos utilizar la función para visualizar los niveles objetivo.

if(buyCondition){//---- Executes if buy conditions are met
   Print("BUY SIGNAL - RSI: ", rsiValues[0],//---- Prints buy signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = askPrice - stopLossPoints * _Point;//---- Calculates stop loss level for buy
   takeProfitLevel = askPrice + (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for buy
   obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Signal Position");//---- Places buy order
   buySequenceActive = true;//---- Activates buy sequence flag
   DrawTradeLevelLine(takeProfitLevel, true);//---- Draws take profit line for buy
}

Aquí, gestionamos la ejecución de la operación de compra cuando «buyCondition» es verdadero, utilizando la función Print para registrar «BUY SIGNAL - RSI: » con «rsiValues[0]», añadiendo «rsiAverage» y «rsiStdDeviation» mediante la función DoubleToString si «useStatisticalFilter» está habilitado, para obtener información detallada. Calculamos «stopLossLevel» como «askPrice» menos «stopLossPoints * _Point» y «takeProfitLevel» como «askPrice» más «stopLossPoints * riskRewardRatio * _Point», y luego utilizamos el método «obj_Trade. Buy» para colocar una orden de compra con «tradeVolume», _Symbol, «askPrice», «stopLossLevel» y «takeProfitLevel», etiquetada como «Signal Position». Por último, establecemos «buySequenceActive» en verdadero y llamamos a «DrawTradeLevelLine» con «takeProfitLevel» y verdadero para visualizar la línea de take profit de la compra. Al ejecutar el programa, obtenemos el siguiente resultado.

POSICIÓN DE COMPRA CONFIRMADA

En la imagen, podemos ver que se cumplen todas las condiciones de la señal de compra, y marcamos automáticamente el siguiente nivel en el gráfico. Por lo tanto, podemos seguir haciendo lo mismo con una señal de venta.

// Sell Signal
bool sellCondition = rsiValues[1] >= rsiOverboughtLevel && rsiValues[0] < rsiOverboughtLevel;//---- Checks RSI crossing below overbought
if(useStatisticalFilter){//---- Applies statistical filter if enabled
   sellCondition = sellCondition && (rsiValues[0] > (rsiAverage + statDeviationFactor * rsiStdDeviation));//---- Adds statistical condition
}

sellCondition = macdMAIN[0] > 0 && macdSIGNAL[0] > 0;//---- Confirms MACD above zero for sell signal

if(sellCondition){//---- Executes if sell conditions are met
   Print("SELL SIGNAL - RSI: ", rsiValues[0],//---- Prints sell signal details
         useStatisticalFilter ? " Avg: " + DoubleToString(rsiAverage, 2) + " StdDev: " + DoubleToString(rsiStdDeviation, 2) : "");
   stopLossLevel = bidPrice + stopLossPoints * _Point;//---- Calculates stop loss level for sell
   takeProfitLevel = bidPrice - (stopLossPoints * riskRewardRatio) * _Point;//---- Calculates take profit level for sell
   obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Signal Position");//---- Places sell order
   sellSequenceActive = true;//---- Activates sell sequence flag
   DrawTradeLevelLine(takeProfitLevel, false);//---- Draws take profit line for sell
}

Aquí aplicamos la lógica opuesta a la operación de compra, estableciendo «sellCondition» si «rsiValues[1]» supera «rsiOverboughtLevel» y «rsiValues[0]» cae por debajo, añadiendo «useStatisticalFilter» para comprobar «rsiValues[0]» por encima de «rsiAverage + statDeviationFactor * rsiStdDeviation», y confirmando con «macdMAIN[0]» y «macdSIGNAL[0]» por encima de cero. Si es cierto, utilizamos «Print» para «SELL SIGNAL - RSI: » con «rsiValues[0]» y estadísticas a través de DoubleToString, establecemos «stopLossLevel» como «bidPrice + stopLossPoints * _Point» y «takeProfitLevel» como «bidPrice - (stopLossPoints * riskRewardRatio) * _Point», luego llamamos a «obj_Trade.Sell» y «DrawTradeLevelLine» con falso, activando «sellSequenceActive». Ahora que las posiciones están abiertas, necesitamos encadenar las posiciones ganadoras, siguiendo la tendencia y modificando las posiciones. Aquí se muestra la función para modificar las operaciones.

//+------------------------------------------------------------------+
//| Modify Trades                                                    |
//+------------------------------------------------------------------+
void ModifyTrades(ENUM_POSITION_TYPE positionType, double newStopLoss){//---- Function to modify open trades
   for(int i = 0; i < PositionsTotal(); i++){//---- Loops through all open positions
      ulong ticket = PositionGetTicket(i);//---- Gets ticket number of position
      if(ticket > 0 && PositionSelectByTicket(ticket)){//---- Checks if ticket is valid and selectable
         ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);//---- Gets position type
         if(type == positionType){//---- Checks if position matches specified type
            obj_Trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP));//---- Modifies position with new stop loss
         }
      }
   }
}

Aquí, implementamos la función «ModifyTrades» para actualizar las operaciones abiertas, tomando «positionType» y «newStopLoss» como entradas, y luego utilizando la función PositionsTotal para recorrer todas las posiciones con un bucle «for» desde 0 hasta «i». Para cada uno, utilizamos la función PositionGetTicket para obtener el número de «ticket», comprobamos si es válido y seleccionable con PositionSelectByTicket, y utilizamos «PositionGetInteger» para obtener el «tipo» como ENUM_POSITION_TYPE, comparándolo con «positionType». Si coincide, utilizamos «obj_Trade.PositionModify» para ajustar el stop loss de la operación a «newStopLoss», manteniendo al mismo tiempo el take profit de PositionGetDouble con «POSITION_TP», lo que garantiza una gestión precisa de la operación. A continuación la función para encadenar las operaciones.

else {//---- Handles cascading logic when positions exist
   // Cascading Buy Logic
   if(buySequenceActive && askPrice >= takeProfitLevel){//---- Checks if buy sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit + (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = askPrice - minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Buy(tradeVolume, _Symbol, askPrice, stopLossLevel, 0,"Cascade Position");//---- Places new buy order
      ModifyTrades(POSITION_TYPE_BUY, stopLossLevel);//---- Modifies existing buy trades with new stop loss
      Print("CASCADING BUY - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading buy details
      DrawTradeLevelLine(takeProfitLevel, true);//---- Updates take profit line for buy
   }
   // Cascading Sell Logic
   else if(sellSequenceActive && bidPrice <= takeProfitLevel){//---- Checks if sell sequence active and price hit take profit
      double previousTakeProfit = takeProfitLevel;//---- Stores previous take profit level
      takeProfitLevel = previousTakeProfit - (stopLossPoints * riskRewardRatio) * _Point;//---- Sets new take profit level
      stopLossLevel = bidPrice + minStopLossPoints * _Point;//---- Sets new stop loss level
      obj_Trade.Sell(tradeVolume, _Symbol, bidPrice, stopLossLevel, 0,"Cascade Position");//---- Places new sell order
      ModifyTrades(POSITION_TYPE_SELL, stopLossLevel);//---- Modifies existing sell trades with new stop loss
      Print("CASCADING SELL - New TP: ", takeProfitLevel, " New SL: ", stopLossLevel);//---- Prints cascading sell details
      DrawTradeLevelLine(takeProfitLevel, false);//---- Updates take profit line for sell
   }
}

Aquí, gestionamos la lógica en cascada cuando existen posiciones, comprobando si «buySequenceActive» es verdadero y «askPrice» alcanza «takeProfitLevel», luego almacenamos «previousTakeProfit», establecemos un nuevo «takeProfitLevel» con «stopLossPoints * riskRewardRatio * _Point» añadido, y «stopLossLevel» como «askPrice - minStopLossPoints * _Point», utilizando «obj_Trade.Buy» para una nueva orden y «ModifyTrades» para actualizar los stops POSITION_TYPE_BUY, con «Print» registrando los detalles de «CASCADING BUY» y «DrawTradeLevelLine» actualizando la línea de compra.

Para las ventas, si «sellSequenceActive» es verdadero y «bidPrice» alcanza «takeProfitLevel», reflejamos esto restando de «previousTakeProfit» para el nuevo «takeProfitLevel», estableciendo «stopLossLevel» como «bidPrice + minStopLossPoints * _Point», llamando a «obj_Trade. Sell» y «ModifyTrades» para POSITION_TYPE_SELL, registrando con «Print» y actualizando la línea de venta con «DrawTradeLevelLine». Al ejecutar el sistema, obtenemos el siguiente resultado.

POSICIONES EN CASCADA

A partir de la imagen, podemos confirmar la cascada de posiciones y modificar el stop loss para todas las posiciones. Ahora lo que tenemos que hacer es asegurarnos de limpiar los objetos que añadimos cuando no necesitamos el sistema. Podemos lograrlo mediante el controlador de eventos OnDeinit, pero primero necesitaremos una función de limpieza.

//+------------------------------------------------------------------+
//| Delete Level Objects                                             |
//+------------------------------------------------------------------+
void DeleteTradeLevelObjects(){//---- Function to delete trade level objects
   ObjectDelete(0, takeProfitLineName);//---- Deletes take profit line object
   ObjectDelete(0, takeProfitTextName);//---- Deletes take profit text object
}

Implementamos la función «DeleteTradeLevelObjects» para limpiar los elementos visuales del gráfico, utilizando la función ObjectDelete para eliminar el objeto de línea «takeProfitLineName» y el objeto de texto «takeProfitTextName», asegurándonos de que los antiguos niveles de take profit se borren antes de dibujar los nuevos. Ahora llamamos a esta función en el controlador de eventos OnDeinit.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){//---- Expert advisor deinitialization function
   DeleteTradeLevelObjects();//---- Removes trade level visualization objects from chart
}

Aquí simplemente llamamos a la función para eliminar los objetos del gráfico, asegurándonos de limpiar el gráfico una vez que eliminemos el programa, logrando así nuestro objetivo. Lo que queda es realizar pruebas retrospectivas del programa, lo cual se trata en la siguiente sección.


Prueba retrospectiva

Tras realizar exhaustivas pruebas retrospectivas, hemos obtenido los siguientes resultados.

Gráfico de la prueba retrospectiva:

GRÁFICO

Informe de la prueba retrospectiva:

INFORME


Conclusión

En conclusión, hemos elaborado con éxito una estrategia de capas comerciales en MQL5, combinando MACD y RSI con métodos estadísticos para automatizar el escalado dinámico de posiciones en mercados con tendencia. El programa cuenta con una sólida detección de señales, lógica de operaciones en cascada y niveles visuales de toma de ganancias, adaptándose con precisión a los cambios de impulso. Puede utilizar esta base para mejorarla aún más con ajustes como optimizar «rsiLookbackPeriod» o ajustar «riskRewardRatio» para obtener un mejor rendimiento.

Descargo de responsabilidad: Este artículo tiene fines exclusivamente educativos. El trading conlleva un riesgo financiero significativo y el comportamiento del mercado puede ser volátil. Es fundamental realizar pruebas retrospectivas exhaustivas y gestionar los riesgos antes de utilizarlo en modo real.

Con esta ilustración, puedes perfeccionar tus habilidades de automatización y refinar la estrategia. Experimenta y optimiza. ¡Feliz trading!

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

Trading con algoritmos: La IA y su camino hacia las alturas doradas Trading con algoritmos: La IA y su camino hacia las alturas doradas
En este artículo veremos un método para crear estrategias comerciales para el oro utilizando el aprendizaje automático. Considerando el enfoque propuesto para el análisis y la previsión de series temporales desde distintos ángulos, podemos determinar sus ventajas e inconvenientes en comparación con otras formas de crear sistemas comerciales basados únicamente en el análisis y la previsión de series temporales financieras.
Análisis angular de los movimientos de precios: un modelo híbrido para predecir los mercados financieros Análisis angular de los movimientos de precios: un modelo híbrido para predecir los mercados financieros
¿Qué es el análisis angular de los mercados financieros? ¿Cómo usar los ángulos de precios y el aprendizaje automático para predecir con una exactitud de 67? ¿Cómo combinar un modelo de regresión y clasificación con características angulares y obtener un algoritmo que funcione? ¿Qué tiene que ver Gann con esto? ¿Por qué los ángulos de movimiento de los precios son una buena señal para el aprendizaje automático?
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.
Determinamos la sobrecompra y la sobreventa usando la teoría del caos Determinamos la sobrecompra y la sobreventa usando la teoría del caos
Hoy determinaremos la sobrecompra y la sobreventa del mercado mediante la teoría del caos; usando la integración de los principios de la teoría del caos, la geometría fractal y las redes neuronales, pronosticaremos los mercados financieros. El presente artículo demostrará la aplicación del exponente de Lyapunov como medida de la aleatoriedad del mercado y la adaptación dinámica de las señales comerciales. La metodología incluye un algoritmo de generación de ruido fractal, activación por tangente hiperbólica y optimización con impulso.