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

MetaTrader 5 — Ejemplos |
26 0
Christian Benjamin
Christian Benjamin

Contenido



Introducción

Bienvenidos a la siguiente entrega de la serie Desarrollo del kit de herramientas para el análisis de la acción del precio. Nuestro objetivo es automatizar el análisis de la acción del precio y hacerlo intuitivo y accesible para los operadores que se basan en la estructura del precio en lugar de en indicadores de «caja negra». En este artículo ampliamos el trabajo anterior, en el que se introdujeron métricas estadísticas como la media, la desviación estándar, la mediana y otros niveles de la distribución calculados a partir de los precios típicos de las velas, y mostramos cómo estas métricas se corresponden de forma natural con importantes niveles de referencia del mercado, como los niveles de soporte y resistencia y los puntos pivote.

En lugar de codificar los parámetros como valores fijos y ajustar manualmente los valores de entrada, presentamos ahora un enfoque más interactivo, directamente en el gráfico: el Panel estadístico. Este panel de control muestra los botones de control y los campos editables directamente en el gráfico, de modo que los usuarios pueden calcular y visualizar niveles estadísticos cuando lo deseen. Aunque se mantienen muchas de las funciones de la implementación anterior, este diseño supone un enfoque más sofisticado y centrado en el usuario para el análisis directamente en el gráfico.

En este artículo vamos a:

  • Explicar la lógica y la razón de ser del Panel estadístico.
  • Repasar los aspectos más destacados de la implementación en MQL5.
  • Compartir ejemplos de resultados y patrones de uso.
  • Resumir las conclusiones y los próximos pasos.


Comprender el concepto

Las medidas estadísticas calculadas a partir de los datos de precios, como la media, la mediana, los percentiles y las modas basadas en la densidad, reflejan la tendencia central y la forma de la distribución de la acción del precio reciente. Estas métricas suelen coincidir con zonas clave de reacción del mercado: zonas en las que se concentra la liquidez, a las que el precio tiende a volver o en las que se originan las rupturas y los giros. Considerar estas estadísticas como niveles de referencia permite adoptar tanto enfoques discrecionales como sistemáticos. Son fáciles de interpretar visualmente y adecuados para la supervisión automatizada. Puedes obtener más información sobre métricas como la media, la desviación estándar y la mediana en el artículo anterior, donde las expliqué en profundidad.

La lógica de esta herramienta se basa en controles mediante botones que simplifican el análisis de mercado. El panel convierte las entradas del desarrollador en una interfaz de usuario ligera, situada directamente en el gráfico, que permite a los operadores:

  • Seleccionar un intervalo de fecha y hora (directamente en el gráfico o introduciéndolo manualmente).
  • Calcular las estadísticas de distribución en el intervalo seleccionado.
  • Trazar líneas correspondientes a la media, las bandas de desviación estándar, los percentiles, la mediana y las modas.
  • Crear una instantánea de los niveles de referencia para su supervisión en tiempo real (detección de toques, rupturas y giros).
  • Exportar los datos de la instantánea a un archivo CSV.
  • Restablecer rápidamente el panel a su estado inicial.

Esta experiencia de usuario directamente en el gráfico elimina las dudas sobre qué ajustes utilizar y agiliza los flujos de trabajo de análisis. Además, hace que la herramienta sea más segura y fácil de usar para operar en tiempo real: en lugar de tener que modificar continuamente el código, los operadores pueden calcular y comprobar los resultados en cuestión de segundos.

Diagrama de flujo

El diagrama muestra el sencillo modelo de interacción del panel: un solo clic en un botón desencadena una de tres acciones claramente definidas (Reset, Calculate, Toggle). Cada acción realiza operaciones deterministas (borrar datos, calcular y trazar estadísticas o alternar la visibilidad), actualiza el gráfico de inmediato y, a continuación, devuelve el asesor experto (EA) a un estado inactivo. Este flujo de trabajo directamente en el gráfico elimina la necesidad de editar código para los análisis rutinarios, agiliza la toma de decisiones y reduce el riesgo de dejar objetos o variables globales obsoletos en el terminal.


Implementación

En primer lugar, comenzamos por establecer los metadatos básicos de nuestro indicador. Incluimos comentarios en el encabezado que especifican el nombre del archivo, el autor y los datos de derechos de autor, lo que ayuda a identificar el script y a atribuir su propiedad. En cuanto a las directivas específicas de la plataforma, establecemos propiedades como #property copyright, #property version y #property strict. Estas directivas garantizan que MetaTrader reconozca correctamente nuestro script, aplique reglas sintácticas estrictas para una programación más segura y disponga de información de versión esencial para el mantenimiento. Esta configuración inicial es fundamental, ya que garantiza que nuestro código se integre a la perfección con MetaTrader y se ajuste a las mejores prácticas.

//+------------------------------------------------------------------+
//|                                         Statistical Dashboard.mq5|
//|                               Copyright 2025, Christian Benjamin.|
//|                           https://www.mql5.com/en/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "https://www.mql5.com/en/users/lynnchris"
#property version   "1.36"
#property strict

A continuación, incorporamos bibliotecas externas para ampliar las capacidades de nuestro script. Incluimos el archivo ArrayObj.mqh, que ofrece funciones avanzadas de gestión de arrays. De este modo, podemos gestionar de forma eficiente colecciones dinámicas de objetos y datos, lo cual es un aspecto importante a la hora de trabajar con múltiples niveles, señales y componentes de la interfaz de usuario. Este enfoque modular nos permite mantener nuestro código limpio y escalable, sobre todo a medida que vamos añadiendo funcionalidades como objetos gráficos o variables globales.

// Include array utility library
#include <Arrays/ArrayObj.mqh>

A continuación, definimos los parámetros de entrada del usuario que permiten a los operadores personalizar el comportamiento del indicador. Entre estas variables de entrada se incluyen los períodos retrospectivos, los umbrales de señal, las preferencias visuales y los controles de activación/desactivación. Por ejemplo, Lookback determina cuántas barras analizamos a la vez, mientras que ZScoreSignalEnter controla la sensibilidad ante las desviaciones del mercado. Al poner estos parámetros a disposición de los operadores, les permitimos adaptar el indicador a diferentes mercados, marcos temporales y estrategias personales, lo que hace que nuestra implementación sea flexible y fácil de usar.

// === Inputs ===
input int    Lookback               = 1000;
input bool   ExcludeCurrent         = true;
input bool   UseWeightedByVol       = true;
input int    ModeBins               = 30;
input int    KDEGridPoints          = 100;
input double KDEBandwidthFactor     = 1.0;
input int    RefreshEveryXTicks     = 1;
input double ZScoreSignalEnter      = 2.0;
input double ZScoreSignalExit       = 0.8;
input bool   AllowLongSignals       = true;
input bool   AllowShortSignals      = true;
input bool   SendAlertOnSignal      = false;
input bool   PlaySoundOnSignal      = false;
input string SoundFileOnSignal      = "alert.wav";
input bool   SendPushOnSignal       = false;
input ENUM_TIMEFRAMES TF           = PERIOD_CURRENT;
input int    TimerIntervalSeconds   = 60;
input int    CleanupIntervalSeconds = 3600;
input bool     AutoSnapshotLevels   = false;
input datetime InputRefStart        = 0;
input datetime InputRefEnd          = 0;
input int      MonitorBars          = 20;
input double   TouchTolerancePips   = 3.0;
input double   BreakoutPips         = 5.0;
input double   ReversalPips         = 5.0;
input bool     UseCloseForConfirm   = true;
input bool     UseATRforThresholds  = true;
input double   ATRMultiplier        = 0.5;
input int      ATRperiod            = 14;
input bool     ClearSnapshotOnStart = false;
input int LabelOffset_Mean_Sec   = 0;
input int LabelOffset_Median_Sec = -60;
input int LabelOffset_ModeB_Sec  = -120;
input int LabelOffset_ModeK_Sec  = -180;
input int LabelOffset_Pct_Sec    = -240;
input bool DebugMode = false;
input string InputRefStartStr = "";
input string InputRefEndStr   = "";

A medida que avanzamos, definimos variables de estado internas para realizar un seguimiento del estado actual del indicador. Variables como awaitingSetStart y refStartChart contienen información sobre las interacciones del usuario —por ejemplo, si está estableciendo puntos de referencia— y almacenan marcas de tiempo de referencia. Variables como currentSignal indican si el sistema señala actualmente una posición larga, corta o neutral. Estas variables internas son fundamentales, ya que permiten que nuestro script mantenga el contexto entre los distintos ticks y los comandos del usuario, lo que garantiza un comportamiento coherente y ágil.

// Internal variables to track state
bool awaitingSetStart = false;
bool awaitingSetEnd = false;
datetime refStartChart = 0;
datetime refEndChart = 0;
int currentSignal = 0; // 1 for long, -1 for short, 0 for neutral

Para gestionar varios niveles de referencia, definimos un tipo de datos estructurado denominado RefLevel. Esta estructura recoge datos como el nombre del nivel, su precio, si la cotización lo ha tocado, el número de toques, los precios máximo y mínimo observados y otras métricas. A continuación, creamos un array refLevels[] para almacenar varias instancias de este tipo, lo que nos permite supervisar y analizar varios niveles simultáneamente. El uso de un enfoque estructurado como este aporta claridad y escalabilidad: a medida que los operadores añaden más niveles, nuestro código puede gestionarlos de forma sistemática y eficiente.

struct RefLevel
{
  string name;
  double price;
  bool touched;
  datetime touchTime;
  int touchCount;
  double highest;
  double lowest;
  double avgTouchVol;
  int recentTouches;
  double persistence;
  int result; // -1, 0, 1
  datetime resolvedTime;
};
RefLevel refLevels[]; // Array to hold multiple levels

A lo largo de nuestra implementación, declaramos numerosas funciones para modularizar nuestra lógica. Por ejemplo, disponemos de funciones para crear botones, etiquetas y objetos gráficos (CreateButton(), CreateHLine_Pro(), DrawArrowAt()), exportar datos (ExportSnapshotCSV()) y realizar cálculos estadísticos (ComputeLevelScore(), Median(), Variance()). Este diseño modular es fundamental, ya que separa la gestión de la interfaz de usuario de las rutinas de análisis, lo que hace que nuestro código sea más fácil de entender, depurar y ampliar. Además, nos permite reutilizar fragmentos de código en diferentes partes del script.

// Forward declarations
void CreateToolbar();
void DeleteToolbar();
void CreateButton(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text);
void CreateButtonStatLabel(string labName,int corner,int xdist,int ydist,string text);
void CreateEditField(string name,int corner,int xdist,int ydist,int xsize,int ysize,string text);
void ExportSnapshotCSV();
void CreateHLine_Pro(string name,double price,double score,string friendlyLabel);
double ComputeLevelScore(int touchCount,double avgTouchVolume,int recentTouches,double persistenceBars,datetime lastTouchTime);
void DrawArrowAt(string name, datetime when, double price, bool isBuy);
void CreatePanel();
void CreateOrUpdateLineText(string name, datetime t, double price, string text);
void RemoveOldObjects(int ageSec);
void ClearSnapshot();
void ClearSnapshotVisuals();
void SnapshotReferenceLevels(double mean_val,double p25,double p75,double median_val,double mode_b,double mode_k,double stddev);
void MonitorReferenceLevels(const MqlRates &rates[], int copied);
double pipToPointMultiplier();
void DeleteObjectIfExists(string name);
void RemoveHistogramObjects();
void SetObjTimestamp(string name);
datetime GetObjTimestamp(string name);
void CleanupMetaForObject(string name);
void CleanupAllMetaGlobals();
double Mean(const double &a[], int n);
double WeightedMean(const double &a[], const double &w[], int n);
double WeightedMeanFromRates(const MqlRates &rates[], int copied);
double Variance(const double &a[], int n, bool sample);
double Median(const double &a[], int n);
double Percentile(const double &a[], int n, double q);
double ModeBinned(const double &a[], int n, int bins);
double ModeKDE(const double &a[], int n, int gridPts, double bwFactor);
double ArrayMin(const double &a[], int n);
double ArrayMax(const double &a[], int n);
bool ComputeStatsFromGlobals(double &mean,double &stddev,double &median,double &modeb,double &modek,double &p25,double &p75,double &zscore);

A continuación, desarrollamos funciones de utilidad que simplifican las tareas habituales. Por ejemplo, UpdateLabelText() actualiza dinámicamente las etiquetas de la interfaz de usuario, TrimString() depura las cadenas introducidas por el usuario y pipToPointMultiplier() convierte pips en puntos específicos de la plataforma para realizar cálculos precisos. Estas funciones auxiliares mejoran la solidez del código, evitan la redundancia y garantizan un tratamiento coherente de los datos, características que resultan esenciales para un indicador de nivel profesional.

// Example: update label text
void UpdateLabelText(string name, string text)
{
  if(ObjectFind(0, name) >= 0)
    ObjectSetString(0, name, OBJPROP_TEXT, text);
  else
    CreateButtonStatLabel(name, 0, 0, 0, text);
}

// Example: trim strings
string TrimString(string s)
{
  // Implementation omitted for brevity
}

A continuación, implementamos una rutina de reinicio completa llamada ResetAll(). Esta función borra las instantáneas, elimina todos los objetos gráficos y los metadatos asociados, restablece las variables internas y reconstruye la interfaz de usuario. Es una función fundamental, ya que permite a los usuarios reiniciar el análisis desde cero sin necesidad de reiniciar MetaTrader, especialmente tras modificar los parámetros o al encontrarse con estados inesperados. Esto mejora la experiencia del usuario y mantiene la integridad de los análisis en curso.

void ResetAll()
{
  // Clear snapshots, delete objects, reset globals, rebuild UI
  ClearSnapshot();
  RemoveExistingEAObjects();
  CleanupAllMetaGlobals();
  RemoveHistogramObjects();
  DeleteToolbar();
  
  // Reset internal variables
  currentSignal = 0;
  refStartChart = 0;
  refEndChart = 0;
  refSnapshotTaken = false;
  snapshotTakenTime = 0;
  
  // Recreate UI
  CreatePanel();
  CreateToolbar();
}

En la función OnInit(), inicializamos el indicador cuando se carga. Aquí generamos identificadores únicos basados en el símbolo y el marco temporal, creamos paneles visuales y barras de herramientas, configuramos temporizadores para la limpieza periódica e inicializamos puntos de referencia en función de los datos introducidos por el usuario o de las anotaciones en el gráfico. Esta fase de configuración garantiza que todos los recursos necesarios —componentes de la interfaz de usuario, variables globales y estados internos— estén correctamente configurados antes de que comience el procesamiento de datos en tiempo real. Una inicialización adecuada es fundamental para evitar errores y garantizar un funcionamiento fluido.

int OnInit()
{
  // Set base strings for global variables
  S_base = StringFormat("CSTATS_%s_%d", _Symbol, (int)TF);
  CreatePanel();
  if(ClearSnapshotOnStart) ClearSnapshot();
  EventSetTimer(TimerIntervalSeconds);
  CreateToolbar();
  // Additional setup...
  return INIT_SUCCEEDED;
}

Por su parte, la función OnDeinit() se encarga de la limpieza cuando se elimina el indicador. Desactiva los temporizadores, elimina los objetos gráficos, borra las variables globales y restablece el entorno. Este paso es fundamental, ya que evita las fugas de recursos y la saturación del gráfico, y garantiza que los indicadores o scripts posteriores funcionen sin interferencias. Mantiene el buen funcionamiento y el rendimiento general del entorno de negociación.

void OnDeinit(const int reason)
{
  EventKillTimer();
  // Delete graphical objects
  DeleteObjectIfExists(S_mean);
  DeleteObjectIfExists(S_panel);
  // Clear global variables
  CleanupAllMetaGlobals();
}

A continuación, implementamos un controlador de temporizador con OnTimer(), que se ejecuta a intervalos especificados. Elimina los objetos gráficos obsoletos y las instantáneas caducadas en función de su antigüedad, lo que mantiene el gráfico ordenado y garantiza que los datos mostrados sigan siendo relevantes. Esta limpieza periódica garantiza la claridad y el rendimiento, sobre todo durante sesiones de negociación prolongadas con un flujo continuo de datos.

void OnTimer()
{
  // Periodic cleanup of old objects
  RemoveOldObjects(CleanupIntervalSeconds);
  // Clear expired snapshots
  if(refSnapshotTaken && snapshotTakenTime > 0 && (TimeCurrent() - snapshotTakenTime) >= CleanupIntervalSeconds)
  {
    ClearSnapshot();
  }
}

El núcleo de nuestro análisis en tiempo real reside en OnTick(). Cada vez que llega un tick de mercado, esta función comprueba si el indicador está en pausa o si debe omitir el procesamiento en función de las frecuencias de actualización. A continuación, recopila datos recientes del mercado mediante la función GetRatesForSelection(), que obtiene precios históricos dentro de los rangos o períodos retrospectivos definidos por el usuario. A partir de estos datos, calculamos medidas estadísticas —media, mediana, moda y desviación estándar— mediante funciones como ComputeStatsFromRates(). Estos indicadores constituyen la base para identificar los regímenes del mercado, las desviaciones y las posibles señales.

A continuación, calculamos una puntuación z, que representa en qué medida el último precio se desvía de la media, y que sirve como desencadenante de las señales. En función de los umbrales, actualizamos el estado actual de la señal y marcamos visualmente las señales con flechas, lo que proporciona a los operadores indicaciones visuales inmediatas sobre las condiciones del mercado.

void OnTick()
{
  if(GlobalVariableCheck(S_base + "_PAUSED") && GlobalVariableGet(S_base + "_PAUSED") == 1.0)
    return; // Paused
  
  // Throttle refresh rate
  tick_count++;
  if(tick_count < RefreshEveryXTicks) return;
  tick_count = 0;
  
  // Gather data
  MqlRates rates[]; int copied=0;
  if(!GetRatesForSelection(rates, copied))
    return;
  
  // Compute stats
  double mean_val, stddev, median_val, mode_b, mode_k, p25, p75;
  if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75))
    return;
  
  // Calculate z-score
  double latest = (rates[0].high + rates[0].low + rates[0].close) / 3.0;
  double zscore = (stddev > 0) ? (latest - mean_val) / stddev : 0;
  
  // Store global variables
  GlobalVariableSet(S_base + "_mean", mean_val);
  GlobalVariableSet(S_base + "_zscore", zscore);
  
  // Generate signals
  int newSignal = 0;
  if(zscore >= ZScoreSignalEnter && AllowLongSignals)
    newSignal = 1;
  else if(zscore <= -ZScoreSignalEnter && AllowShortSignals)
    newSignal = -1;
  
  // Update visual signals
  if(newSignal != currentSignal)
  {
    if(newSignal == 1)
      DrawArrowAt(S_arrow_long, iTime(_Symbol, TF, 0), latest, true);
    else if(newSignal == -1)
      DrawArrowAt(S_arrow_short, iTime(_Symbol, TF, 0), latest, false);
    currentSignal = newSignal;
  }
}

Para facilitar el análisis de datos, creamos funciones como GetRatesForSelection() y ComputeStatsFromRates(). La primera recupera los datos de mercado pertinentes, teniendo en cuenta los intervalos de fechas o los períodos retrospectivos especificados por el usuario, lo que garantiza que el análisis se centre en el ámbito de interés del operador. La segunda realiza cálculos estadísticos —media, mediana y modas— mediante algoritmos robustos, como la ordenación de arrays y el cálculo de percentiles. Estas funciones constituyen la columna vertebral de la capacidad analítica del indicador, ya que transforman los datos brutos del mercado en información relevante para la toma de decisiones.

bool GetRatesForSelection(MqlRates &rates[], int &copied)
{
  // Fetch data based on date range or lookback
  if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0)
  {
    int shiftStart = iBarShift(_Symbol, TF, refEndChart, false);
    int shiftEnd = iBarShift(_Symbol, TF, refStartChart, false);
    int startShift = MathMin(shiftStart, shiftEnd);
    int endShift = MathMax(shiftStart, shiftEnd);
    int count = endShift - startShift + 1;
    ArrayResize(rates, count);
    copied = CopyRates(_Symbol, TF, startShift, count, rates);
    return copied > 0;
  }
  else
  {
    int startShift = ExcludeCurrent ? 1 : 0;
    int needed = Lookback;
    ArrayResize(rates, needed);
    copied = CopyRates(_Symbol, TF, startShift, needed, rates);
    return copied > 0;
  }
}

La interactividad es un aspecto clave; creamos funciones para gestionar las entradas y las interacciones de los usuarios. Por ejemplo, las funciones CreateButton(), CreateEditField() y CreateToolbar() generan controles de interfaz de usuario en el gráfico, lo que permite a los operadores ajustar parámetros o activar acciones. La función OnChartEvent() procesa los clics del usuario, las pulsaciones de botones y las modificaciones de objetos, actualizando las variables internas o los puntos de referencia según corresponda. Este diseño hace que el indicador sea muy adaptable, lo que permite a los operadores personalizar los parámetros de análisis sobre la marcha, algo esencial en entornos de negociación dinámicos.

bool ComputeStatsFromRates(const MqlRates &rates[], int copied, double &mean, double &stddev, double &median, double &modeb, double &modek, double &p25, double &p75)
{
  // Extract data
  double vals[]; ArrayResize(vals, copied);
  for(int i=0; i<copied; i++)
  {
    double tp = (rates[i].high + rates[i].low + rates[i].close) / 3.0;
    vals[i] = tp;
  }
  // Compute measures
  mean = Mean(vals, copied);
  stddev = MathSqrt(Variance(vals, copied, true));
  median = Median(vals, copied);
  p25 = Percentile(vals, copied, 0.25);
  p75 = Percentile(vals, copied, 0.75);
  modeb = ModeBinned(vals, copied, ModeBins);
  modek = ModeKDE(vals, copied, KDEGridPoints, KDEBandwidthFactor);
  return true;
}

Al hacer clic en el botón «Mean», la función OnChartEvent() detecta esta acción a través del evento CHARTEVENT_OBJECT_CLICK. A continuación, el código comprueba el nombre del objeto para determinar que el objeto sobre el que se ha hecho clic es, efectivamente, el botón «Mean». Tras la confirmación, el asesor experto (EA) procede a muestrear el rango de datos de mercado pertinente llamando a la función GetRatesForSelection(). Si la recuperación de datos falla —es decir, si no hay datos disponibles o se produce un error—, el script actualiza la etiqueta correspondiente para informar al usuario de que no se han encontrado datos y, a continuación, finaliza.

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id != CHARTEVENT_OBJECT_CLICK)
      return;

   string objName = sparam;

   if(objName == S_base + "_BTN_SHOWMEAN")
   {
      // Step 1: Sample data for the selected range
      MqlRates rates[];
      int copied=0;
      if(!GetRatesForSelection(rates, copied) || copied <= 0)
      {
         // Failure: No data available
         UpdateLabelText(S_base + "_LBL_BTN_MEAN", "No data for selection");
         return;
      }

      // Step 2: Compute statistics (mean, stddev, etc.)
      double mean_val, stddev, median_val, mode_b, mode_k, p25, p75;
      if(!ComputeStatsFromRates(rates, copied, mean_val, stddev, median_val, mode_b, mode_k, p25, p75))
      {
         // Failure: Calculation failed
         UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Compute failed");
         return;
      }

      // Step 3: Draw horizontal line at mean
      CreateHLine_Pro(S_mean, mean_val, 0.85, "Mean");

      // Step 4: Update label with computed mean
      string rangeTxt = "";
      if(UseDateRangeOnChart && refStartChart > 0 && refEndChart > 0)
         rangeTxt = StringFormat("%s -> %s", TimeToString(refStartChart, TIME_DATE|TIME_MINUTES), TimeToString(refEndChart, TIME_DATE|TIME_MINUTES));
      else
         rangeTxt = StringFormat("Lookback %d bars", copied);

      CreateOrUpdateLineText(S_mean + "_TXT", iTime(_Symbol, TF, 0), mean_val, StringFormat("Mean: %s | %s | N=%d", DoubleToString(mean_val, _Digits), rangeTxt, copied));
      UpdateLabelText(S_base + "_LBL_BTN_MEAN", "Mean: " + DoubleToString(mean_val, _Digits));

      // End of process for button press
      return;
   }

   // Similar structure applies for other buttons like Std, Mode, Draw Levels, etc.
}

Si la recuperación de datos se realiza correctamente, el asesor experto (EA) pasa a calcular la media pasando los datos muestreados a la función ComputeStatsFromRates(). Si el cálculo falla por cualquier motivo, la etiqueta se actualiza para indicar el fallo y el proceso finaliza. De lo contrario, una vez realizado correctamente el cálculo, el asesor experto (EA) traza una línea horizontal en el nivel de la media calculada mediante la función CreateHLine_Pro(), personalizando su aspecto y su etiqueta para mayor claridad. Al mismo tiempo, el script actualiza la etiqueta estadística situada junto al botón con el nuevo valor de la media, lo que proporciona una respuesta visual inmediata. Una vez completados estos pasos, el proceso finaliza y el asesor experto (EA) queda listo para la siguiente interacción del usuario. Este flujo garantiza que cada vez que se pulsa un botón se active una secuencia de muestreo de datos, análisis y actualizaciones visuales, lo que hace que la herramienta sea interactiva e informativa.

Lógica basada en botones

Los elementos visuales son fundamentales para una interpretación rápida; implementamos funciones como CreateHLine_Pro(), DrawArrowAt() y CreateOrUpdateLineText(). Estas funciones trazan líneas horizontales que indican niveles estadísticos, como la media o la mediana; dibujan flechas que señalan rupturas o giros; y muestran anotaciones de texto con información detallada.

void CreateHLine_Pro(string name, double price, double score, string friendlyLabel)
{
  int width = 1 + (int)MathRound(score * 3.0);
  color col = clrDodgerBlue;
  // Determine color/style based on label
  if(ObjectFind(0, name) >= 0)
  {
    ObjectSetDouble(0, name, OBJPROP_PRICE, price);
    ObjectSetInteger(0, name, OBJPROP_COLOR, col);
    ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
    ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
  }
  else
  {
    ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
    ObjectSetDouble(0, name, OBJPROP_PRICE, price);
    ObjectSetInteger(0, name, OBJPROP_COLOR, col);
    ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
    ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
  }
  // Add label
  CreateOrUpdateLineText(name + "_TXT", iTime(_Symbol, TF, 0), price, friendlyLabel);
}

Estos marcadores visuales ayudan a los operadores a reconocer al instante los niveles o señales clave sin tener que analizar minuciosamente las cifras, lo que mejora la rapidez y la precisión en la toma de decisiones.

void DrawArrowAt(string name, datetime when, double price, bool isBuy)
{
  if(ObjectFind(0, name) >= 0)
    ObjectDelete(0, name);
  color col = isBuy ? clrLime : clrMaroon;
  int arrowCode = isBuy ? 233 : 234; // Up or down arrow
  ObjectCreate(0, name, OBJ_ARROW, 0, when, price);
  ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
  ObjectSetInteger(0, name, OBJPROP_COLOR, col);
  ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
}

La función MonitorReferenceLevels() se encarga de supervisar las interacciones del mercado con los niveles de referencia. Esta función realiza un seguimiento de cómo interactúa el precio con los niveles predefinidos, detectando toques, rupturas y giros. Actualiza el número de toques, los precios máximos y mínimos observados y las métricas de persistencia para evaluar la importancia de cada nivel. Cuando se cumplen determinados criterios —como que se produzcan varios toques de un nivel o se superen ciertos umbrales—, la función da por resuelto el nivel y activa notificaciones visuales o alertas, lo que ayuda a los operadores a detectar los puntos de inflexión del mercado.

void MonitorReferenceLevels(const MqlRates &rates[], int copied)
{
  // For each level, check touch, breakout, or reversal conditions
  for(int i=0; i<ArraySize(refLevels); i++)
  {
    RefLevel &L = refLevels[i];
    // Touch detection
    if(!L.touched)
    {
      if(rates[0].high >= L.price - touchTol && rates[0].low <= L.price + touchTol)
      {
        L.touched = true;
        L.touchTime = rates[0].time;
        L.touchCount++;
        // Update visual
        CreateOrUpdateLineText(...);
      }
    }
    else
    {
      // Check for breakout or reversal
      if(rates[0].high >= L.price + breakoutThreshold)
        L.result = 1; // Breakout
      else if(rates[0].low <= L.price - reversalThreshold)
        L.result = -1; // Reversal
      // Draw outcome
      DrawOutcome(L, L.result == 1);
    }
  }
}

El indicador también permite capturar instantáneas del estado actual del mercado mediante la función SnapshotReferenceLevels(). Esta función registra los valores actuales de los niveles, calcula puntuaciones en función de los toques registrados y almacena estos datos para futuras consultas.

void SnapshotReferenceLevels(double mean_val, double p25, double p75, double median_val, double mode_b, double mode_k, double stddev)
{
  // Store snapshot data
  snapshot_mean = mean_val;
  snapshot_p25 = p25;
  snapshot_p75 = p75;
  snapshot_median = median_val;
  snapshot_modeb = mode_b;
  snapshot_modek = mode_k;
  // Visualize snapshot
  ClearSnapshotVisuals();
  // Create lines
  CreateHLine_Pro(...);
  CreateOrUpdateLineText(...);
  refSnapshotTaken = true;
  snapshotTakenTime = TimeCurrent();
}

A continuación, los datos de la instantánea se pueden exportar a archivos CSV mediante la función ExportSnapshotCSV(), lo que permite a los operadores analizar niveles históricos, comparar diferentes regímenes de mercado o compartir datos con terceros. Esta funcionalidad aporta mayor profundidad al análisis de mercado, lo que permite realizar revisiones sin conexión y llevar a cabo una planificación estratégica.

void ExportSnapshotCSV()
{
  // Save snapshot data to CSV file
  string filename = StringFormat("CSTATS_SNAPSHOT_%s_%d.csv", _Symbol, (int)TimeCurrent());
  int handle = FileOpen(filename, FILE_WRITE | FILE_CSV);
  // Write headers and data
  FileWrite(handle, "symbol", _Symbol);
  // ...
  FileClose(handle);
}

A lo largo de toda nuestra implementación, mantenemos un enfoque coherente en la gestión de objetos gráficos y metadatos. Funciones como DeleteObjectIfExists(), SetObjTimestamp() y CleanupMetaForObject() garantizan que los objetos se creen, actualicen y eliminen correctamente, lo que evita el desorden y garantiza la integridad de los datos. Una gestión adecuada de las marcas de tiempo nos permite hacer un seguimiento de cuándo se modificaron o crearon por última vez los objetos, lo que ayuda a eliminar los elementos visuales obsoletos y a mantener una superposición precisa en los gráficos.

void CreatePanel()
  {
   if(ObjectFind(0, S_panel) >= 0)
      ObjectDelete(0, S_panel);
   if(!ObjectCreate(0, S_panel, OBJ_LABEL, 0, 0, 0))
     {
      if(DebugMode)
         Print("CreatePanel: ObjectCreate failed: ", GetLastError());
      return;
     }
   ObjectSetInteger(0, S_panel, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, S_panel, OBJPROP_XDISTANCE, 6);
   ObjectSetInteger(0, S_panel, OBJPROP_YDISTANCE, 24);
   ObjectSetString(0, S_panel, OBJPROP_TEXT, "Statistical — Dashboard");
   ObjectSetInteger(0, S_panel, OBJPROP_FONTSIZE, 11);
   ObjectSetInteger(0, S_panel, OBJPROP_SELECTABLE, false);
#ifdef __MQL5__
   ObjectSetInteger(0, S_panel, OBJPROP_BACK, false);
#endif
   SetObjTimestamp(S_panel);
  }



Resultados

En esta sección se explica qué resultados ofrece el asesor experto (EA) en la práctica y cómo interpretarlos. El siguiente diagrama muestra cómo el asesor experto (EA) permanece inactivo tras haberse incorporado al gráfico. A la izquierda, puedes ver el Panel estadístico y la barra de herramientas, con botones etiquetados como «Mean», «Std», «Mode», «Draw Levels», «Snapshot», «Apply Dates» y «Reset All». Junto a cada botón hay etiquetas provisionales que se actualizarán con las estadísticas calculadas al pulsar el botón correspondiente. En la esquina superior derecha hay un control en el que puedes introducir intervalos de fechas personalizados o puntos de referencia. Por el momento, no se han calculado niveles estadísticos, por lo que el gráfico solo muestra algunas primitivas del terminal y unas cuantas líneas horizontales que quedan de ejecuciones anteriores.

Al hacer clic en botones como «Mean» o «Draw Levels», el asesor experto (EA) tomará muestras del rango seleccionado —ya sea en función del período retrospectivo o de fechas concretas del gráfico— y calculará métricas como la media, la desviación estándar, los percentiles, la mediana y las modas. A continuación, trazará las líneas horizontales y las etiquetas pertinentes, rellenará los campos estadísticos y, si decides tomar una instantánea, comenzará a supervisar los toques, la persistencia y las posibles señales, como rupturas o giros, en esos niveles.

Resultado 1

A continuación, te mostraré cómo configurar el intervalo de fechas, lo que implica especificar la fecha de inicio y la fecha de finalización de tu análisis.

Configuración del intervalo de tiempo

Ahora, repasemos el rendimiento general.

El asesor experto (EA) está incorporado al gráfico con el intervalo de fechas establecido (Inicio: 14/08/2025 a las 11:00 — Fin: 23/09/2025 a las 04:00) y los dos marcadores de fecha y hora visibles en el gráfico. Cada botón del panel funciona al instante: al pulsar «Mean», «Std», «Mode», «Median», «P75/P25», etc., se calcula la métrica correspondiente al intervalo seleccionado y se representa directamente en el gráfico mediante una línea horizontal acompañada de una etiqueta de texto. «Draw Levels» traza un grupo de niveles seleccionados a la vez; «Remove Levels» los borra; y «Snapshot»/«Save Snapshot» guarda una instantánea de la configuración actual para su uso posterior.


Conclusión

El punto fuerte de esta herramienta reside en su lógica basada en botones, lo que hace que la comprobación y el análisis de gráficos sean rápidos e interactivos. Con un solo clic, se calculan al instante niveles estadísticos como la media, la desviación estándar, la moda, la mediana y los percentiles, y se muestran en el gráfico como líneas horizontales etiquetadas. Esto evita tener que realizar cálculos manuales y permite comparar, trazar o eliminar niveles rápidamente durante las pruebas. La posibilidad de aplicar o restablecer rangos, guardar instantáneas y controlar los niveles directamente desde el panel la convierte en un potente asistente para explorar cómo reacciona el precio ante diferentes zonas estadísticas, lo que agiliza tanto la investigación como el análisis en tiempo real.

Soporte y resistencia

El diagrama adicional refuerza esta conclusión al mostrar cómo los niveles calculados coinciden con el comportamiento real del mercado. La línea discontinua morada (percentil 25) y la línea continua amarilla (media más desviación estándar) demuestran que estos resultados estadísticos actúan sistemáticamente como auténticas zonas de soporte y resistencia. Observar cómo reacciona el precio en torno a estos niveles demuestra claramente que la herramienta define zonas clave de rebote y retroceso y destaca puntos fiables para anticipar rupturas y giros.

Esta herramienta está diseñada para facilitar el análisis de los niveles de precios mediante cálculos estadísticos. No realiza operaciones en tu nombre: la decisión sigue siendo tuya. Piensa en esta herramienta como en un asesor experto (EA) de apoyo para analizar niveles que funciona mejor cuando se utiliza junto con tu propia estrategia de trading. El objetivo es aclarar cómo interactúa el precio con los niveles derivados estadísticamente, ofreciendo un contexto más sólido para las rupturas, los giros y los retrocesos. De cara al futuro, espero poder desarrollar más herramientas que permitan profundizar en el análisis de la acción del precio y que sirvan de apoyo para tomar decisiones de trading bien fundamentadas.

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

Archivos adjuntos |
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
Mejore su análisis de mercado con el EA «Candlestick Probability», nativo de MQL5, una herramienta ligera que transforma las barras de precios sin procesar en información sobre probabilidades en tiempo real y específica para cada instrumento. Clasifica los patrones pinbar, envolvente y doji al cierre de la barra, utiliza un filtrado basado en el ATR y ofrece confirmación opcional de las rupturas. El EA calcula los porcentajes brutos y ponderados por volumen de continuación del precio en la dirección indicada por el patrón, lo que le ayuda a comprender el resultado típico de cada patrón en símbolos y marcos temporales concretos. Los marcadores en el gráfico, un panel de control compacto y los botones interactivos facilitan la validación y permiten centrarse en lo esencial. Exporte registros CSV detallados para realizar pruebas sin conexión. Úselos para elaborar perfiles de probabilidad, optimizar estrategias y convertir el reconocimiento de patrones en una ventaja cuantificable.
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.
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.
Automatización de estrategias de trading en MQL5 (Parte 34): Sistema de ruptura de líneas de tendencia con evaluación de la calidad del ajuste mediante R² Automatización de estrategias de trading en MQL5 (Parte 34): Sistema de ruptura de líneas de tendencia con evaluación de la calidad del ajuste mediante R²
En este artículo, desarrollamos un sistema de ruptura de líneas de tendencia en MQL5 que identifica líneas de soporte y resistencia a partir de puntos de giro, validadas mediante el coeficiente de determinación R² y restricciones angulares, para automatizar las operaciones de ruptura. Nuestro plan consiste en detectar máximos y mínimos de giro dentro de un período retrospectivo determinado, trazar líneas de tendencia con un número mínimo de puntos de contacto y validarlas mediante métricas de R² y restricciones del ángulo de inclinación para garantizar su fiabilidad.