Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 13): Herramienta RSI Sentinel
Contenido
- Introducción
- Descripción general de la estrategia
- Código MQL5
- Desglose del código
- Pruebas y resultados
- Conclusión
Introducción
La divergencia es un concepto del análisis técnico donde el movimiento de un indicador, como el impulso o los osciladores, se desvía del movimiento del precio. Básicamente, cuando el precio forma nuevos máximos o mínimos que no se reflejan en el indicador, puede señalar un debilitamiento de la tendencia y potencialmente presagiar una reversión o un cambio en el impulso. La divergencia del RSI es una forma sencilla de detectar posibles reversiones del mercado. Cuando el precio se mueve en una dirección mientras el RSI va en otra, podría indicar un cambio de tendencia. Sin embargo, escanear sus gráficos manualmente para buscar estas señales puede ser lento y propenso a errores. Ahí es donde entra la automatización.
En este artículo, crearemos un asesor experto MQL5 que detecta automáticamente señales de divergencia RSI. El EA marcará estas señales con flechas claras en su gráfico y proporcionará un breve resumen para que pueda ver rápidamente lo que está sucediendo. Ya sea que sea un principiante o un comerciante experimentado, esta herramienta lo ayuda a detectar oportunidades comerciales que puede validar antes de ejecutar operaciones, todo sin gastar horas en análisis manual. Profundicemos y veamos cómo este EA de divergencia RSI puede simplificar su proceso de trading.
Descripción general de la estrategia
Entendiendo la divergencia del RSI
La divergencia RSI ocurre cuando el índice de fuerza relativa (Relative Strength Index, RSI) se mueve en una dirección diferente al precio del activo, lo que indica un posible cambio en el impulso del precio. Este contraste entre el RSI y la acción del precio es un indicador clave que los traders utilizan para anticipar reversiones del mercado o continuaciones de tendencias. Normalmente, el RSI sigue el impulso del precio, confirmando así las tendencias predominantes. Sin embargo, cuando aparece la divergencia, revela una discrepancia que a menudo precede a un movimiento de precios significativo. Reconocer estas señales de forma temprana puede ser crucial para cronometrar entradas y salidas del mercado.
En el contexto de la divergencia del RSI, hay dos tipos principales
1. Divergencia regular del RSI
La divergencia regular del RSI generalmente se considera una señal de reversión. Indica que la tendencia actual está perdiendo fuerza y podría estar a punto de revertirse.
- Divergencia alcista regular del RSI
Se produce cuando el precio forma un mínimo más bajo mientras que el RSI forma un mínimo más alto. Esto sugiere que, aunque el precio está bajando, el impulso está empezando a cambiar hacia arriba, lo que indica una posible reversión a una tendencia alcista.

Figura 1. Divergencia alcista
- Divergencia bajista regular del RSI
Ocurre cuando el precio forma un máximo más alto mientras que el RSI forma un máximo más bajo. A pesar del aumento del precio, el debilitamiento del impulso (como lo muestra el RSI) indica que una recesión podría estar en el horizonte.

Figura 2. Divergencia bajista
2. Divergencia oculta del RSI
La divergencia oculta del RSI se interpreta como una señal de continuación de la tendencia, en lugar de una reversión inminente. Confirma que la tendencia actual todavía tiene fuerza, incluso cuando el RSI y el precio divergen temporalmente.
- Divergencia alcista oculta del RSI: en una tendencia alcista, si el precio forma un mínimo más alto mientras que el RSI forma un mínimo más bajo, indica que la corrección es solo temporal y que es probable que la tendencia alcista continúe.

Figura 3. Divergencia alcista oculta
- Divergencia bajista oculta del RSI: en una tendencia bajista, cuando el precio forma un máximo más bajo mientras que el RSI forma un máximo más alto, confirma la fortaleza de la tendencia bajista y sugiere que es probable que el movimiento descendente persista.

Figura 4. Divergencia bajista oculta
A continuación se muestra una tabla resumen que resume las diferencias clave entre los tipos de divergencia RSI:
| Tipo de divergencia del RSI | Acción del precio | Acción RSI | Tipo de señal | Expectativa |
|---|---|---|---|---|
| Alcista regular | Low Low(LL) | Higher Low(HL) | Reversión al alza | De tendencia bajista a tendencia alcista |
| Bajista regular | Higher High(HH) | Lower Higher(LH) | Reversión a la baja | De tendencia alcista a tendencia bajista |
| Alcista oculta | High Low(HL) | Low Low(LL) | Continuación hacia arriba | La tendencia alcista continúa |
| Bajista oculto | Lower High (LH) | High High(HH) | Continuación hacia abajo | La tendencia bajista continúa |
En resumen, este EA escanea continuamente los datos de precios y RSI durante un período retrospectivo definido para detectar discrepancias entre sus movimientos, lo que llamamos divergencia RSI.
A continuación se muestra lo que hace:
1. Recopilación y preparación de datos
El EA recopila valores RSI junto con los datos de precios correspondientes (mínimos, máximos, cierres y hora) de barras recientes. Esto garantiza que el análisis siempre se base en la información más reciente y completa.2. Identificación de puntos de inflexión
Luego determina los máximos y mínimos locales tanto en el precio como en los datos RSI. Estos puntos de oscilación sirven como marcadores de referencia para nuestro análisis de divergencia.3. Detección de divergencia regular
- Divergencia alcista regular: el EA busca instancias en las que el precio forma un mínimo más bajo mientras que el RSI forma un mínimo más alto, lo que indica que una tendencia bajista puede estar perdiendo impulso y podría revertirse al alza.
- Divergencia bajista regular: también busca situaciones en las que el precio alcanza un máximo más alto mientras que el RSI forma un máximo más bajo, lo que indica que una tendencia alcista podría estar llegando a su fin a medida que el impulso disminuye.
- Divergencia alcista oculta: En una tendencia alcista, si el precio forma un mínimo más alto pero el RSI registra un mínimo más bajo, el EA identifica esto como una señal de que la tendencia alcista general sigue siendo fuerte a pesar de un retroceso temporal.
- Divergencia bajista oculta: Por el contrario, durante una tendencia bajista, si el precio alcanza un máximo más bajo mientras que el RSI muestra un máximo más alto, se confirma que es probable que la tendencia bajista continúe.
5. Generación de señales visuales y de registro
Una vez detectada una divergencia, ya sea regular u oculta, el EA marca visualmente el evento en el gráfico (utilizando flechas y etiquetas) y registra los detalles de la señal para su posterior análisis o backtesting. Para obtener más información sobre cómo se llevan a cabo los procesos anteriores, consulte la sección Desglose del código a continuación.
Código MQL5
//+--------------------------------------------------------------------+ //| RSI Divergence.mql5 | //| Copyright 2025, Christian Benjamin | //| https://www.mql5.com | //+--------------------------------------------------------------------+ #property copyright "2025, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict //---- Input parameters input int InpRSIPeriod = 14; // RSI period input int InpSwingLeft = 1; // Bars to the left for swing detection (relaxed) input int InpSwingRight = 1; // Bars to the right for swing detection (relaxed) input int InpLookback = 100; // Number of bars to scan for divergence input int InpEvalBars = 5; // Bars after which to evaluate a signal input int InpMinBarsBetweenSignals = 1; // Minimum bars between same-type signals (allows frequent re-entry) input double InpArrowOffset = 3.0; // Arrow offset (in points) for display input double InpMinSwingDiffPct = 0.05; // Lower minimum % difference to qualify as a swing input double InpMinRSIDiff = 1.0; // Lower minimum difference in RSI between swing points // Optional RSI threshold filter for bullish divergence (disabled by default) input bool InpUseRSIThreshold = false; // If true, require earlier RSI swing to be oversold for bullish divergence input double InpRSIOversold = 30; // RSI oversold level input double InpRSIOverbought = 70; // RSI overbought level (if needed for bearish) //---- Global variables int rsiHandle; // Handle for the RSI indicator double rsiBuffer[]; // Buffer for RSI values double lowBuffer[]; // Buffer for low prices double highBuffer[]; // Buffer for high prices double closeBuffer[]; // Buffer for close prices datetime timeBuffer[]; // Buffer for bar times int g_totalBars = 0; // Number of bars in our copied arrays datetime lastBarTime = 0; // Time of last closed bar //---- Structure to hold signal information struct SignalInfo { string type; // e.g. "RegBearish Divergence", "HiddenBullish Divergence" int barIndex; // Bar index where the signal was generated datetime signalTime; // Time of the signal bar double signalPrice; // Price used for the signal (swing high for bearish, swing low for bullish) }; SignalInfo signals[]; // Global array to store signals //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); if(rsiHandle == INVALID_HANDLE) { Print("Error creating RSI handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(rsiHandle != INVALID_HANDLE) IndicatorRelease(rsiHandle); EvaluateSignalsAndPrint(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Process once per new closed candle (using bar1's time) datetime currentBarTime = iTime(_Symbol, _Period, 1); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; //--- Copy RSI data ArrayResize(rsiBuffer, InpLookback); ArraySetAsSeries(rsiBuffer, true); if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0) { Print("Error copying RSI data"); return; } //--- Copy price and time data ArrayResize(lowBuffer, InpLookback); ArrayResize(highBuffer, InpLookback); ArrayResize(closeBuffer, InpLookback); ArraySetAsSeries(lowBuffer, true); ArraySetAsSeries(highBuffer, true); ArraySetAsSeries(closeBuffer, true); if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 || CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 || CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0) { Print("Error copying price data"); return; } ArrayResize(timeBuffer, InpLookback); ArraySetAsSeries(timeBuffer, true); if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0) { Print("Error copying time data"); return; } g_totalBars = InpLookback; //--- Identify swing lows and swing highs int swingLows[]; int swingHighs[]; int startIndex = InpSwingLeft; int endIndex = g_totalBars - InpSwingRight; for(int i = startIndex; i < endIndex; i++) { if(IsSignificantSwingLow(i, InpSwingLeft, InpSwingRight)) { ArrayResize(swingLows, ArraySize(swingLows) + 1); swingLows[ArraySize(swingLows) - 1] = i; } if(IsSignificantSwingHigh(i, InpSwingLeft, InpSwingRight)) { ArrayResize(swingHighs, ArraySize(swingHighs) + 1); swingHighs[ArraySize(swingHighs) - 1] = i; } } //--- Bearish Divergence (using swing highs) if(ArraySize(swingHighs) >= 2) { ArraySort(swingHighs); // ascending order: index 0 is most recent int recent = swingHighs[0]; int previous = swingHighs[1]; // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high if(highBuffer[recent] > highBuffer[previous] && rsiBuffer[recent] < rsiBuffer[previous] && (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff) { Print("Regular Bearish Divergence detected at bar ", recent); DisplaySignal("RegBearish Divergence", recent); } // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high else if(highBuffer[recent] < highBuffer[previous] && rsiBuffer[recent] > rsiBuffer[previous] && (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff) { Print("Hidden Bearish Divergence detected at bar ", recent); DisplaySignal("HiddenBearish Divergence", recent); } } //--- Bullish Divergence (using swing lows) if(ArraySize(swingLows) >= 2) { ArraySort(swingLows); // ascending order: index 0 is most recent int recent = swingLows[0]; int previous = swingLows[1]; // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low if(lowBuffer[recent] < lowBuffer[previous] && rsiBuffer[recent] > rsiBuffer[previous] && (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff) { // Optionally require the earlier swing's RSI be oversold if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold) { Print("Regular Bullish Divergence detected at bar ", recent); DisplaySignal("RegBullish Divergence", recent); } } // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low else if(lowBuffer[recent] > lowBuffer[previous] && rsiBuffer[recent] < rsiBuffer[previous] && (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff) { Print("Hidden Bullish Divergence detected at bar ", recent); DisplaySignal("HiddenBullish Divergence", recent); } } } //+------------------------------------------------------------------------+ //| IsSignificantSwingLow: Determines if the bar at 'index' is a swing low | //+------------------------------------------------------------------------+ bool IsSignificantSwingLow(int index, int left, int right) { double currentLow = lowBuffer[index]; // Check left side for a local minimum condition for(int i = index - left; i < index; i++) { if(i < 0) continue; double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0; if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct) return false; } // Check right side for a local minimum condition for(int i = index + 1; i <= index + right; i++) { if(i >= g_totalBars) break; double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0; if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct) return false; } return true; } //+--------------------------------------------------------------------------+ //| IsSignificantSwingHigh: Determines if the bar at 'index' is a swing high | //+--------------------------------------------------------------------------+ bool IsSignificantSwingHigh(int index, int left, int right) { double currentHigh = highBuffer[index]; // Check left side for a local maximum condition for(int i = index - left; i < index; i++) { if(i < 0) continue; double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0; if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct) return false; } // Check right side for a local maximum condition for(int i = index + 1; i <= index + right; i++) { if(i >= g_totalBars) break; double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0; if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct) return false; } return true; } //+------------------------------------------------------------------+ //| DisplaySignal: Draws an arrow on the chart and records the signal| //+------------------------------------------------------------------+ void DisplaySignal(string signalText, int barIndex) { // Prevent duplicate signals on the same bar (or too close) for(int i = 0; i < ArraySize(signals); i++) { if(StringFind(signals[i].type, signalText) != -1) if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals) return; } // Update a "LatestSignal" label for regular signals. if(StringFind(signalText, "Reg") != -1) { string labelName = "LatestSignal"; if(ObjectFind(0, labelName) == -1) { if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0)) { Print("Failed to create LatestSignal label"); return; } ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0); ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite); } ObjectSetString(0, labelName, OBJPROP_TEXT, signalText); } // Create an arrow object for the signal. string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex); if(ObjectFind(0, arrowName) < 0) { int arrowCode = 0; double arrowPrice = 0.0; color arrowColor = clrWhite; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(StringFind(signalText, "Bullish") != -1) { arrowCode = 233; // Wingdings up arrow arrowColor = clrLime; arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point); } else if(StringFind(signalText, "Bearish") != -1) { arrowCode = 234; // Wingdings down arrow arrowColor = clrRed; arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point); } if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice)) { Print("Failed to create arrow object ", arrowName); return; } ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); } // Record the signal for evaluation. SignalInfo sig; sig.type = signalText; sig.barIndex = barIndex; sig.signalTime = timeBuffer[barIndex]; if(StringFind(signalText, "Bullish") != -1) sig.signalPrice = lowBuffer[barIndex]; else sig.signalPrice = highBuffer[barIndex]; ArrayResize(signals, ArraySize(signals) + 1); signals[ArraySize(signals) - 1] = sig; UpdateSignalCountLabel(); } //+------------------------------------------------------------------+ //| UpdateSignalCountLabel: Updates a label showing signal counts | //+------------------------------------------------------------------+ void UpdateSignalCountLabel() { int regCount = 0, hidCount = 0; for(int i = 0; i < ArraySize(signals); i++) { if(StringFind(signals[i].type, "Reg") != -1) regCount++; else if(StringFind(signals[i].type, "Hidden") != -1) hidCount++; } string countText = "Regular Signals: " + IntegerToString(regCount) + "\nHidden Signals: " + IntegerToString(hidCount); string countLabel = "SignalCount"; if(ObjectFind(0, countLabel) == -1) { if(!ObjectCreate(0, countLabel, OBJ_LABEL, 0, 0, 0)) { Print("Failed to create SignalCount label"); return; } ObjectSetInteger(0, countLabel, OBJPROP_CORNER, 0); ObjectSetInteger(0, countLabel, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, countLabel, OBJPROP_YDISTANCE, 40); ObjectSetInteger(0, countLabel, OBJPROP_COLOR, clrYellow); } ObjectSetString(0, countLabel, OBJPROP_TEXT, countText); } //+--------------------------------------------------------------------+ //| EvaluateSignalsAndPrint: After backtesting, prints signal accuracy | //+--------------------------------------------------------------------+ void EvaluateSignalsAndPrint() { double closeAll[]; int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll); if(totalBars <= 0) { Print("Error copying complete close data for evaluation"); return; } ArraySetAsSeries(closeAll, true); int totalEvaluated = 0, regTotal = 0, hidTotal = 0; int regEval = 0, hidEval = 0; int regCorrect = 0, hidCorrect = 0; for(int i = 0; i < ArraySize(signals); i++) { int evalIndex = signals[i].barIndex - InpEvalBars; if(evalIndex < 0) continue; double evalClose = closeAll[evalIndex]; if(StringFind(signals[i].type, "Bullish") != -1) { if(StringFind(signals[i].type, "Reg") != -1) { regTotal++; regEval++; if(evalClose > signals[i].signalPrice) regCorrect++; } else if(StringFind(signals[i].type, "Hidden") != -1) { hidTotal++; hidEval++; if(evalClose > signals[i].signalPrice) hidCorrect++; } totalEvaluated++; } else if(StringFind(signals[i].type, "Bearish") != -1) { if(StringFind(signals[i].type, "Reg") != -1) { regTotal++; regEval++; if(evalClose < signals[i].signalPrice) regCorrect++; } else if(StringFind(signals[i].type, "Hidden") != -1) { hidTotal++; hidEval++; if(evalClose < signals[i].signalPrice) hidCorrect++; } totalEvaluated++; } } double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0; double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0; double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0; Print("----- Backtest Signal Evaluation -----"); Print("Total Signals Generated: ", ArraySize(signals)); Print("Signals Evaluated: ", totalEvaluated); Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%"); Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%"); Print("Hidden Signals: ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%"); } //+------------------------------------------------------------------+
Desglose del código
1. Información del encabezado y parámetros de entrada
En la parte superior de nuestro script, tenemos un encabezado bien definido que proporciona información clave sobre el código.
Información sobre el archivo y el autor
El encabezado especifica el nombre del archivo (RSI Divergence.mql5), el aviso de derechos de autor y un enlace al perfil del autor. Esto garantiza la atribución adecuada y proporciona a los usuarios un punto de referencia si necesitan comprobar si hay actualizaciones o documentación adicional.
Directivas de versionado y compilación
Las directivas #property establecen propiedades importantes, como el número de versión y el uso de reglas de compilación estrictas (#property strict). Esto ayuda a mantener la coherencia y a reducir los posibles errores durante el desarrollo y la implementación. Continuando, la sección de parámetros de entrada es esencial para la personalización. Estos parámetros le permiten a usted, o a cualquier usuario, ajustar con precisión el comportamiento de la lógica de detección de divergencias sin modificar el código central. A continuación se presentan algunos aspectos destacados:
Parámetros de detección de RSI y oscilación
- InpRSIPeriod: Establece el período para el indicador RSI.
- InpSwingLeft e InpSwingRight: Definen cuántos bares de cada lado se tienen en cuenta al detectar puntos de oscilación. Ajustar estos valores hace que la detección de balanceo sea más flexible o más estricta.
Configuración de divergencia y evaluación de señales
- InpLookback: Determina cuántos bares en el pasado analizará el script en busca de divergencias.
- InpEvalBars: Especifica el número de barras que hay que esperar antes de evaluar si una señal ha tenido éxito.
- InpMinBarsBetweenSignals: Ayuda a evitar señales duplicadas al imponer una separación mínima entre barras para señales similares.
Personalización de la pantalla
- InpArrowOffset: Establece la distancia (en puntos) a la que las flechas se desplazan del punto de oscilación, lo que mejora la claridad visual del gráfico.
Filtro de umbral RSI opcional
- InpUseRSIThreshold, junto con InpRSIOversold e InpRSIOverbought, proporciona una capa adicional de filtrado. Esto garantiza que, en caso de divergencia alcista, la oscilación anterior del RSI se encuentre en la zona de sobreventa, si el usuario decide activar este filtro.
//+------------------------------------------------------------------+ //| RSI Divergence.mql5 | //| Copyright 2025, Christian Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2025, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/users/lynnchris" #property version "1.0" #property strict //---- Input parameters input int InpRSIPeriod = 14; // RSI period input int InpSwingLeft = 1; // Bars to the left for swing detection (relaxed) input int InpSwingRight = 1; // Bars to the right for swing detection (relaxed) input int InpLookback = 100; // Number of bars to scan for divergence input int InpEvalBars = 5; // Bars after which to evaluate a signal input int InpMinBarsBetweenSignals = 1; // Minimum bars between same-type signals (allows frequent re-entry) input double InpArrowOffset = 3.0; // Arrow offset (in points) for display input double InpMinSwingDiffPct = 0.05; // Lower minimum % difference to qualify as a swing input double InpMinRSIDiff = 1.0; // Lower minimum difference in RSI between swing points // Optional RSI threshold filter for bullish divergence (disabled by default) input bool InpUseRSIThreshold = false; // If true, require earlier RSI swing to be oversold for bullish divergence input double InpRSIOversold = 30; // RSI oversold level input double InpRSIOverbought = 70; // RSI overbought level (if needed for bearish)
2. Inicialización del indicador
En esta parte, inicializamos nuestro indicador RSI. La función OnInit() crea un identificador para el indicador RSI utilizando parámetros como el símbolo, el marco temporal y el período RSI especificado por el usuario. Este paso es crucial porque todas las operaciones posteriores dependen de tener un identificador RSI válido para recuperar los datos del indicador.
- La función iRSI se invoca con los parámetros necesarios.
- Se implementa el manejo de errores para detectar cualquier fallo en la creación del identificador.
- La inicialización garantiza que nuestro indicador esté listo para la adquisición y el análisis de datos.
int OnInit() { // Create the RSI indicator handle with the specified period rsiHandle = iRSI(_Symbol, _Period, InpRSIPeriod, PRICE_CLOSE); if(rsiHandle == INVALID_HANDLE) { Print("Error creating RSI handle"); return(INIT_FAILED); } return(INIT_SUCCEEDED); }
3. Adquisición de datos en cada nueva vela
En la función OnTick(), comprobamos si se ha cerrado una nueva vela antes de procesarla. Esto garantiza que nuestro análisis siempre se realice con datos completos. A continuación, copiamos matrices de valores RSI, mínimos, máximos, cierres y datos temporales durante un periodo de revisión configurable. Al configurar las matrices como series, se garantiza que los datos se ordenen con la barra más reciente en el índice 0.
- El código espera a la siguiente vela cerrada para evitar procesar datos incompletos.
- Los datos del RSI y del precio se recuperan utilizando funciones como CopyBuffer y CopyLow/High/Close/Time.
- El uso de ArraySetAsSeries conserva el orden correcto para el análisis de series temporales.
void OnTick() { // Process only once per new closed candle by comparing bar times datetime currentBarTime = iTime(_Symbol, _Period, 1); if(currentBarTime == lastBarTime) return; lastBarTime = currentBarTime; // Copy RSI data for a given lookback period ArrayResize(rsiBuffer, InpLookback); ArraySetAsSeries(rsiBuffer, true); if(CopyBuffer(rsiHandle, 0, 0, InpLookback, rsiBuffer) <= 0) { Print("Error copying RSI data"); return; } // Copy price data (lows, highs, closes) and time data for analysis ArrayResize(lowBuffer, InpLookback); ArrayResize(highBuffer, InpLookback); ArrayResize(closeBuffer, InpLookback); ArraySetAsSeries(lowBuffer, true); ArraySetAsSeries(highBuffer, true); ArraySetAsSeries(closeBuffer, true); if(CopyLow(_Symbol, _Period, 0, InpLookback, lowBuffer) <= 0 || CopyHigh(_Symbol, _Period, 0, InpLookback, highBuffer) <= 0 || CopyClose(_Symbol, _Period, 0, InpLookback, closeBuffer) <= 0) { Print("Error copying price data"); return; } ArrayResize(timeBuffer, InpLookback); ArraySetAsSeries(timeBuffer, true); if(CopyTime(_Symbol, _Period, 0, InpLookback, timeBuffer) <= 0) { Print("Error copying time data"); return; } g_totalBars = InpLookback; // (Further processing follows here...) }
4. Detección de oscilaciones (identificación de mínimos y máximos de oscilación)
Antes de poder detectar divergencias, primero debemos identificar los puntos de inflexión significativos. Se utilizan dos funciones auxiliares, IsSignificantSwingLow e IsSignificantSwingHigh, para identificar mínimos y máximos locales. Para ello, comparan el valor mínimo o máximo de una barra con las barras vecinas dentro de una ventana determinada y comprueban que la diferencia porcentual cumple un umbral establecido.- Las funciones comprueban tanto la izquierda como la derecha de la barra actual.
- Calculan la diferencia porcentual para garantizar que solo se marquen las variaciones significativas.
- Este filtrado reduce el ruido, garantizando que nuestro análisis de divergencia se centre en movimientos significativos del mercado.
bool IsSignificantSwingLow(int index, int left, int right) { double currentLow = lowBuffer[index]; // Check left side for local minimum condition for(int i = index - left; i < index; i++) { if(i < 0) continue; double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0; if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct) return false; } // Check right side for local minimum condition for(int i = index + 1; i <= index + right; i++) { if(i >= g_totalBars) break; double pctDiff = MathAbs((lowBuffer[i] - currentLow) / currentLow) * 100.0; if(lowBuffer[i] < currentLow && pctDiff > InpMinSwingDiffPct) return false; } return true; } bool IsSignificantSwingHigh(int index, int left, int right) { double currentHigh = highBuffer[index]; // Check left side for local maximum condition for(int i = index - left; i < index; i++) { if(i < 0) continue; double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0; if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct) return false; } // Check right side for local maximum condition for(int i = index + 1; i <= index + right; i++) { if(i >= g_totalBars) break; double pctDiff = MathAbs((currentHigh - highBuffer[i]) / currentHigh) * 100.0; if(highBuffer[i] > currentHigh && pctDiff > InpMinSwingDiffPct) return false; } return true; }
5. Detección de divergencias: Divergencias bajistas y alcistas
Una vez identificados los puntos de oscilación, el algoritmo compara las oscilaciones recientes para detectar divergencias. Para la divergencia bajista, el código analiza dos máximos oscilantes y verifica si el precio está formando un máximo más alto mientras el RSI muestra un máximo más bajo (o viceversa para la divergencia bajista oculta). Para la divergencia alcista, se comparan de manera similar dos mínimos oscilantes. Un umbral RSI opcional puede validar aún más las señales alcistas al garantizar que la lectura RSI anterior esté en territorio de sobreventa.
- Para el análisis de divergencia se utilizan dos puntos de oscilación recientes (máximos o mínimos).
- Las condiciones para las divergencias regulares y ocultas están claramente separadas.
- Los parámetros opcionales (como la condición de sobreventa del RSI) proporcionan un filtrado adicional para la intensidad de la señal.
// --- Bearish Divergence (using swing highs) if(ArraySize(swingHighs) >= 2) { ArraySort(swingHighs); // Ensure ascending order: index 0 is most recent int recent = swingHighs[0]; int previous = swingHighs[1]; // Regular Bearish Divergence: Price makes a higher high while RSI makes a lower high if(highBuffer[recent] > highBuffer[previous] && rsiBuffer[recent] < rsiBuffer[previous] && (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff) { Print("Regular Bearish Divergence detected at bar ", recent); DisplaySignal("RegBearish Divergence", recent); } // Hidden Bearish Divergence: Price makes a lower high while RSI makes a higher high else if(highBuffer[recent] < highBuffer[previous] && rsiBuffer[recent] > rsiBuffer[previous] && (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff) { Print("Hidden Bearish Divergence detected at bar ", recent); DisplaySignal("HiddenBearish Divergence", recent); } } // --- Bullish Divergence (using swing lows) if(ArraySize(swingLows) >= 2) { ArraySort(swingLows); // Ensure ascending order: index 0 is most recent int recent = swingLows[0]; int previous = swingLows[1]; // Regular Bullish Divergence: Price makes a lower low while RSI makes a higher low if(lowBuffer[recent] < lowBuffer[previous] && rsiBuffer[recent] > rsiBuffer[previous] && (rsiBuffer[recent] - rsiBuffer[previous]) >= InpMinRSIDiff) { // Optionally require the earlier RSI swing to be oversold if(!InpUseRSIThreshold || rsiBuffer[previous] <= InpRSIOversold) { Print("Regular Bullish Divergence detected at bar ", recent); DisplaySignal("RegBullish Divergence", recent); } } // Hidden Bullish Divergence: Price makes a higher low while RSI makes a lower low else if(lowBuffer[recent] > lowBuffer[previous] && rsiBuffer[recent] < rsiBuffer[previous] && (rsiBuffer[previous] - rsiBuffer[recent]) >= InpMinRSIDiff) { Print("Hidden Bullish Divergence detected at bar ", recent); DisplaySignal("HiddenBullish Divergence", recent); } }
6. Visualización y grabación de señales
Cuando se detecta una divergencia, es importante marcar la señal visualmente y registrar sus detalles para una evaluación posterior. La función DisplaySignal() no solo crea una flecha en el gráfico (utilizando diferentes códigos y colores de flecha para las señales alcistas y bajistas), sino que también actualiza una etiqueta para la última señal y almacena los metadatos de la señal en una matriz global. Este registro sistemático permite realizar posteriormente un backtesting de la estrategia.
- Las señales duplicadas se evitan comprobando si ya existe una señal para una barra similar.
- Las señales visuales como flechas y etiquetas mejoran la legibilidad del gráfico.
- Cada señal se almacena con detalles como tipo, índice de barras, hora y precio, lo que facilita la evaluación posterior del rendimiento.
void DisplaySignal(string signalText, int barIndex) { // Prevent duplicate signals on the same or nearby bars for(int i = 0; i < ArraySize(signals); i++) { if(StringFind(signals[i].type, signalText) != -1) if(MathAbs(signals[i].barIndex - barIndex) < InpMinBarsBetweenSignals) return; } // Update a label for the latest regular signal if(StringFind(signalText, "Reg") != -1) { string labelName = "LatestSignal"; if(ObjectFind(0, labelName) == -1) { if(!ObjectCreate(0, labelName, OBJ_LABEL, 0, 0, 0)) { Print("Failed to create LatestSignal label"); return; } ObjectSetInteger(0, labelName, OBJPROP_CORNER, 0); ObjectSetInteger(0, labelName, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, labelName, OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, labelName, OBJPROP_COLOR, clrWhite); } ObjectSetString(0, labelName, OBJPROP_TEXT, signalText); } // Create an arrow object to mark the signal on the chart string arrowName = "Arrow_" + signalText + "_" + IntegerToString(barIndex); if(ObjectFind(0, arrowName) < 0) { int arrowCode = 0; double arrowPrice = 0.0; color arrowColor = clrWhite; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(StringFind(signalText, "Bullish") != -1) { arrowCode = 233; // Wingdings up arrow arrowColor = clrLime; arrowPrice = lowBuffer[barIndex] - (InpArrowOffset * point); } else if(StringFind(signalText, "Bearish") != -1) { arrowCode = 234; // Wingdings down arrow arrowColor = clrRed; arrowPrice = highBuffer[barIndex] + (InpArrowOffset * point); } if(!ObjectCreate(0, arrowName, OBJ_ARROW, 0, timeBuffer[barIndex], arrowPrice)) { Print("Failed to create arrow object ", arrowName); return; } ObjectSetInteger(0, arrowName, OBJPROP_COLOR, arrowColor); ObjectSetInteger(0, arrowName, OBJPROP_ARROWCODE, arrowCode); } // Record the signal details for later backtesting evaluation SignalInfo sig; sig.type = signalText; sig.barIndex = barIndex; sig.signalTime = timeBuffer[barIndex]; if(StringFind(signalText, "Bullish") != -1) sig.signalPrice = lowBuffer[barIndex]; else sig.signalPrice = highBuffer[barIndex]; ArrayResize(signals, ArraySize(signals) + 1); signals[ArraySize(signals) - 1] = sig; UpdateSignalCountLabel(); }
Evaluación de la prueba retrospectiva sobre la desinicialización
Por último, la función EvaluateSignalsAndPrint() se invoca cuando el experto se desinicializa. Esta función evalúa retrospectivamente todas las señales registradas comparando el movimiento del precio unas pocas barras después de la señal contra el precio registrado de la señal. Calcula la precisión de las señales regulares y ocultas, proporcionando información valiosa sobre el rendimiento de nuestra estrategia de divergencia.
- La función recupera datos históricos de cierre completos.
- Cada señal se evalúa tras un número fijo de barras (según lo establecido por InpEvalBars).
- Se calculan métricas de precisión para las señales generales, así como por separado para las señales regulares y ocultas, lo que ayuda en la validación del rendimiento.
void EvaluateSignalsAndPrint() { double closeAll[]; int totalBars = CopyClose(_Symbol, _Period, 0, WHOLE_ARRAY, closeAll); if(totalBars <= 0) { Print("Error copying complete close data for evaluation"); return; } ArraySetAsSeries(closeAll, true); int totalEvaluated = 0, regTotal = 0, hidTotal = 0; int regEval = 0, hidEval = 0; int regCorrect = 0, hidCorrect = 0; for(int i = 0; i < ArraySize(signals); i++) { int evalIndex = signals[i].barIndex - InpEvalBars; if(evalIndex < 0) continue; double evalClose = closeAll[evalIndex]; if(StringFind(signals[i].type, "Bullish") != -1) { if(StringFind(signals[i].type, "Reg") != -1) { regTotal++; regEval++; if(evalClose > signals[i].signalPrice) regCorrect++; } else if(StringFind(signals[i].type, "Hidden") != -1) { hidTotal++; hidEval++; if(evalClose > signals[i].signalPrice) hidCorrect++; } totalEvaluated++; } else if(StringFind(signals[i].type, "Bearish") != -1) { if(StringFind(signals[i].type, "Reg") != -1) { regTotal++; regEval++; if(evalClose < signals[i].signalPrice) regCorrect++; } else if(StringFind(signals[i].type, "Hidden") != -1) { hidTotal++; hidEval++; if(evalClose < signals[i].signalPrice) hidCorrect++; } totalEvaluated++; } } double overallAccuracy = (totalEvaluated > 0) ? (double)(regCorrect + hidCorrect) / totalEvaluated * 100.0 : 0.0; double regAccuracy = (regEval > 0) ? (double)regCorrect / regEval * 100.0 : 0.0; double hidAccuracy = (hidEval > 0) ? (double)hidCorrect / hidEval * 100.0 : 0.0; Print("----- Backtest Signal Evaluation -----"); Print("Total Signals Generated: ", ArraySize(signals)); Print("Signals Evaluated: ", totalEvaluated); Print("Overall Accuracy: ", DoubleToString(overallAccuracy, 2), "%"); Print("Regular Signals: ", regTotal, " | Evaluated: ", regEval, " | Accuracy: ", DoubleToString(regAccuracy, 2), "%"); Print("Hidden Signals: ", hidTotal, " | Evaluated: ", hidEval, " | Accuracy: ", DoubleToString(hidAccuracy, 2), "%"); }
Pruebas y resultados
Después de compilar correctamente su EA con MetaEditor, arrastre su EA al gráfico para probarlo. Asegúrate de utilizar una cuenta demo para evitar arriesgar dinero real. También puede agregar el indicador RSI a su gráfico para confirmar fácilmente la señal mientras prueba su EA. Para ello, vaya a la pestaña Indicadores, seleccione el indicador RSI en la carpeta Paneles y configure sus parámetros preferidos, asegurándose de que coincidan con los de su EA. Vea el GIF a continuación, que ilustra cómo agregar la ventana del indicador RSI en un gráfico de MetaTrader 5. También se puede ver la señal confirmada, una divergencia alcista regular en un marco de tiempo de un minuto.

Figura 5. Indicador de configuración y resultado de la prueba 1
A continuación se muestra otra prueba que realizamos en Boom 500, confirmada tanto por la acción del precio como por el indicador RSI, que muestra una señal de venta.

Figura 6. Resultado de la prueba 2
Se realizó otra prueba utilizando backtesting en el GIF que aparece a continuación, que muestra varios cambios positivos. Si miras con atención, notarás señales de continuación ocultas y señales regulares. Sin embargo, algunas señales deben ser filtradas debido a la falta de confirmación, a pesar de su impacto positivo.

Figura 7. Resultado de la prueba 3
Conclusión
Esta herramienta ha demostrado estar extremadamente alineada con la acción del precio, que es el objetivo principal de nuestra serie para crear tantas herramientas de análisis de la acción del precio como sea posible. Realmente he apreciado la eficacia con la que el indicador RSI interactúa con la acción del precio al extraer señales positivas de las divergencias. Las pruebas que hemos realizado han mostrado resultados prometedores y una tendencia positiva.
Sin embargo, creo que es hora de introducir otra mejora que utilice bibliotecas externas para una identificación de swing precisa y exacta, mejorando así la precisión de la señal. Mi consejo es que pruebes la herramienta a fondo y ajustes sus parámetros para adaptarlos a tu estilo de trading. Recuerde que cada señal generada debe verificarse antes de ingresar, ya que la herramienta está diseñada para ayudarlo a monitorear el mercado y confirmar su estrategia general.
| Fecha | Nombre de la herramienta | Descripción | Versión | Actualizaciones | Notas |
|---|---|---|---|---|---|
| 01/10/24 | Chart Projector | Script para superponer la acción del precio del día anterior con efecto fantasma. | 1.0 | Lanzamiento inicial | Primera herramienta en Lynnchris Tool Chest |
| 18/11/24 | Analytical Comment | Proporciona información del día anterior en formato tabular y anticipa la dirección futura del mercado. | 1.0 | Lanzamiento inicial | Segunda herramienta en Lynnchris Tool Chest |
| 27/11/24 | Analytics Master | Actualización periódica de las métricas del mercado cada dos horas | 1.01 | Segundo lanzamiento | Tercera herramienta en Lynnchris Tool Chest |
| 02/12/24 | Analytics Forecaster | Actualización periódica de las métricas del mercado cada dos horas con integración de Telegram | 1.1 | Tercera edición | Herramienta número 4 |
| 09/12/24 | Volatility Navigator | El EA analiza las condiciones del mercado utilizando los indicadores Bandas de Bollinger, RSI y ATR | 1.0 | Lanzamiento inicial | Herramienta número 5 |
| 19/12/24 | Mean Reversion Signal Reaper | Analiza el mercado utilizando la estrategia de reversión a la media y proporciona señales. | 1.0 | Lanzamiento inicial | Herramienta número 6 |
| 09/01/25 | Signal Pulse | Analizador de múltiples marcos temporales | 1.0 | Lanzamiento inicial | Herramienta número 7 |
| 17/01/25 | Metrics Board | Panel con botón para análisis | 1.0 | Lanzamiento inicial | Herramienta número 8 |
| 21/01/25 | External Flow | Análisis a través de bibliotecas externas | 1.0 | Lanzamiento inicial | Herramienta número 9 |
| 27/01/25 | VWAP | Volume Weighted Average Price | 1.3 | Lanzamiento inicial | Herramienta número 10 |
| 02/02/25 | Heikin Ashi | Identificación de señales de suavizado y reversión de tendencias | 1.0 | Lanzamiento inicial | Herramienta número 11 |
| 04/02/25 | FibVWAP | Generación de señales mediante análisis de Python | 1.0 | Lanzamiento inicial | Herramienta número 12 |
| 14/02/25 | RSI DIVERGENCE | Acción del precio frente a divergencias del RSI | 1.0 | Lanzamiento inicial | Herramienta número 13 |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/17198
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.
Gestor de riesgos profesional remoto para Forex en Python
Algoritmo del restaurador de éxito — Successful Restaurateur Algorithm (SRA)
Cierres parciales condicionales (Parte 1): Creación de la clase base
Redes neuronales en el trading: Integración de la teoría del caos en la previsión de series temporales (Final)
- 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