Recetas MQL5 - Desarrollo de un indicador multidivisa para el análisis de la divergencia de precios
Anatoli Kazharski | 29 abril, 2014
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:
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).
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!