English Русский Deutsch 日本語
preview
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

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

MetaTrader 5 — Ejemplos |
21 0
Christian Benjamin
Christian Benjamin

Í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.

Todos los resultados de «sí» o «no» y los volúmenes se suman por separado para los pinbars, los patrones envolventes y los Dojis. Para cada patrón, la herramienta registra el número total de apariciones, cuántas tuvieron un resultado favorable en la barra siguiente, cuántas tuvieron un resultado favorable en las siguientes N barras, cuántas rupturas se produjeron y cuántas de ellas se confirmaron, además de la actividad de negociación acumulada de las barras en las que apareció el patrón y de aquellas cuyas apariciones tuvieron un resultado favorable. Mantener estos totales por separado permite evaluar cómo se comporta cada patrón en el símbolo y el marco temporal concretos que usted está analizando.
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
Ejemplo: Si la herramienta ha detectado 50 pinbars y la barra siguiente se ha movido tal y como se esperaba en 15 ocasiones, entonces 15 ÷ 50 = 0,30 → 0,30 × 100 = 30 % de aciertos en la barra siguiente. A veces resulta útil ponderar los resultados en función de la actividad de negociación, en lugar de basarse en los recuentos sin ponderar. En ese caso, la herramienta suma los volúmenes en lugar del número de apariciones. Las fórmulas son:
  • % 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

Archivos adjuntos |
Utilizando redes neuronales en MetaTrader Utilizando redes neuronales en MetaTrader
En el artículo se muestra la aplicación de las redes neuronales en los programas de MQL, usando la biblioteca de libre difusión FANN. Usando como ejemplo una estrategia que utiliza el indicador MACD se ha construido un experto que usa el filtrado con red neuronal de las operaciones. Dicho filtrado ha mejorado las características del sistema comercial.
Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 42): Pruebas interactivas en gráficos con lógica basada en botones y niveles estadísticos Desarrollo de un kit de herramientas para el análisis de la acción del precio (Parte 42): Pruebas interactivas en gráficos con lógica basada en botones y niveles estadísticos
En un mundo en el que la rapidez y la precisión son fundamentales, las herramientas de análisis deben ser tan inteligentes como los mercados en los que operamos. Este artículo presenta un asesor experto (EA) basado en la lógica basada en botones: un sistema interactivo que transforma al instante los datos brutos de precios en niveles estadísticos significativos. Con un solo clic, calcula y muestra la media, la desviación estándar, los percentiles y mucho más, convirtiendo los análisis avanzados en señales claras directamente en el gráfico. Destaca las zonas en las que es más probable que el precio rebote, retroceda o rompa un nivel, lo que hace que el análisis sea más rápido y práctico.
Particularidades del trabajo con números del tipo double en MQL4 Particularidades del trabajo con números del tipo double en MQL4
En estos apuntes hemos reunido consejos para resolver los errores más frecuentes al trabajar con números del tipo double en los programas en MQL4.
Algoritmo de búsqueda basado en cuervos — Crow Search Algorithm (CSA) Algoritmo de búsqueda basado en cuervos — Crow Search Algorithm (CSA)
El algoritmo de búsqueda del cuervo (CSA) es una elegante metaheurística, inspirada en la capacidad de los cuervos para esconder comida y encontrar los escondites ajenos, que resuelve problemas de optimización mediante el equilibrio entre el seguimiento de soluciones exitosas y la exploración aleatoria del espacio de búsqueda. Vamos a averiguar qué rendimiento tiene el algoritmo.