Automatización de estrategias de trading en MQL5 (Parte 14): Estrategia Trade Layering con técnicas estadísticas basadas en MACD y RSI
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:
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.

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.

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.

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.

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:

Informe de la prueba retrospectiva:

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
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.
Trading con algoritmos: La IA y su camino hacia las alturas doradas
Análisis angular de los movimientos de precios: un modelo híbrido para predecir los mercados financieros
Particularidades del trabajo con números del tipo double en MQL4
Determinamos la sobrecompra y la sobreventa usando la teoría del caos
- 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