Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 43): Probabilidad de las velas japonesas y rupturas
Índice
Introducción
En este artículo llevamos a cabo un análisis de probabilidad de tres tipos comunes de velas japonesas, doji, patrón envolvente y pinbar, para evaluar cómo influyen en la evolución de los precios en diferentes pares de instrumentos. Estas velas suelen indicar, bien la continuación de la tendencia, bien su inversión. Por ejemplo, en una tendencia bajista, un pinbar alcista o un patrón envolvente alcista pueden indicar un posible cambio de tendencia al alza y, a la inversa, en una tendencia alcista, un pinbar bajista o un patrón envolvente bajista pueden preceder a un cambio de tendencia a la baja. Un doji suele representar indecisión: en el marco de una tendencia fuerte, puede indicar una breve pausa seguida de una continuación, mientras que en niveles clave de soporte o resistencia, o tras un movimiento prolongado, puede indicar un posible cambio de tendencia. Un doji que se forma en un mercado lateral, o sin contexto de volumen, suele ser neutro.
Un problema habitual al que se enfrentan los operadores son las entradas prematuras, como ver un pinbar alcista y comprar de inmediato, para acabar en una posición con pérdidas cuando el mercado no logra revertir su tendencia. Los distintos instrumentos y marcos temporales reaccionan de forma diferente ante una misma formación; algunos pares muestran una mayor continuación del precio en la dirección indicada por el patrón tras determinados patrones. Este EA está diseñado como un analista de probabilidades; analiza las barras históricas y cuantifica la frecuencia con la que cada patrón da lugar a una continuación, un cambio de tendencia o rupturas exitosas en un par y un marco temporal concretos. Esa perspectiva empírica ayuda a los operadores que basan sus decisiones en la acción del precio a saber qué patrones son más fiables a la hora de identificar señales de reversión o de continuación en los mercados en los que operan.
Si observa con atención el diagrama anterior, el patrón A es un patrón envolvente alcista, que se asemeja a una señal de compra, y vemos que el mercado sube tras su formación, lo que confirma un resultado positivo. En B observamos un Doji seguido de un pinbar bajista, y posteriormente el mercado se mueve a favor de los bajistas, lo que confirma la señal bajista. Lo mismo ocurre con C, que es un patrón envolvente bajista que indicó una venta y dio lugar a la caída esperada.
En D hay un Doji formado en un nivel de soporte, lo que sugiere un posible giro alcista; el pinbar posterior debe interpretarse en función de su cola y su ubicación, ya que el color de la vela por sí solo puede resultar engañoso. Por último, E muestra un patrón envolvente bajista que falló, ya que el precio subió en lugar de bajar, lo que demuestra que estos patrones son probabilísticos y pueden generar señales falsas. Presentar nuestra herramienta como un analista de probabilidades ayuda a cuantificar con qué frecuencia cada formación da lugar a un cambio de tendencia, una continuación o un fracaso en un par y un marco temporal concretos.
Resumen de la estrategia
Lo que quiero que usted comprenda es la idea que hay detrás de esta herramienta. En primer lugar, explicaré el concepto y ofreceré un diagrama de flujo para que pueda seguir la lógica; a continuación, mostraré los detalles de la programación en la sección de implementación.
En primer lugar, la herramienta recopila las barras de precios recientes que usted le ha pedido que analice para el símbolo y el marco temporal seleccionados. Esto significa que carga los precios de apertura, máximo, mínimo y cierre de cada vela, junto con la actividad de negociación de esa barra. Además, prepara la visualización del gráfico y pone a cero todos los contadores para que cada ejecución comience desde cero. El objetivo es usar un historial limpio y reciente, de modo que los porcentajes reflejen los datos con los que usted espera operar.
A continuación, la herramienta analiza las barras históricas una por una y comprueba si alguna de ellas coincide con alguna de las tres formas de velas que analizamos: «pinbar», «engulfing» o «doji». En pocas palabras, un pinbar tiene un cuerpo pequeño y una cola larga; un patrón envolvente se produce cuando el cuerpo de una vela cubre por completo el cuerpo de la vela anterior; y un Doji tiene un cuerpo minúsculo. Cuando la herramienta encuentra una coincidencia, registra ese momento como una aparición del patrón.
Cada vez que aparece un patrón, la herramienta registra varios datos: que el patrón se haya producido, cuándo se ha producido y cuánta actividad de negociación hubo en esa barra (el «volumen»). A continuación, comprueba tres resultados para esa aparición: si la barra cerrada inmediatamente posterior se movió en la dirección que sugería el patrón, si el precio se movió en esa dirección en algún momento dentro de las siguientes N barras que usted haya establecido y si el precio rebasó el máximo o el mínimo del patrón en la cantidad de pips preestablecida y la barra siguiente confirmó esa ruptura. Cada comprobación se registra como «sí» o «no», y también se almacenan los volúmenes correspondientes a esas apariciones concretas.

La vista más sencilla convierte esos recuentos en porcentajes fáciles de interpretar. Las fórmulas son:
- % de aciertos en la barra siguiente = (Apariciones del patrón con resultado favorable en la barra siguiente ÷ Total de apariciones) × 100
- % de aciertos en las siguientes N barras = (Apariciones del patrón con resultado favorable en las siguientes N barras ÷ Total de apariciones) × 100
- Porcentaje de éxito en las rupturas = (Rupturas confirmadas ÷ Total de rupturas) × 100
- % de aciertos en la barra siguiente (ponderado por volumen) = (Volumen de los patrones con resultado favorable en la barra siguiente ÷ Volumen total de todos los patrones) × 100
- % de aciertos en las siguientes N barras (ponderado por volumen) = (Volumen de los patrones con resultado favorable en las siguientes N barras ÷ Volumen total de todos los patrones) × 100
- % de rupturas (ponderado por volumen) = (Volumen de las rupturas confirmadas ÷ Volumen total de rupturas) × 100
Ejemplo: Si los 50 pinbars tuvieran en conjunto un volumen total de 10 000 y los 15 con resultado favorable tuvieran un volumen de 3000, entonces 3000 ÷ 10 000 = 0,30 → 0,30 × 100 = 30 % de aciertos en la barra siguiente (ponderado por volumen).
Por último, la herramienta muestra las cifras correspondientes a cada patrón en el panel de control y, si se desea, las guarda en un archivo CSV. Para cada patrón, normalmente verá el número total de apariciones, el % de aciertos en la barra siguiente, el % de aciertos en las siguientes N barras, el recuento de rupturas y el porcentaje de éxito de las rupturas, así como las versiones ponderadas por volumen si las ha activado. Si un patrón nunca se ha producido, la herramienta muestra un 0 % o «N/A» en los porcentajes que no se pueden calcular. El consejo práctico es sencillo: consulte ambas formas de presentar los resultados. Los porcentajes simples le indican la frecuencia con la que ocurrió algo, mientras que los porcentajes ponderados por volumen le indican en qué medida la actividad de negociación respaldó esos resultados.
Implementación
Este asesor experto, el «Candlestick Probability EA», es una herramienta compacta pero con numerosas funciones, diseñada para identificar patrones clave de velas japonesas (pinbar, patrón envolvente, doji), cuantificar su comportamiento histórico y presentar esos resultados directamente en el gráfico y en un archivo. El EA está diseñado para la investigación práctica y la verificación visual rápida: ayuda a los operadores y a los desarrolladores de sistemas a convertir el reconocimiento de patrones en estadísticas cuantificables y repetibles que puedan servir de base para el diseño de estrategias o para la toma de decisiones de negociación manuales.
A continuación, le presento una implementación estructurada en MQL5, que abarca la arquitectura del sistema, los datos de entrada, el modelo estadístico, la visualización y la interfaz de usuario.
Configuración y parámetros de entrada
En la parte superior del archivo hay un conjunto compacto y bien documentado de parámetros de entrada, que permite a los usuarios configurar el EA sin necesidad de editar el código. Estos parámetros abarcan el horizonte de análisis (LookbackBars), la ventana de evaluación posterior (LookAheadBars), los umbrales de ruptura (BreakoutPips, BreakoutConfirmBars), el marco temporal (TF), las opciones de visualización e interfaz de usuario, como ShowPatternMarkers y ShowControlButtons, y las opciones de exportación y ponderación, como ExportCSV y UseVolumeWeighting. Presentar estos controles al principio facilita la experimentación rápida y deja constancia de qué parámetros importan al ajustar o comparar resultados.
//--- Inputs, user-configurable parameters input int LookbackBars = 2000; // how many bars to scan input int LookAheadBars = 5; // how many bars forward to look for follow-up input int BreakoutPips = 5; // breakout threshold in pips input int BreakoutConfirmBars = 1; // bars after breakout used for confirmation input ENUM_TIMEFRAMES TF = PERIOD_CURRENT; input bool ExportCSV = false; input string CSVFileName = "PatternStats.csv"; input bool ShowPatternMarkers = true; input int MaxMarkers = 500; input int MarkerFontSize = 12; input int MarkerOffsetPips = 3; input int MarkerStackWindow = 2; input bool UseVolumeWeighting = true; input bool ShowControlButtons = true;
Modelo de datos de estadísticas de patrones
Las métricas de cada patrón se encapsulan en una única estructura PatternStats, que recoge los recuentos de apariciones, los aciertos en la barra siguiente y dentro de la ventana de evaluación posterior, las rupturas y los aciertos de ruptura confirmada, el volumen total acumulado y los valores equivalentes ponderados por volumen. Organizar las métricas de esta forma permite que la agregación sea modular y facilita la incorporación de nuevas estadísticas o patrones sin necesidad de modificar el bucle de análisis principal.
struct PatternStats { string name; int total; int nextSameDir; int withinLookAheadSame; int breakouts; int breakoutSuccess; double volTotal; double weightedNextSameDir; double weightedWithin; double weightedBreakouts; double weightedBreakoutSuccess; }; // Example declarations PatternStats pinbar, engulfing, dojiStats;
Inicialización, OnInit
Las tareas de inicio se ejecutan de forma determinista en OnInit(); el código pone a cero todas las estadísticas mediante ResetStats(), asigna nombres descriptivos a cada instancia de PatternStats, prepara los contadores de marcadores por barra y, opcionalmente, crea botones de control cuando la interactividad está habilitada. Luego se inicia un temporizador de eventos con un intervalo corto mediante EventSetTimer() y se llama a Analyze() una vez de forma inmediata para que el gráfico muestre los resultados al añadir el EA al gráfico, lo que garantiza un comportamiento reproducible tras cada reinicio y un funcionamiento seguro durante las sesiones en directo.
int OnInit() { // reset stats and name patterns ResetStats(); pinbar.name = "Pinbar"; engulfing.name = "Engulfing"; dojiStats.name = "Doji"; // prepare marker counts buffer ArrayResize(markerCounts, 64); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; // create UI if requested if(ShowControlButtons) CreateControlButtons(); // run periodically to detect new bars EventSetTimer(5); // initial analysis Analyze(); return(INIT_SUCCEEDED); }
Finalización y limpieza, OnDeinit
En OnDeinit() se realiza una limpieza al finalizar: se detiene el temporizador, se eliminan todos los objetos del gráfico creados por el EA —incluidos los marcadores, los botones y los elementos del panel de control— y se fuerza el redibujado del gráfico para eliminar los artefactos visuales. Esta limpieza minuciosa evita que queden elementos gráficos residuales y garantiza que las demás herramientas o plantillas no se vean afectadas al retirar el EA.
void OnDeinit(const int reason) { EventKillTimer(); DeleteAllOurObjects(); // removes only objects with EA prefixes ChartRedraw(); }
Actualizaciones reactivas, OnTimer y OnChartEvent
La capacidad de respuesta del gráfico se mantiene mediante un temporizador ligero y el procesamiento de los eventos del gráfico. OnTimer() detecta la llegada de nuevas barras y activa Analyze() solo cuando es necesario para evitar trabajo redundante, mientras que OnChartEvent() detecta los clics en los botones y activa o desactiva los flujos de detección. Haciendo clic en un control, se actualiza el indicador de activación, se renueva el texto del botón con las métricas actuales y se vuelve a ejecutar el análisis, de modo que las interacciones del usuario se reflejan inmediatamente en las estadísticas de la pantalla.
void OnTimer() { static datetime lastTime = 0; datetime t = iTime(_Symbol, TF, 1); // previous completed bar if(t == lastTime) return; lastTime = t; Analyze(); } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, "Btn_") == 0) { if(StringCompare(sparam, "Btn_Pinbar") == 0) { enabledPinbar = !enabledPinbar; UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); Analyze(); } if(StringCompare(sparam, "Btn_Engulfing") == 0) { enabledEngulfing = !enabledEngulfing; UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); Analyze(); } if(StringCompare(sparam, "Btn_Doji") == 0) { enabledDoji = !enabledDoji; UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); Analyze(); } } }
Funciones de detección de patrones
Cada regla de patrón se implementa como una función auxiliar concisa y parametrizada, como IsPinbar(shift), IsEngulfing(shift) e IsDoji(shift). Estas funciones leen únicamente los campos necesarios de las barras y aplican umbrales configurables, como PinbarBodyPct, PinbarTailPct y DojiBodyPct, devolviendo valores direccionales (+1, −1, 0), lo que simplifica la agregación posterior y permite aplicar una lógica de patrones priorizada o combinada.
int IsPinbar(int shift) { if(IsDoji(shift)) return 0; double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return 0; double body = MathAbs(c - o); if(body > PinbarBodyPct * range) return 0; double upperTail = h - MathMax(o, c); double lowerTail = MathMin(o, c) - l; if(lowerTail >= PinbarTailPct * range && upperTail <= (1.0 - PinbarTailPct) * range) return +1; if(upperTail >= PinbarTailPct * range && lowerTail <= (1.0 - PinbarTailPct) * range) return -1; return 0; } int IsEngulfing(int shift) { int totalBars = iBars(_Symbol, TF); if(totalBars <= shift + 1) return 0; double o1 = iOpen(_Symbol, TF, shift), c1 = iClose(_Symbol, TF, shift); double o2 = iOpen(_Symbol, TF, shift + 1), c2 = iClose(_Symbol, TF, shift + 1); double body1 = c1 - o1, body2 = c2 - o2; if(body1 > 0 && body2 < 0 && o1 <= c2 && c1 >= o2) return +1; if(body1 < 0 && body2 > 0 && o1 >= c2 && c1 <= o2) return -1; return 0; } bool IsDoji(int shift) { double o = iOpen(_Symbol, TF, shift), c = iClose(_Symbol, TF, shift); double h = iHigh(_Symbol, TF, shift), l = iLow(_Symbol, TF, shift); double range = h - l; if(range <= 0) return false; return (MathAbs(c - o) <= DojiBodyPct * range); }
Bucle central de análisis: Analyze
Una única pasada lineal por la ventana retrospectiva configurada constituye el núcleo del EA; el escalado de puntos y pips específico de cada símbolo se calcula una sola vez al inicio, las funciones de detección de patrones se invocan para cada desplazamiento de barra, los patrones detectados se agregan mediante UpdateStatsOnPattern() y, opcionalmente, se dibujan marcadores apilados. Mantener el bucle de una sola pasada garantiza resultados deterministas y un coste de CPU predecible.
void Analyze() { ResetStats(); int totalBars = iBars(_Symbol, TF); if(totalBars < 3) return; int maxIndex = MathMin(totalBars - 1, LookbackBars); if(maxIndex < 2) return; // pip scaling double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); double factor = (digits == 5 || digits == 3) ? 10.0 : 1.0; double breakoutPoints = BreakoutPips * point * factor; ArrayResize(markerCounts, MathMax(3, maxIndex + 3)); for(int i=0;i<ArraySize(markerCounts);i++) markerCounts[i]=0; if(ShowPatternMarkers) DeletePatternObjects(); int drawn=0; for(int shift=2; shift<=maxIndex; shift++) { int eng = IsEngulfing(shift); int pin = IsPinbar(shift); bool doj = IsDoji(shift); bool matched = false; if(eng != 0 && enabledEngulfing) { UpdateStatsOnPattern(engulfing, shift, eng, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Engulfing", eng, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && pin != 0 && enabledPinbar) { UpdateStatsOnPattern(pinbar, shift, pin, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Pinbar", pin, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } matched = true; } if(!matched && doj && enabledDoji) { UpdateStatsOnPattern(dojiStats, shift, 0, LookAheadBars, breakoutPoints); if(ShowPatternMarkers && drawn < MaxMarkers) { DrawPatternOnChartWithStacking(shift, "Doji", 0, MarkerFontSize, MarkerOffsetPips, factor); drawn++; } } } if(ExportCSV) SaveToCSV(); DrawDashboard(); DrawSummaryHeaderAndLabel(); if(ShowControlButtons) { UpdateButtonLabel("Btn_Pinbar", pinbar, enabledPinbar); UpdateButtonLabel("Btn_Engulfing", engulfing, enabledEngulfing); UpdateButtonLabel("Btn_Doji", dojiStats, enabledDoji); } ChartRedraw(); }
Actualización de estadísticas, UpdateStatsOnPattern
El reconocimiento de un patrón activa una rutina de medición exhaustiva que incrementa los contadores de ocurrencias, acumula volumen, analiza la barra inmediatamente siguiente y la ventana de evaluación posterior configurada en busca de movimientos en la misma dirección, detecta rupturas en torno a los extremos del patrón utilizando BreakoutPips y evalúa la confirmación tras BreakoutConfirmBars. Se mantienen tanto los recuentos brutos como los recuentos ponderados por volumen, de modo que los resultados pueden interpretarse bien en función de la frecuencia simple, bien en función de la participación en el mercado.
void UpdateStatsOnPattern(PatternStats &s, int shift, int direction, int lookAhead, double breakoutPoints) { s.total++; double vol = (double)iVolume(_Symbol, TF, shift); if(vol <= 0) vol = 1.0; s.volTotal += vol; // next-bar direction if(shift - 1 >= 0) { double nO = iOpen(_Symbol, TF, shift - 1), nC = iClose(_Symbol, TF, shift - 1); int nDir = (nC > nO) ? +1 : (nC < nO) ? -1 : 0; if(direction == 0) { if(nDir != 0) { s.nextSameDir++; s.weightedNextSameDir += vol; } } else { if(nDir == direction) { s.nextSameDir++; s.weightedNextSameDir += vol; } } } // within lookahead bool anySame = false; double weightedAny = 0.0; int totalBars = iBars(_Symbol, TF); for(int k=1; k<=lookAhead; k++) { int idx = shift - k; if(idx < 1 || idx > totalBars - 1) break; double o = iOpen(_Symbol, TF, idx), c = iClose(_Symbol, TF, idx); double v = (double)iVolume(_Symbol, TF, idx); if(v <= 0) v = 1.0; int d = (c > o) ? +1 : (c < o) ? -1 : 0; if(direction == 0) { if(d != 0) { anySame = true; weightedAny += v; break; } } else { if(d == direction) { anySame = true; weightedAny += v; break; } } } if(anySame) { s.withinLookAheadSame++; s.weightedWithin += (weightedAny > 0.0 ? weightedAny : vol); } // breakout detection and confirmation double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); bool breakout = false; if(shift - 1 >= 0) { double nextHigh = iHigh(_Symbol, TF, shift - 1), nextLow = iLow(_Symbol, TF, shift - 1); if(direction == +1 && nextHigh >= highP + breakoutPoints) breakout = true; else if(direction == -1 && nextLow <= lowP - breakoutPoints) breakout = true; else if(direction == 0 && (nextHigh >= highP + breakoutPoints || nextLow <= lowP - breakoutPoints)) breakout = true; } if(breakout) { s.breakouts++; s.weightedBreakouts += vol; int confirmShift = shift - 1 - BreakoutConfirmBars; if(confirmShift >= 0 && confirmShift <= totalBars - 1) { double cO = iOpen(_Symbol, TF, confirmShift), cC = iClose(_Symbol, TF, confirmShift); double cVol = (double)iVolume(_Symbol, TF, confirmShift); if(cVol <= 0) cVol = 1.0; int cDir = (cC > cO) ? +1 : (cC < cO) ? -1 : 0; bool confirmed = false; if(direction == +1 && cC >= highP + breakoutPoints) confirmed = true; else if(direction == -1 && cC <= lowP - breakoutPoints) confirmed = true; else if(direction == 0 && (cC >= highP + breakoutPoints || cC <= lowP - breakoutPoints)) confirmed = true; if(confirmed && (direction == 0 ? cDir != 0 : cDir == direction)) { s.breakoutSuccess++; s.weightedBreakoutSuccess += cVol; } } } }
Cálculo de porcentajes y exportación a CSV
La conversión de recuentos brutos o sumas ponderadas a porcentajes legibles se realiza de forma centralizada en la función ComputePct(); cuando se activa la opción UseVolumeWeighting, se devuelven porcentajes ponderados por volumen; en caso contrario, se usan porcentajes de frecuencia simples. Cuando ExportCSV tiene el valor true, SaveToCSV() genera un archivo delimitado por tabulaciones con encabezados y una fila por patrón, de modo que el análisis sin conexión en Python, R o herramientas de hojas de cálculo resulta sencillo y reproducible.
double ComputePct(double weightedValue, PatternStats &s, int simpleCount, int total) { if(UseVolumeWeighting) { if(s.volTotal <= 0.0) return 0.0; return 100.0 * weightedValue / s.volTotal; } else { if(total <= 0) return 0.0; return 100.0 * simpleCount / total; } } void SaveToCSV() { int handle = FileOpen(CSVFileName, FILE_WRITE|FILE_CSV|FILE_ANSI, '\t'); if(handle == INVALID_HANDLE) { Print("Failed to open CSV: ", CSVFileName); return; } FileWrite(handle, "Pattern","Total","Next%","Within%","Breakouts","Success%","VolTotal","W_Next%","W_Within%","W_Breakouts%","W_Success%"); WriteStatsLine(handle, pinbar); WriteStatsLine(handle, engulfing); WriteStatsLine(handle, dojiStats); FileClose(handle); } void WriteStatsLine(int handle, PatternStats &s) { double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double withinPct = ComputePct(s.weightedWithin, s, s.withinLookAheadSame, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; double wBreakoutsPct = s.volTotal > 0.0 ? 100.0 * s.weightedBreakouts / s.volTotal : 0.0; double wSuccessPct = s.weightedBreakouts > 0.0 ? 100.0 * s.weightedBreakoutSuccess / s.weightedBreakouts : 0.0; FileWrite(handle, s.name, s.total, DoubleToString(nextPct,1), DoubleToString(withinPct,1), s.breakouts, DoubleToString(successPct,1), DoubleToString(s.volTotal,0), DoubleToString(nextPct,1), DoubleToString(withinPct,1), DoubleToString(wBreakoutsPct,1), DoubleToString(wSuccessPct,1)); }
Visualización en el gráfico, marcadores y apilamiento
Los indicadores visuales se dibujan mediante la función DrawPatternOnChartWithStacking(), que coloca flechas de texto o símbolos compactos en la barra del patrón, apilándolos verticalmente para evitar solapamientos. El apilamiento utiliza el array markerCounts[], que lleva el recuento de marcadores por barra, y el parámetro MarkerStackWindow para controlar la disposición en niveles. Los marcadores están codificados por colores y son deliberadamente discretos, de modo que proporcionan un contexto inmediato sin ocultar la evolución de los precios.
void DrawPatternOnChartWithStacking(int shift, string patternName, int direction, int fontSize, int offsetPips, double factor) { string name = "Pattern_" + patternName + "_" + IntegerToString(shift); if(ObjectFind(0, name) != -1) ObjectDelete(0, name); datetime t = iTime(_Symbol, TF, shift); double highP = iHigh(_Symbol, TF, shift), lowP = iLow(_Symbol, TF, shift); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double offset = offsetPips * point * factor; double priceBase = (highP + lowP) / 2.0; if(direction > 0) priceBase = lowP - offset; else if(direction < 0) priceBase = highP + offset; int idx = shift; if(idx >= ArraySize(markerCounts)) ArrayResize(markerCounts, idx + 5); int stack = markerCounts[idx]++; double stackOffset = (stack / MarkerStackWindow) * offset * 1.2; if(direction > 0) priceBase -= stackOffset; else priceBase += stackOffset; string arrow = (direction>0 ? "▲" : direction<0 ? "▼" : "◆"); string text = arrow + " " + StringSubstr(patternName, 0, 3); ObjectCreate(0, name, OBJ_TEXT, 0, t, priceBase); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, 0); ObjectSetInteger(0, name, OBJPROP_BACK, 1); ObjectSetInteger(0, name, OBJPROP_COLOR, (direction>0 ? clrLime : direction<0 ? clrRed : clrLightBlue)); }
Panel de control y etiquetas de resumen
Un panel compacto integrado en el gráfico, generado por la función DrawDashboard(), muestra columnas estándar como Pattern, Total, Next%, Within%, Breakouts, y Success%, mientras que la función DrawSummaryHeaderAndLabel() crea un resumen en la esquina superior izquierda para una consulta rápida. El uso de prefijos coherentes en los nombres de los objetos permite que el EA elimine de forma fiable estos elementos de la interfaz de usuario cuando ya no sean necesarios.
void DrawDashboard() { string rectName = "Dash_Background"; if(ObjectFind(0, rectName) != -1) ObjectDelete(0, rectName); ObjectCreate(0, rectName, OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, rectName, OBJPROP_CORNER, 0); ObjectSetInteger(0, rectName, OBJPROP_XDISTANCE, 8); ObjectSetInteger(0, rectName, OBJPROP_YDISTANCE, 70); ObjectSetInteger(0, rectName, OBJPROP_XSIZE, 660); ObjectSetInteger(0, rectName, OBJPROP_YSIZE, 80); ObjectSetInteger(0, rectName, OBJPROP_BACK, 1); ObjectSetInteger(0, rectName, OBJPROP_COLOR, clrDarkSlateGray); // header and rows omitted for brevity, see DrawDashboard in main code for full cell logic } void DrawSummaryHeaderAndLabel() { string header = "SummaryHeader"; if(ObjectFind(0, header) != -1) ObjectDelete(0, header); ObjectCreate(0, header, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, header, OBJPROP_CORNER, 0); ObjectSetInteger(0, header, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, header, OBJPROP_YDISTANCE, 12); ObjectSetInteger(0, header, OBJPROP_FONTSIZE, 11); ObjectSetString(0, header, OBJPROP_TEXT, "Candlestick Pattern Stats Summary"); ObjectSetInteger(0, header, OBJPROP_COLOR, clrBlue); string sum = "SummaryLabel"; if(ObjectFind(0, sum) != -1) ObjectDelete(0, sum); ObjectCreate(0, sum, OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, sum, OBJPROP_CORNER, 0); ObjectSetInteger(0, sum, OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, sum, OBJPROP_YDISTANCE, 30); ObjectSetInteger(0, sum, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, sum, OBJPROP_BACK, 1); ObjectSetString(0, sum, OBJPROP_TEXT, FormatStatsForLabel(pinbar) + "\n" + FormatStatsForLabel(engulfing) + "\n" + FormatStatsForLabel(dojiStats)); }
Botones de control y etiquetas dinámicas
Los botones de control de tamaño automático se crean con la función CreateAutoSizedButton() y se actualizan en tiempo real mediante la función UpdateButtonLabel(). Cada botón muestra un identificador breve, el estado activado/desactivado y los valores actuales de las métricas «% de aciertos en la barra siguiente» y «% de éxito». La función EstimateButtonWidth() evita que el texto quede cortado, y los colores de los botones cambian para reflejar si están activados o desactivados, de modo que los usuarios puedan ajustar de forma interactiva qué patrones contribuyen a las estadísticas.
void CreateAutoSizedButton(string name, int x, int y, string text) { if(ObjectFind(0, name) != -1) ObjectDelete(0, name); ObjectCreate(0, name, OBJ_BUTTON, 0, 0, 0); ObjectSetInteger(0, name, OBJPROP_CORNER, 1); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetString(0, name, OBJPROP_FONT, ButtonFont); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, ButtonFontSize); ObjectSetString(0, name, OBJPROP_TEXT, text); int w = EstimateButtonWidth(text, ButtonFontSize); int h = ButtonFontSize + ButtonPaddingY * 2; ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); } void UpdateButtonLabel(string name, PatternStats &s, bool enabled) { if(ObjectFind(0, name) == -1) return; string onoff = enabled ? "ON" : "OFF"; double nextPct = ComputePct(s.weightedNextSameDir, s, s.nextSameDir, s.total); double successPct = s.breakouts > 0 ? 100.0 * s.breakoutSuccess / s.breakouts : 0.0; string shortName = (StringCompare(s.name, "Pinbar") == 0) ? "Pin" : (StringCompare(s.name,"Engulfing")==0) ? "Eng" : "Doj"; string txt = shortName + ": " + onoff + " Next% " + DoubleToString(nextPct,1) + " Succ% " + DoubleToString(successPct,1); ObjectSetString(0, name, OBJPROP_TEXT, txt); ObjectSetInteger(0, name, OBJPROP_XSIZE, EstimateButtonWidth(txt, ButtonFontSize)); if(enabled) { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrWhite); ObjectSetInteger(0, name, OBJPROP_COLOR, clrBlack); } else { ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clrLightGray); ObjectSetInteger(0, name, OBJPROP_COLOR, clrDimGray); } } int EstimateButtonWidth(const string txt, const int fontSize) { int len = StringLen(txt); double charW = fontSize * 0.75; int base = (int)MathCeil(len * charW) + ButtonPaddingX * 2 + 10; if(base < 100) base = 100; return base; }
Limpieza y gestión de objetos
Cada objeto del gráfico creado por el EA lleva un prefijo determinista, como OBJ_PREFIX_PATTERN, OBJ_PREFIX_DASH u OBJ_PREFIX_BTN. Las funciones de eliminación asociadas solo eliminan los objetos que coinciden con esos prefijos, lo que minimiza el riesgo de eliminar accidentalmente objetos del usuario no relacionados y mantiene la huella del EA predecible y no invasiva.
void DeleteAllOurObjects() { int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string nm = ObjectName(0, i); if(StringFind(nm, "Pattern_") == 0 || StringFind(nm, "Dash_") == 0 || StringFind(nm, "Btn_") == 0 || StringCompare(nm, "SummaryLabel") == 0 || StringCompare(nm, "SummaryHeader") == 0) ObjectDelete(0, nm); } }
Pruebas
En esta sección, evaluamos el EA en el índice Step y el índice Boom 300, seleccionados para comprobar la solidez de la herramienta en instrumentos sintéticos con perfiles de volatilidad contrastados.
- Índice Step

El diagrama anterior ilustra el rendimiento de la herramienta en el índice Step. Cuando se añade el EA al gráfico, se crea un panel de control compacto en la esquina superior izquierda que resume las estadísticas de los patrones, así como un conjunto de botones de activación/desactivación en la esquina inferior izquierda que controlan la visualización en el gráfico. Al hacer clic en un botón, se desactiva o se activa la secuencia de patrones correspondiente, lo que elimina o restaura las etiquetas de las velas para que pueda recorrer el gráfico y comprobar visualmente la identificación de los patrones. Los controles de alternancia resultan útiles cuando usted quiere centrar la atención en un único tipo de patrón.
En el ejemplo que se muestra, el panel de control indica unas tasas de éxito del 40,2 % para el pinbar y del 31,7 % para el patrón envolvente. El Doji muestra un 65,1 %; sin embargo, en este análisis consideramos el Doji como una señal neutra, ya que su aparente fuerza se debe principalmente al filtrado de las rupturas, más que a un sesgo direccional. Por consiguiente, el resultado del Doji se interpreta mejor como un indicio de una tendencia a resolverse mediante movimientos de continuación o de ruptura en este instrumento, más que como una simple señal alcista o bajista.
Los resultados también se exportan a un archivo CSV para su verificación independiente. Para generar los archivos, abra los parámetros de entrada del EA y establezca ExportCSV = true; el nombre del archivo de resumen se puede modificar mediante CSVFileName. El EA guardará el archivo CSV con el resumen en la carpeta Files de su terminal para que pueda analizarlo sin conexión.
input bool ExportCSV = true; input string CSVFileName = "PatternStats.csv";
- Índice Boom 300

En el índice Boom 300, las tasas de éxito registradas son del 40,0 % para el patrón envolvente, del 42,1 % para el pinbar y del 78,6 % para el Doji. Dichas cifras indican que, según las reglas del EA, es mucho más probable que las apariciones de Doji en este instrumento vayan seguidas de movimientos de continuación o de ruptura, mientras que el patrón envolvente y el pinbar muestran un comportamiento direccional más moderado.
Al comparar el índice Boom 300 con el índice Step, el rendimiento del pinbar es prácticamente idéntico en ambos instrumentos, lo que sugiere un cierto grado de solidez de dicho patrón. Sin embargo, el patrón envolvente ofrece un rendimiento notablemente mejor en el índice Boom 300 que en el índice Step; esto implica que sus señales generan una mayor continuación del precio en la dirección indicada por el patrón en el índice Boom 300, con los parámetros del EA y durante el periodo de la muestra analizada.
Conclusión
En este artículo hemos explicado paso a paso cómo crear un asesor experto en MQL5 que mida las probabilidades de los patrones de velas japonesas en varios instrumentos; hemos mostrado cómo detectar formaciones de pinbar, patrón envolvente y doji; hemos recopilado las estadísticas por patrón y hemos presentado las tasas de éxito para que usted pueda evaluar qué patrones generan una continuación del precio en la dirección indicada por el patrón en cada par. Presentamos el EA como una herramienta de análisis experimental para visualizar los patrones de evolución de los precios y sus probabilidades empíricas. Está concebido como una herramienta de ayuda para la investigación y la verificación, más que como un robot de trading «llave en mano», y los usuarios deben configurar sus parámetros de entrada y aplicar sus procedimientos de validación para adaptarlo a sus marcos temporales e instrumentos.
Cuando se demuestra su solidez con muestras suficientemente amplias, este enfoque ayuda a establecer expectativas realistas sobre el comportamiento habitual de un patrón determinado en operaciones reales; además, permite realizar pruebas reproducibles mediante los archivos de auditoría en formato CSV. De cara al futuro, tengo previsto mejorar la herramienta en próximas entregas, y agradezco cualquier sugerencia de mejora o solicitud de nuevas funciones de análisis.
Lea mis otros artículos.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19738
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.