English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Recetas MQL5 - Desarrollo de un indicador multidivisa para el análisis de la divergencia de precios

Recetas MQL5 - Desarrollo de un indicador multidivisa para el análisis de la divergencia de precios

MetaTrader 5Ejemplos | 29 abril 2014, 15:50
949 0
Anatoli Kazharski
Anatoli Kazharski

Introducción

En este artículo veremos el desarrollo de un indicador multidivisa para el análisis de la divergencia de precios en un periodo de tiempo determinado. Muchos de los momentos más importantes ya se vieron en el artículo anterior sobre programación de indicadores multidivisa Recetas MQL5 - Desarrollo de un indicador multidivisa de la volativilidad en MQL5. Por eso, esta vez sólo nos detendremos en las funciones nuevas, o bien en aquellas funciones que hayan sufrido cambios significativos. Si es la primera vez que se familiariza con el tema de la programación de indicadores multidivisa, le recomendamos que, en primer lugar, lea el artículo anterior.

En este artículo, veremos las cuestiones siguientes:

  • cambio de las propiedades del gráfico;
  • procesado de los eventos CHARTEVENT_OBJECT_DRAG (Arrastrar un objeto gráfico) y CHARTEVENT_CHART_CHANGE (evento de cambio de tamaño del gráfico o de las propiedades de un objeto gráfico a través del diálogo de propiedades);
  • dibujado de los búfers de indicador en más de un color;
  • determinar los máximos y mínimos en los búfers de indicador en la zona visible para establecer el máximo/mínimo del gráfico;
  • inversión de una fila.

Para un indicador, el volumen total del código resulta bastante grande, cerca de 1500 líneas. Por eso distribuiremos todas las funciones en archivos, en categorías por separado, después los acoplaremos al archivo principal del proyecto. En total, tendremos tres categorías de funciones para los archivos internos:

  • Checks.mqh - son las funciones para diversas comprobaciones y para la carga de datos accesibles;
  • Objects.mqh - son las funciones para el manejo y gestión de objetos gráficos;
  • Chart.mqh - son las funciones para el manejo y gestión de las propiedades del gráfico.

Las funciones que no tienen nada que ver con las categorías enumeradas más arriba, las dejaremos en el archivo principal.


Proceso de desarrollo del indicador

A continuación, procederemos a programar un indicador. Lo primero que hay que hacer, es crear un nuevo proyecto. Para ello, cree en el directorio Metatrader 5\MQL5\Indicators una carpeta con el nombre del indicador, y en ella la carpeta Include, en la cual se incluirán los archivos que acoplaremos. Después, cree el archivo principal en la carpeta del indicador. Se puede hacer de manera manual, creando un archivo de texto con la extensión *.mq5 o con la ayuda de la Guía MQL5 sobre plantillas preparadas. Además de las funciones elementales del programa OnInit(), OnDeinit() y OnCalculate(), se usarán también OnChartEvent() y OnTimer().

Al igual que en el artículo anterior, además del símbolo actual, mostraremos en el gráfico los datos de los otros cinco símbolos indicados en los parámetros externos. Pero esta vez, en lugar de valores calculados según cierta fórmula, mostraremos en el gráfico datos relacionados puramente con el precio. El tipo de representación de los datos queda a la elección del usuario. Es decir, en los parámetros internos se puede elegir, de entre la lista desplegable, una de las tres posibilidades: Línea, Barras o Velas japonesas.

Si se necesitara representar los datos como una línea de un solo color, en las propiedades del indicador, en el paratado de parámetros específicos (#property), basta con indicar la cantidad de búfers igual a la cantidad de símbolos, justo al principio. Pero dado que existen dos modos más para dibujar las series gráficas, en forma de barras y velas, para el modo de dos colores de los búfers necesitaremos algo más: cuatro búfers para formar cada serie y un búfer para establecer el color, según la condición de cada elemento en la serie gráfica.

Hay que establecer el color para cada serie gráfica en los parámetros específicos del programa. Para ello basta con enumerarlos con la ayuda de la coma. En primer lugar va el color que se usa en el modo de un solo color. En el de dos colores, se usará para colorear las barras/velas dirigidas hacia arriba. El segundo color se usará sólo en el modo de dos colores para las barras/velas dirigidas hacia abajo.

Abajo mostramos el código con la enumeración de los parámetros específicos:

#property indicator_chart_window // mostrar el indicador en la ventana del gráfico
#property indicator_buffers 25   // Cantidad de búfers para calcular el indicador
#property indicator_plots   5    // Cantidad de series gráficas
//--- Colores de los búfers de color
#property indicator_color1  clrDodgerBlue,C'0,50,100'
#property indicator_color2  clrLimeGreen,C'20,80,20'
#property indicator_color3  clrGold,C'160,140,0'
#property indicator_color4  clrAqua,C'0,140,140'
#property indicator_color5  clrMagenta,C'130,0,130'

Con ayuda de la directiva #define declaramos las constantes, y con la ayuda de la línea de comando #include añadimos los archivos con las funciones de las que se ha hablado más arriba, además de la clase para trabajar con el lienzo de la Biblioteca estándar:

//--- Constantes 
#define RESET           0 // Retorno al terminal del comando del recuento del indicador
#define SYMBOLS_COUNT   5 // Cantidad de símbolos
//--- Añadimos la clase para trabajar con el lienzo
#include <Canvas\Canvas.mqh>
//--- Añadimos nuestras bibliotecas
#include "Include/Checks.mqh"
#include "Include/Chart.mqh"
#include "Include/Objects.mqh"

Para crear listas desplegables, en los parámetros externos de elección del tipo de dibujado y el modo del punto inicial de divergencia de precios, creamos las enumeraciones ENUM_DRAWTYPE y ENUM_START_POINT:

//--- Tipo de dibujado de la información de precios
enum ENUM_DRAWTYPE
  {
   LINE   =0,  // Línea
   BARS   =1,  // Barras
   CANDLES=2   // Velas japonesas
  };
//--- Modo de punto inicial de divergencia de precios
enum ENUM_START_POINT
  {
   VERTICAL_LINE=0,  // Línea vertical
   MONTH        =1,  // Mes
   WEEK         =2,  // Semana
   DAY          =3,  // Día
   HOUR         =4   // Hora
  };

Ya hemos hablado más arriba de los tipos de representación de la información, ahora veremos con un poco más de detalle qué significa el punto inicial de divergencia de precios.

Vamos a crear en total cinco modos: Línea vertical, Mes, Semana, Día y Hora. Para el modo Línea vertical, al cargar el indicador en el gráfico se establecerá una línea vertical. Con el desplazamiento de esta línea se muestra la barra en la que los precios de todos los símbolos confluirán en un punto. Este punto de apoyo será el nivel del precio de apertura de la barra mostrada para el símbolo actual. Cualquier otro modo mostrará al programa que los precios deberán confluir cada vez en el comienzo del periodo mostrado. Es decir, al comienzo de cada mes, de cada semana, de cada día o de cada hora.

Más abajo puede familiarizarse con la lista de parámetros externos del indicador:

//--- Parámetros externos
input  ENUM_DRAWTYPE    DrawType             =CANDLES;       // Tipo de dibujado
input  ENUM_START_POINT StartPriceDivergence =VERTICAL_LINE; // Comienzo de la divergencia de precios
input  bool             TwoColor             =false;         // barras/velas de dos colores
sinput string dlm01=""; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
input  string           Symbol02             ="GBPUSD";      // Símbolo 2
input  bool             Inverse02            =false;         // Inversión del símbolo 2
input  string           Symbol03             ="AUDUSD";      // Símbolo 3
input  bool             Inverse03            =false;         // Inversión del símbolo 3
input  string           Symbol04             ="NZDUSD";      // Símbolo 4
input  bool             Inverse04            =false;         // Inversión del símbolo 4
input  string           Symbol05             ="USDCAD";      // Símbolo 5
input  bool             Inverse05            =false;         // Inversión del símbolo 5
input  string           Symbol06             ="USDCHF";      // Símbolo 6
input  bool             Inverse06            =false;         // Inversión del símbolo 6

La numeración de los símbolos comienza desde el dos, ya que el uno es el símbolo actual en el gráfico.

Para cada símbolo añadido se puede activar la inversión. La inversión significa que la información sobre dicho símbolo se mostrará invertida. Esto puede ser útil cuando en la lista de símbolos utilizados para el análisis haya ciertos símbolos para los que una misma divisa (por ejemplo, el dólar USA) puede actuar tanto como básica, como cotizadora. Por ejemplo, en la pareja de divisas EURUSD, el dólar es la divisa cotizadora, y en la pareja USDCHF, es la básica. En semejante caso, si en el gráfico el símbolo actual es EURUSD, entonces para USDCHF se puede conectar la inversión, obteniendo, de esta forma, una representación más cómoda de los datos a analizar.

Más abajo tenemos la lista de variables y matrices globales:

//--- Estructura de las matrices de los búfers de indicador
struct buffers
  {
   double            open[];   // Búfer para los precios de apertura
   double            high[];   // Búfer para los precios de los máximos
   double            low[];    // Búfer para los precios de los mínimos
   double            close[];  // Búfer para los precios de cierre
   double            icolor[]; // Búfer para determinar el color del elemento
  };
buffers           buffer_data[SYMBOLS_COUNT];
//--- Carga de la clase
CCanvas           canvas;
//--- Variables/matrices para copiar los datos desde OnCalculate()
int               OC_rates_total     =0; // Tamaño de las series temporales de entrada
int               OC_prev_calculated =0; // Barras procesadas en la llamada anterior
datetime          OC_time[];             // Hora de apertura
double            OC_open[];             // Precios de apertura
double            OC_high[];             // Precios máximos
double            OC_low[];              // Precios mínimos
double            OC_close[];            // Precios de cierre
long              OC_tick_volume[];      // Volúmenes de los ticks
long              OC_volume[];           // Volúmenes reales
int               OC_spread[];           // Spread

//--- Para almacenar y comprobar la hora de la primera barra en el terminal
datetime          series_first_date[SYMBOLS_COUNT];
datetime          series_first_date_last[SYMBOLS_COUNT];
//--- Matriz de la hora de la barra desde la que se comienza el dibujado
datetime          limit_time[SYMBOLS_COUNT];
//--- matriz de los nombres de los símbolos
string            symbol_names[SYMBOLS_COUNT];
//--- Matriz de las banderas de la inversión de los símbolos
bool              inverse[SYMBOLS_COUNT];
//--- Color de las líneas del indicador
color             line_colors[SYMBOLS_COUNT]={clrDodgerBlue,clrLimeGreen,clrGold,clrAqua,clrMagenta};
//--- Línea que simboliza la ausencia de símbolo
string            empty_symbol="EMPTY";
//--- propiedades del gráfico
int               window_number              =WRONG_VALUE;               // Número de la ventana del indicador
int               chart_width                =0;                         // Ancho del gráfico
int               chart_height               =0;                         // Altura del gráfico
int               last_chart_width           =0;                         // Último ancho del gráfico en la memoria
int               last_chart_height          =0;                         // Última altura del gráfico en la memoria
int               chart_center_x             =0;                         // Centro del gráfico por la horizontal
int               chart_center_y             =0;                         // Centro del gráfico por la vertical
color             color_bar_up               =clrRed;                    // Color de la barra hacia arriba
color             color_bar_down             =C'100,0,0';                // Color de la barra hacia abajo
string            indicator_shortname        ="MS_PriceDivergence";      // Nombre breve del indicador
string            prefix                     =indicator_shortname+"_";   // Prefijo para los objetos
//--- Nombre de la línea vertical del punto inicial de divergencia de precios
string            start_price_divergence=prefix+"start_price_divergence";
//--- Propiedades del lienzo
string            canvas_name             =prefix+"canvas";          // Nombre del lienzo
color             canvas_background       =clrBlack;                 // Color del fondo del lienzo
uchar             canvas_opacity          =190;                      // Nivel de opacidad
int               font_size               =16;                       // Tamaño de la fuente
string            font_name               ="Calibri";                // Fuente
ENUM_COLOR_FORMAT clr_format              =COLOR_FORMAT_ARGB_RAW;    // Los componentes del color deben ser establecidos correctamente por parte del usuario
//--- Mensajes del lienzo
string            msg_prepare_data        ="¡Preparando los datos! Espere, por favor...";
string            msg_not_synchronized    ="¡Los datos no están sincronizados! Espere, por favor...";
string            msg_load_data           ="";
string            msg_sync_update         ="";
string            msg_last                ="";
//---
ENUM_TIMEFRAMES   timeframe_start_point  =Period();    // Time frame para el punto de divergencia de los precios
datetime          first_period_time      =NULL;        // Hora del primer periodo indicado de datos en el gráfico
double            divergence_price       =0.0;         // Precio del punto inicial de divergencia de precios
datetime          divergence_time        =NULL;        // Hora del punto inicial de divergencia de precios
double            symbol_difference[SYMBOLS_COUNT];    // Diferencia de precio con respecto al símbolo actual
double            inverse_difference[SYMBOLS_COUNT];   // Diferencia formada al calcular la inversión

Ahora veremos las funciones que se usan al inicializarse el indicador. En principio, no hay grandes diferencias si comparamos con lo que había en la función OnInit() en el indicador del artículo anterior.

Añadimos la comprobación sobre dónde se usa el indicador. El asunto es que en el simulador, en este momento, los desarrolladores del terminal no han implementado todas las posibilidades de manejo de las propiedades del gráfico, por eso nos limitaremos a utilizar el indicador fuera del simulador. Para ellos escribiremos una función sencilla, CheckTesterMode(). Se encontrará en el archivo Checks.mqh:

//+------------------------------------------------------------------+
//| Comprueba si se usa el indicador en el simulador                 |
//+------------------------------------------------------------------+
bool CheckTesterMode()
  {
//--- Si el indiacdor ha sido iniciado en el simulador, notificaremos que su misión no es ser utilizado en el simulador
   if(MQLInfoInteger(MQL_TESTER) || 
      MQLInfoInteger(MQL_VISUAL_MODE) || 
      MQLInfoInteger(MQL_OPTIMIZATION))
     {
      Comment("¡En este momento, el indicador <- "+MQLInfoString(MQL_PROGRAM_NAME)+" -> no está preparado para utilizarse en el simulador!");
      return(false);
     }
//---
   return(true);
  }

Otra función nueva más, SetBarsColors() , está pensada para establecer el color de las barras/velas del símbolo actual, y se ubica en el archivo Chart.mqh.

//+------------------------------------------------------------------+
//| Establece el color de las barras del símbolo actual              |
//+------------------------------------------------------------------+
void SetBarsColors()
  {
//--- Color de la barra hacia arriba, la sombra o del contorno del cuerpo de la vela del toro
   ChartSetInteger(0,CHART_COLOR_CHART_UP,color_bar_up);
//--- Color del cuerpo de la vela del toro
   ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,color_bar_up);
//--- Color de la línea del gráfico y de las velas japonesas "Lluvia"
   ChartSetInteger(0,CHART_COLOR_CHART_LINE,color_bar_up);
//--- Si está conectado el modo de dos colores
   if(TwoColor)
     {
      //--- Color de la barra hacia abajo, la sombra o del contorno del cuerpo de la vela del oso
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_down);
      //--- Color del cuerpo de la vela del oso
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_down);
     }
//--- Si está desconectado el modo de dos colores
   else
     {
      //--- Color de la barra hacia abajo, la sombra o del contorno del cuerpo de la vela del oso
      ChartSetInteger(0,CHART_COLOR_CHART_DOWN,color_bar_up);
      //--- Color del cuerpo de la vela del oso
      ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,color_bar_up);
     }
  }

En la inicialización hay que determinar qué modo se ha elegido en el parámetro externo StartPriceDivergence. Si se ha elegido el modo Línea vertical, entonces en la variable global timeframe_start_point permanecerá el valor por defecto, es decir, el time frame actual. En caso contrario, se establecerá el time frame que corresponda a la elección. Para ello escribiremos la función InitStartPointTF():

//+------------------------------------------------------------------+
//| Determina el time frame para el modo                             |
//| del punto inical de los precios                                  |
//+------------------------------------------------------------------+
void InitStartPointTF()
  {
//--- Si tenemos el modo de la línea vertical, salimos
   if(StartPriceDivergence==VERTICAL_LINE)
      return;
//--- De lo contrario, determinamos el periodo
   switch(StartPriceDivergence)
     {
      case MONTH : timeframe_start_point=PERIOD_MN1; break;
      case WEEK  : timeframe_start_point=PERIOD_W1;  break;
      case DAY   : timeframe_start_point=PERIOD_D1;  break;
      case HOUR  : timeframe_start_point=PERIOD_H1;  break;
     }
  }

La función CheckInputParameters(), a diferencia del artículo anterior, ahora tiene este aspecto:

//+------------------------------------------------------------------+
//| Comprueba la corrección de los parámetros de entrada             |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- Si ahora el modo no es el de línea vertical
   if(StartPriceDivergence!=VERTICAL_LINE)
     {
      //--- Si el periodo actual es superior o igual al indicado para el punto inicial de divergencia de precios, lo declaramos y salimos
      if(PeriodSeconds()>=PeriodSeconds(timeframe_start_point))
        {
         Print("¡El time frame actual debe ser menor que en el parámetro Start Price Divergence!");
         Comment("El time frame actual debe ser menor que en el parámetro Start Price Divergence!");
         return(false);
        }
     }
//---
   return(true);
  }

La inicialización de las matrices tiene más o menos el mismo aspecto que en el artículo anterior. Sólo ha cambiado el nombre de las matrices y su cantidad.

//+------------------------------------------------------------------+
//| Primera inicialización de matrices                               |
//+------------------------------------------------------------------+
void InitArrays()
  {
   ArrayInitialize(limit_time,NULL);
   ArrayInitialize(symbol_difference,0.0);
   ArrayInitialize(inverse_difference,0.0);
   ArrayInitialize(series_first_date,NULL);
   ArrayInitialize(series_first_date_last,NULL);
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      ArrayInitialize(buffer_data[s].open,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].high,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].low,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].close,EMPTY_VALUE);
      ArrayInitialize(buffer_data[s].icolor,EMPTY_VALUE);
     }
  }
//+------------------------------------------------------------------+
//| Inicializa la matriz de símbolos                                 |
//+------------------------------------------------------------------+
void InitSymbolNames()
  {
   symbol_names[0]=AddSymbolToMarketWatch(Symbol02);
   symbol_names[1]=AddSymbolToMarketWatch(Symbol03);
   symbol_names[2]=AddSymbolToMarketWatch(Symbol04);
   symbol_names[3]=AddSymbolToMarketWatch(Symbol05);
   symbol_names[4]=AddSymbolToMarketWatch(Symbol06);
  }
//+------------------------------------------------------------------+
//| Inicializa la matriz de inversión                                |
//+------------------------------------------------------------------+
void InitInverse()
  {
   inverse[0]=Inverse02;
   inverse[1]=Inverse03;
   inverse[2]=Inverse04;
   inverse[3]=Inverse05;
   inverse[4]=Inverse06;
  }

Los cambios sustanciales se han llevado a cabo en la fucnión SetIndicatorProperties(). Es prácticamente una función nueva. Ahora, dependiendo del modo que se haya elegido para la representación de los datos, durante la inicialización se establecerán las propiedades correspondientes.

//+------------------------------------------------------------------+
//|  Establece las propiedades del indicador                         |
//+------------------------------------------------------------------+
void SetIndicatorProperties()
  {
//--- Establecemos el nombre breve
   IndicatorSetString(INDICATOR_SHORTNAME,indicator_shortname);
//--- Establecemos la cantidad de símbolos
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
//---  En el modo línea solamente se necesita un búfer, que representa el precio de apertura
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         SetIndexBuffer(s,buffer_data[s].close,INDICATOR_DATA);
     }
//--- En otros modos utilizamos todos los precios para construir 
//    las barras/velas y un búfer adicional para el modo de dos colores
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         static int buffer_number=0;
         SetIndexBuffer(buffer_number,buffer_data[s].open,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].high,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].low,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].close,INDICATOR_DATA);
         buffer_number++;
         SetIndexBuffer(buffer_number,buffer_data[s].icolor,INDICATOR_COLOR_INDEX);
         buffer_number++;
        }
     }
//--- Establecemos la marca para el time frame actual
//    En el modo de la línea se encuentra solamente el precio de apertura
   if(DrawType==LINE)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetString(s,PLOT_LABEL,symbol_names[s]+",Close");
     }
//--- En otros modos se encuentran todos los precios de las barras/velas
//    Como separador se usa ";"
   else if(DrawType==BARS || DrawType==CANDLES)
     {
      for(int s=0; s<SYMBOLS_COUNT; s++)
        {
         PlotIndexSetString(s,PLOT_LABEL,
                            symbol_names[s]+",Open;"+
                            symbol_names[s]+",High;"+
                            symbol_names[s]+",Low;"+
                            symbol_names[s]+",Close");
        }
     }
//--- Establecemos el tipo de línea para los búfers de indicador
//    Línea
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_LINE);
//--- Barras
   if(DrawType==BARS)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_BARS);
//--- Velas
   if(DrawType==CANDLES)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_DRAW_TYPE,DRAW_COLOR_CANDLES);

//--- Establecemos el tipo de línea para los datos del símbolo actual
//    Línea
   if(DrawType==LINE)
      ChartSetInteger(0,CHART_MODE,CHART_LINE);
//--- Barras
   if(DrawType==BARS)
      ChartSetInteger(0,CHART_MODE,CHART_BARS);
//--- Velas
   if(DrawType==CANDLES)
      ChartSetInteger(0,CHART_MODE,CHART_CANDLES);

//--- Establecemos el grosor de la línea
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetInteger(s,PLOT_LINE_WIDTH,1);
//--- Establecemos el color de la línea para el modo Línea
   if(DrawType==LINE)
      for(int s=0; s<SYMBOLS_COUNT; s++)
         PlotIndexSetInteger(s,PLOT_LINE_COLOR,line_colors[s]);
//--- Establecemos la muestra de datos solamente de los símbolos existentes, en la Ventana de Datos
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      if(symbol_names[s]!=empty_symbol)
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,true);
      else
         PlotIndexSetInteger(s,PLOT_SHOW_DATA,false);
     }
//--- Valor vacío para representar, que no se dibuja
   for(int s=0; s<SYMBOLS_COUNT; s++)
      PlotIndexSetDouble(s,PLOT_EMPTY_VALUE,EMPTY_VALUE);
  }

Y, al fin, otra función nueva más, SetDivergenceLine() para usar en OnInit(). Esta función establece una línea vertical de color verde para gestionar el inicio de la divergencia de precios en el modo Línea vertical.

//+------------------------------------------------------------------+
//| Establece la línea vertical para el punto inicial de los precios |
//+------------------------------------------------------------------+
void SetDivergenceLine()
  {
//--- Si no hay línea vertical, entonces la establecemos
   if(StartPriceDivergence==VERTICAL_LINE && ObjectFind(0,start_price_divergence)<0)
      //---Establecemos la línea vertical en la barra verdadera
      CreateVerticalLine(0,0,TimeCurrent()+PeriodSeconds(),start_price_divergence,
                         2,STYLE_SOLID,clrGreenYellow,true,true,false,"","\n");
//--- Si no estamos en el modo de línea vertical
   if(StartPriceDivergence!=VERTICAL_LINE)
      DeleteObjectByName(start_price_divergence);
  }

Más abajo se muestra el aspecto que tendría todo lo explicado anteriormente en la función OnInit(). Cuando todo está distribuido en funciones por separado, leer el programa se convierte en algo muy sencillo.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Comprobamos si se está usando el indicador en el simulador en este momento
   if(!CheckTesterMode())
      return(INIT_FAILED);
//--- Establecemos el color de las barras/velas
   SetBarsColors();
//--- Determinamos el periodo para el punto inicial de divergencia de precios
   InitStartPointTF();
//--- Comprobamos que los parámetros de entrada sean correctos
   if(!CheckInputParameters())
      return(INIT_PARAMETERS_INCORRECT);
//--- Conectamos el temporizador con un intervalo de 1 segundo
   EventSetMillisecondTimer(1000);
//--- Establecemos la fuente para la representación en el lienzo
   canvas.FontSet(font_name,font_size,FW_NORMAL);
//--- Inicialización de las matrices
   InitArrays();
//--- Inicializamos la matriz de los símbolos
   InitSymbolNames();
//--- Inicializamos la matriz de las inversiones
   InitInverse();
//--- Establecemos las propiedades del indicador
   SetIndicatorProperties();
//--- Establecemos la línea vertical del comienzo de la divergencia de los precios
   SetDivergenceLine();
//--- Borramos los comentarios
   Comment("");
//--- Actualizamos el gráfico
   ChartRedraw();
//--- La inicialización ha tenido éxito
   return(INIT_SUCCEEDED);
  }

En la función OnCalculate(), el código del programa ha permanecido prácticamente sin cambios. En el artículo anterior, después de comprobar que la información está disponible, al principio, el programa llenó las matrices auxiliares en un primer ciclo, y sólo después llenó los búfers de indicador con los datos ya listos. En esta ocasión, lo colocaremos todo en un ciclo.

He ideado de manera rigurosa las funciones de comprobación y carga de los datos. Ahora, cada valor que debe ser obtenido supera un determinado número de intentos con una parada de ciclo, si obtiene el resultado. Dado que ahora hay modos donde es necesario determinar el comienzo de tal o cual periodo (mes, semana, día, hora), obtendremos la hora de comienzo del periodo a través del time frame mayor. Para ello se ha escrito una función adicional parecida a LoadAndFormData(), y que tiene un nombre similar LoadAndFormDataHighTF(). Su código es parecido al código fuente de su función análoga, por eso no lo voy a mostrar aquí.

He implementado la comprobación de la accesibilidad de los time frames actual y mayor en una sola función, CheckAvailableData():

//+------------------------------------------------------------------+
//| Comprueba la cantidad de datos accesibles para todos los símbolos|
//+------------------------------------------------------------------+
bool CheckAvailableData()
  {
   int attempts=100;
   
//---
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- Si existe tal símbolo
      if(symbol_names[s]!=empty_symbol)
        {
datetime time[];                    // Matriz para comprobar la cantidad de barras
   int      total_period_bars   =0;    // Cantidad de barras del periodo actual
   datetime terminal_first_date =NULL; // Primera fecha de los datos disponibles del periodo actual en el terminal
         //--- Obtenemos la primera fecha de los datos del periodo actual en el terminal
         terminal_first_date=(datetime)SeriesInfoInteger(symbol_names[s],Period(),SERIES_TERMINAL_FIRSTDATE);
         //--- Obtenemos la cantidad de barras disponibles sobre la fecha indicada
         total_period_bars=Bars(symbol_names[s],Period(),terminal_first_date,TimeCurrent());
         //--- Comprobamos si los datos de las barras están preparados
         for(int i=0; i<attempts; i++)
           {
            //--- Copiamos la cantidad de datos indicada
            if(CopyTime(symbol_names[s],Period(),0,total_period_bars,time))
              {
               //--- Si se ha copiado la cantidad necesaria, establecemos el ciclo
               if(ArraySize(time)>=total_period_bars)
                  break;
              }
           }
         //--- Si se han copiado menos datos, hay que llevar a cabo otro intento más
         if(ArraySize(time)==0 || ArraySize(time)<total_period_bars)
           {
            msg_last=msg_prepare_data;
            ShowCanvasMessage(msg_prepare_data);
            OC_prev_calculated=0;
            return(false);
           }
        }
     }
//--- Si estamos en el modo de línea vertical para el punto inicial de la divergencia de precios, salimos
   if(StartPriceDivergence==VERTICAL_LINE)
      return(true);
   else
     {
      datetime time[];                    // Matriz para comprobar la cantidad de barras
      int      total_period_bars   =0;    // Cantidad de barras del periodo actual
      datetime terminal_first_date =NULL; // Primera fecha de los datos disponibles del periodo actual en el terminal
      //--- Obtenemos la primera fecha de los datos del periodo actual en el terminal
      for(int i=0; i<attempts; i++)
         if((terminal_first_date=(datetime)SeriesInfoInteger(Symbol(),Period(),SERIES_FIRSTDATE))>0)
            break;
      //--- Obtenemos la cantidad de barras disponibles sobre la fecha indicada
      for(int i=0; i<attempts; i++)
         if((total_period_bars=(int)SeriesInfoInteger(Symbol(),timeframe_start_point,SERIES_BARS_COUNT))>0)
            break;
      //--- Comprobamos que los datos de las barras estén preparados
      for(int i=0; i<attempts; i++)
         //--- Copiamos la cantidad de datos indicada
         if(CopyTime(Symbol(),timeframe_start_point,
            terminal_first_date+PeriodSeconds(timeframe_start_point),TimeCurrent(),time)>0)
            break;
      //--- Si se han copiado menos datos, hay que llevar a cabo otro intento más
      if(ArraySize(time)<=0 || total_period_bars<=0)
        {
         msg_last=msg_prepare_data;
         ShowCanvasMessage(msg_prepare_data);
         OC_prev_calculated=0;
         return(false);
        }
     }
//---
   return(true);
  }

La función FillIndicatorBuffers() se ha visto complicada significativamente para la tarea actual. Esto está relacionado con que ahora hay varios modos, y para cada uno de ellos hay que llevar a cabo las acciones necesarias. En esencia, se puede dividir todo en cuatro etapas.

  • La obtención de datos sobre el símbolo indicado
  • La obtención de datos sobre el time frame mayor, la determinación de la hora y el nivel del precio donde los precios de todos los símbolos coinciden;
  • El cálculo de los valores y el llenado de los búfers de indicador;
  • La comprobación de control.

Aquí se comenta con detalle el código de la función, para su estudio:

//+------------------------------------------------------------------+
//| Llenando los búfers de indicador                                 |
//+------------------------------------------------------------------+
void FillIndicatorBuffers(int i,int s,datetime const &time[])
  {
   MqlRates    rates[];             // Estructura de los datos
   double      period_open[];       // Precio de apertura de la barra al inicio de la divergencia de precios
   datetime    period_time[];       // Hora del comienzo del periodo de divergencia de precios
   int         attempts=100;        // Cantidad de intentos de copiado
   datetime    high_tf_time=NULL;   // Hora de la barra del time frame mayor

//--- Si nos encontramos fuera de la zona de las barras de símbolo "auténticas", salimos
   if(time[i]<limit_time[s])
      return;
//--- Borramos el último error
   ResetLastError();
//--- Obtenemos los datos de la barra actual del símbolo indicado
   for(int j=0; j<attempts; j++)
      if(CopyRates(symbol_names[s],Period(),time[i],1,rates)==1)
        { ResetLastError(); break; }
//--- Si no logramos obtener los datos, salimos
   if(ArraySize(rates)<1 || GetLastError()!=0)
      return;
//--- Si la hora actual es anterior a la hora del primer periodo, o
//    la hora de la barra no es igual a la hora de la barra del símbolo actual, o
//    se han obtenido valores vacíos
   if(rates[0].time==NULL || 
      time[i]!=rates[0].time || 
      time[i]<first_period_time || 
      rates[0].low==EMPTY_VALUE || 
      rates[0].open==EMPTY_VALUE ||
      rates[0].high==EMPTY_VALUE ||
      rates[0].close==EMPTY_VALUE)
     {
      //--- Grabamos el valor vacío
      if(DrawType!=LINE)
        {
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
        }
      buffer_data[s].close[i]=EMPTY_VALUE;
      return;
     }
//--- Si estamos en el modo de línea vertical para el punto inicial de divergencia de precios
   if(StartPriceDivergence==VERTICAL_LINE)
     {
      //--- Obtenemos la hora de la línea
      divergence_time=(datetime)ObjectGetInteger(0,start_price_divergence,OBJPROP_TIME);
      //--- Obtenemos la hora de la primera barra
      first_period_time=time[0];
     }
//--- Si estamos en otros modos, rastrearemos el comienzo del periodo
   else
     {
      //--- Si entramos aquí por primera vez, grabamos los datos de la primera barra del time frame mayor
      if(divergence_time==NULL)
        {
         ResetLastError();
         //--- Obtenemos la hora de apertura de la primera barra del time frame mayor
         for(int j=0; j<attempts; j++)
            if(CopyTime(Symbol(),timeframe_start_point,time[0]+PeriodSeconds(timeframe_start_point),1,period_time)==1)
              { ResetLastError(); break; }
         //--- Si no hemos logrado obtener el precio/hora, salimos
         if(ArraySize(period_time)<1 || GetLastError()!=0)
            return;
         //--- De otra forma, grabamos la hora de la primera barra del time frame mayor
         else
            first_period_time=period_time[0];
        }
      //--- Si la hora de la barra actual en el time frame actual es anterior a la hora de la primera barra del time frame mayor
      if(time[i]<first_period_time)
         high_tf_time=first_period_time;
      //--- De otra forma obtendremos los datos de la última barra del time frame mayor, con respecto a la barra actual del time frame actual
      else
         high_tf_time=time[i];
      //--- Ponemos a cero el último error
      ResetLastError();
      //--- Obtenemos el precio de apertura de la primera barra del time frame mayor
      for(int j=0; j<attempts; j++)
         if(CopyOpen(Symbol(),timeframe_start_point,high_tf_time,1,period_open)==1)
           { ResetLastError(); break; }
      //--- Obtenemos la hora de apertura de la primera barra del time frame mayor
      for(int j=0; j<attempts; j++)
         if(CopyTime(Symbol(),timeframe_start_point,high_tf_time,1,period_time)==1)
           { ResetLastError(); break; }
      //--- Si no hemos logrado obtener el precio/hora, salimos
      if(ArraySize(period_open)<1 || ArraySize(period_time)<1 || GetLastError()!=0)
         return;
      //--- Si la hora del time frame actual es anterior a la hora de comienzo del primer periodo o
      //    la hora del periodo indicado no es igual a la que hay en la memoria
      if(time[i]<first_period_time || divergence_time!=period_time[0])
        {
         symbol_difference[s]  =0.0; // Ponemos a cero la diferencia de precios de los símbolos
         inverse_difference[s] =0.0; // Ponemos a cero la diferencia de inversión
         //--- Grabamos la hora del comienzo de la divergencia de precios
         divergence_time=period_time[0];
         //--- Grabamos el nivel del comienzo de la divergencia de precios
         divergence_price=period_open[0];
         //--- Establecemos la línea vertical al comienzo del periodo de divergencia de precios
         CreateVerticalLine(0,0,period_time[0],start_price_divergence+"_"+TimeToString(divergence_time),
                            2,STYLE_SOLID,clrWhite,false,false,true,TimeToString(divergence_time),"\n");
        }
     }
//--- Si estamos en el modo de línea vertical y la hora de la barra es anterior a la hora de la línea
   if(StartPriceDivergence==VERTICAL_LINE && time[i]<divergence_time)
     {
      //--- Mantenemos valores a cero para la diferencia
      symbol_difference[s]  =0.0;
      inverse_difference[s] =0.0;
      //--- Para el modo de dibujado de la Línea, sólo el precio de apertura
      if(DrawType==LINE)
         buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
      //--- Para otros modos de dibujado, todos los precios
      else
        {
         buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
         buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
         buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
         buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
         //--- Establecemos el color para el elemento actual del búfer de indicador
         SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
        }
     }
//--- Si estamos en otros modos
   else
     {
      //--- Si es necesaria la inversión de los datos del símbolo
      if(inverse[s])
        {
         //--- Si ha empezado un nuevo periodo, recalculamos las variables para el cálculo
         if(symbol_difference[s]==0.0)
           {
            //--- Si estamos en el modo de línea vertical
            if(StartPriceDivergence==VERTICAL_LINE)
              {
               //--- Calculamos la diferencia
               symbol_difference[s]  =rates[0].open-OC_open[i];
               inverse_difference[s] =OC_open[i]-(-OC_open[i]);
              }
            //--- Para otros modos
            else
              {
               //--- Calculamos la diferencia
               symbol_difference[s]  =rates[0].open-divergence_price;
               inverse_difference[s] =divergence_price-(-divergence_price);
              }
           }
         //--- Para el modo de la línea, sólo el precio de cierre
         if(DrawType==LINE)
            buffer_data[s].close[i]=-(rates[0].close-symbol_difference[s])+inverse_difference[s];
         //--- Para los otros modos, todos los precios
         else
           {
            buffer_data[s].low[i]   =-(rates[0].low-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].open[i]  =-(rates[0].open-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].high[i]  =-(rates[0].high-symbol_difference[s])+inverse_difference[s];
            buffer_data[s].close[i] =-(rates[0].close-symbol_difference[s])+inverse_difference[s];
            //--- Establecemos el color del elemento actual del búfer de indicador
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
      //--- Si es sin inversión, entonces, al principio del periodo hay que restar sólo la diferencia entre los precios de los símbolos
      else
        {
         //--- Si ha comenzado un periodo nuevo
         if(symbol_difference[s]==0.0)
           {
            //--- Si estamos en el modo de línea vertical
            if(StartPriceDivergence==VERTICAL_LINE)
               symbol_difference[s]=rates[0].open-OC_open[i];
            //--- Para los otros modos
            else
               symbol_difference[s]=rates[0].open-divergence_price;
           }
         //--- Para el modo de dibujado de la línea, sólo el precio de apertura
         if(DrawType==LINE)
            buffer_data[s].close[i]=rates[0].close-symbol_difference[s];
         //--- Para los otros modos de dibujado, todos los precios
         else
           {
            buffer_data[s].low[i]   =rates[0].low-symbol_difference[s];
            buffer_data[s].open[i]  =rates[0].open-symbol_difference[s];
            buffer_data[s].high[i]  =rates[0].high-symbol_difference[s];
            buffer_data[s].close[i] =rates[0].close-symbol_difference[s];
            //--- Establecemos el color para el elemento actual del búfer de indicador
            SetBufferColorIndex(i,s,rates[0].close,rates[0].open);
           }
        }
     }
//--- Comprobamos los valores calculados, a modo de control
//    Para el modo de la línea, sólo el precio de apertura
   if(DrawType==LINE)
     {
      //--- Si la hora actual es anterior a la hora del primer periodo o
      //    la hora de la barra no es igual a la hora de la barra del símbolo actual, grabamos un valor vacío
      if(time[i]!=rates[0].time || time[i]<first_period_time)
         buffer_data[s].close[i]=EMPTY_VALUE;
     }
//--- Para los otros modos, todos los precios
   else
     {
      //--- Si la hora actual es anterior a la hora del primer periodo o
      //    la hora de la barra no es igual a la hora de la barra del símbolo actual o
      //    hemos obtenido valores vacíos
      if(rates[0].time==NULL || 
         time[i]!=rates[0].time || 
         time[i]<first_period_time || 
         rates[0].low==EMPTY_VALUE || 
         rates[0].open==EMPTY_VALUE ||
         rates[0].high==EMPTY_VALUE ||
         rates[0].close==EMPTY_VALUE)
        {
         //--- Grabamos un valor vacío
         buffer_data[s].low[i]   =EMPTY_VALUE;
         buffer_data[s].open[i]  =EMPTY_VALUE;
         buffer_data[s].high[i]  =EMPTY_VALUE;
         buffer_data[s].close[i] =EMPTY_VALUE;
        }
     }
  }

Al estudiar la función de arriba, habrá usted notado una función personalizada más, SetBufferColorIndex(). Con la ayuda de esta función, se establece el color en el búfer de indicador de color.

//+------------------------------------------------------------------+
//| Establece el color del elemento del búfer según la condición     |
//+------------------------------------------------------------------+
void SetBufferColorIndex(int i,int symbol_number,double close,double open)
  {
//--- Si está conectado el modo de dos colores, comprobamos la condición
   if(TwoColor)
     {
      //--- Si el precio de cierre es superior que el de apertura, entonces esta barra es hacia arriba, y usamos el primer color
      if(close>open)
         buffer_data[symbol_number].icolor[i]=0;
      //--- de otra forma, se trata de una barra hacia abajo, y utilizamos el segundo color
      else
         buffer_data[symbol_number].icolor[i]=1;
     }
//--- Si estamos en el modo de un sólo color, entonces usamos el primer color para todas las barras/velas
   else
      buffer_data[symbol_number].icolor[i]=0;
  }

Después de que los búfers de indicador estén llenos, hay que determinar el máximo y el mínimo de todos aquellos valores que se ven en el momento actual en la ventana del gráfico. En MQL5 existe la posibilidad de obtener el número de la primera barra visible en la ventana del gráfico, así como la cantidad de barras visibles. Precisamente estas posibilidades las vamos a usar en otra función personalizada más, CorrectChartMaxMin(). La función se puede dividir en varias etapas:

  • Determinar el número de la primera y la última barra visible;
  • Determinar el máximo y el mínimo de barras visibles del símbolo actual;
  • Determinar el máximo y el mínimo entre todas las matrices de los símbolos;
  • Establecer el máximo y el mínimo de propiedades del grafico.

Más abajo mostramos el código de la función CorrectChartMaxMin(), que se encuentra en el archivo Chart.mqh.

//+------------------------------------------------------------------+
//| Corrige el máximo/mínimo del gráfico                             |
//| con respecto a todos los búfers                                  |
//+------------------------------------------------------------------+
void CorrectChartMaxMin()
  {
   double low[];                  // Matriz de mínimos
   double high[];                 // Matriz de máximos
   int    attempts          =10;  // Cantidad de intentos
   int    array_size        =0;   // Tamaño de la matriz para el dibujado
   int    visible_bars      =0;   // Cantidad de barras visibles
   int    first_visible_bar =0;   // Número de la primera barra visible
   int    last_visible_bar  =0;   // Número de la última barra visible
   double max_price         =0.0; // Precio máximo
   double min_price         =0.0; // Precio mínimo
   double offset_max_min    =0.0; // Desajuste del máximo/mínimo del gráfico
//--- Ponemos a cero el último error
   ResetLastError();
//--- Cantidad de barras visibles
   visible_bars=(int)ChartGetInteger(0,CHART_VISIBLE_BARS);
//--- Número de la primera barra visible
   first_visible_bar=(int)ChartGetInteger(0,CHART_FIRST_VISIBLE_BAR);
//--- Número de la última barra visible
   last_visible_bar=first_visible_bar-visible_bars;
//--- Si existe un error, salimos
   if(GetLastError()!=0)
      return;
//--- Si el valor es incorrecto, lo corregimos
   if(last_visible_bar<0)
      last_visible_bar=0;
//--- Obtenemos el máximo y mínimo del símbolo actual en la parte visible del gráfico
   for(int i=0; i<attempts; i++)
      if(CopyHigh(Symbol(),Period(),last_visible_bar,visible_bars,high)==visible_bars)
         break;
   for(int i=0; i<attempts; i++)
      if(CopyLow(Symbol(),Period(),last_visible_bar,visible_bars,low)==visible_bars)
         break;
//--- Salimos, si los datos no han sido recibidos
   if(ArraySize(high)<=0 || ArraySize(low)<=0)
      return;
//--- Si los datos han sido obtenidos, determinamos las matrices del símbolo actual máximo y mínimo
   else
     {
      min_price=low[ArrayMinimum(low)];
      max_price=high[ArrayMaximum(high)];
     }
//--- Obtenemos los precios máximos y mínimos en todas las matrices de precio
   for(int s=0; s<SYMBOLS_COUNT; s++)
     {
      //--- Si no hay un símbolo así, pasamos al siguiente
      if(symbol_names[s]==empty_symbol)
         continue;
      //---
      datetime time[];         // Matriz de tiempo
      int      bars_count=0; // Cantidad de barras para el cálculo
      //--- Establecemos un tamaño cero en las matrices
      ArrayResize(high,0);
      ArrayResize(low,0);
      //--- Obtenemos la hora de la primera barra visible en el gráfico
      for(int i=0; i<attempts; i++)
         if(CopyTime(Symbol(),Period(),last_visible_bar,visible_bars,time)==visible_bars)
            break;
      //--- Si hay menos datos que barras visibles en el gráfico, salimos
      if(ArraySize(time)<visible_bars)
         return;
      //--- Si la hora de la primera barra "verdadera" es mayor que
      //    la hora de la primera barra visible en el gráfico, entonces
      //    obtenemos la cantidad de barras disponibles en el ciclo actual del símbolo
      if(limit_time[s]>time[0])
        {
         //--- Obtenemos el tamaño de la matriz
         array_size=ArraySize(time);
         //--- Obtenemos la cantidad de barras desde la primera verdadera
         if((bars_count=Bars(Symbol(),Period(),limit_time[s],time[array_size-1]))<=0)
            return;
        }
      //--- De otra forma, obtenemos la cantidad de barras visibles en el gráfico
      else
         bars_count=visible_bars;
      //--- Establecemos la indexación como en las series temporales
      ArraySetAsSeries(low,true);
      ArraySetAsSeries(high,true);
      //--- Copiamos los datos de los búfers de indicador
      //    Todos los modos menos Línea
      if(DrawType!=LINE)
        {
         ArrayCopy(low,buffer_data[s].low);
         ArrayCopy(high,buffer_data[s].high);
        }
      //--- Si estamos en el modo Línea
      else
        {
         ArrayCopy(low,buffer_data[s].close);
         ArrayCopy(high,buffer_data[s].close);
        }
      //--- Obtenemos el tamaño de la matriz
      array_size=ArraySize(high);
      //--- llenamos los valores vacíos,
      //    para que no se tengan en cuenta al determinar los máximos/mínimos
      for(int i=0; i<array_size; i++)
        {
         if(high[i]==EMPTY_VALUE)
            high[i]=max_price;
         if(low[i]==EMPTY_VALUE)
            low[i]=min_price;
        }
      //--- Determinamos los máximos/mínimos, teniendo en cuenta la inversión
      if(inverse[s])
        {
         //--- Si no hay errores, grabamos los valores
         if(ArrayMaximum(high,last_visible_bar,bars_count)>=0 && 
            ArrayMinimum(low,last_visible_bar,bars_count)>=0)
           {
            max_price=fmax(max_price,low[ArrayMaximum(low,last_visible_bar,bars_count)]);
            min_price=fmin(min_price,high[ArrayMinimum(high,last_visible_bar,bars_count)]);
           }
        }
      else
        {
         //--- Si no hay errores, grabamos los valores
         if(ArrayMinimum(low,last_visible_bar,bars_count)>=0 && 
            ArrayMaximum(high,last_visible_bar,bars_count)>=0)
           {
            min_price=fmin(min_price,low[ArrayMinimum(low,last_visible_bar,bars_count)]);
            max_price=fmax(max_price,high[ArrayMaximum(high,last_visible_bar,bars_count)]);
           }
        }
     }
//--- Calculamos el desajuste (3%) con respecto a la parte superior e inferior del gráfico
   offset_max_min=((max_price-min_price)*3)/100;
//--- Conectamos el modo de escala fija del gráfico
   ChartSetInteger(0,CHART_SCALEFIX,true);
//--- Establecemos el máximo/mínimo
   ChartSetDouble(0,CHART_FIXED_MAX,max_price+offset_max_min);
   ChartSetDouble(0,CHART_FIXED_MIN,min_price-offset_max_min);
//--- Actualizamos el gráfico
   ChartRedraw();
  }

La función descrita más arriba la usaremos cuando procesemos los eventos al arrastrar la línea vertical (y, por supuesto, al calcular los valores del indicador en OnCalculate):

//+------------------------------------------------------------------+
//| ChartEvenSit function                                            |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Eventos al arrastrar objetos gráficos
   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      //--- Si ahora tenemos el modo de línea vertical para el punto inicial de la divergencia de precios, actualizamos los búfers de indicador
      if(StartPriceDivergence==VERTICAL_LINE)
         OnCalculate(OC_rates_total,
                     0,
                     OC_time,
                     OC_open,
                     OC_high,
                     OC_low,
                     OC_close,
                     OC_tick_volume,
                     OC_volume,
                     OC_spread);
     }
//--- Cambio de tamaño del gráfico o cambio de propiedades del gráfico a través del diálogo de propiedades
   if(id==CHARTEVENT_CHART_CHANGE)
      //--- Corregimos el máximo y el mínimo del gráfico con respecto a los valores en los búfers de indicador
      CorrectChartMaxMin();
  }

Todas las funciones están listas. Puede familiarizarse más a fondo con el código detallado y comentado gracias al anexo a este artículo.

Vamos a mostrar qué ha resultado al final. Por defecto, en los parámetros externos están establecidos los símbolos GBPUSD, AUDUSD, NZDUSD, USDCAD, USDCHF. En la captura de pantalla mostramos el gráfico semanal de EURUSD en el modo Línea vertical, en la versión desconectada:

Time frame semanal en el modo "Línea vertical"

Dib. 1 - Time frame semanal en el modo "Línea vertical"

En la captura de pantalla de más abajo tenemos un time frame de 30 minutos, en el modo Día, pero sólo por esta vez, con la divisa de base USD tenemos conectado el modo de inversión. En este caso, se trata de USDCAD (vela celeste claro) y USDCHF (vela violeta).

Time frame de 30 minutos en el modo "Día"

Dib. 2 - Time frame de 30 minutos en el modo "Día"


Conclusión

Creo que nos ha salido un instrumento bastante interesante e informativo para el análisis multidivisa de la divergencia de precios. Además, el indicador aún se puede mejorar de manera ilimitada.

¡Gracias por su atención!

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/754

Archivos adjuntos |
Recetas MQL5 - Asesor multidivisa y funcionamiento de órdenes pendientes en MQL5 Recetas MQL5 - Asesor multidivisa y funcionamiento de órdenes pendientes en MQL5
En esta ocasión veremos la creación de un asesor multidivisa, cuyo algoritmo de comercio será construido para trabajar con las órdenes pendientes Buy Stop y Sell Stop. En el artículo estudiaremos las siguientes cuestiones: el comercio en un diapasón temporal indicado, cómo establecer/modificar/eleminar órdenes pendientes, la comprobación de la última posición sobre Take Profit o Stop Loss y el control del historial de operaciones en cada símbolo.
Fundamentos de programación en MQL5 - Cadenas de caracteres Fundamentos de programación en MQL5 - Cadenas de caracteres
Este artículo se ocupa de todo lo que se puede hacer con las cadenas de caracteres en el lenguaje MQL5. El artículo puede ser interesante en primer lugar para los principiantes que se han puesto a estudiar la programación en MQL5. Mientras que los programadores experimentados tienen una buena oportunidad de generalizar y sistematizar sus conocimientos.
Cuentos de robots comerciales: ¿mejor poco, pero mejor? Cuentos de robots comerciales: ¿mejor poco, pero mejor?
Hace dos años, en el artículo "La última cruzada" usted y yo, querido lector, vimos juntos un método (bastante interesante y poco usado en la actualidad) de representación de la información en el mercado, el gráfico de punto y forma. Ahora le propongo intentar escribir un robot comercial, basado en patrones que se pueden ver en los gráficos de punto y forma.
SQL y MQL5: Trabajando con la base de datos SQLite SQL y MQL5: Trabajando con la base de datos SQLite
El presente artículo va dirigido a programadores a los que les interesa el uso de SQL en sus proyectos. En el mismo, presentamos a los lectores la funcionalidad de SQLite y sus ventajas. El artículo no exige de conocimientos previos de SQLite, pero si sería de agradecer un conocimiento mínimo de SQL.