Indicador para la representación del gráfico Kagi

Dmitriy Zabudskiy | 7 mayo, 2014

Introducción

En el artículo "Indicador para la representación del gráfico de punto y figura" se ha descrito uno de los métodos de programación para la creación del gráfico de punto y figura. Este gráfico es conocido desde el siglo XIX. Sin embargo, este no es el único gráfico que nos ha llegado de aquella lejana época. Otra herramienta importante de los primeros tipos de representación del mercado financiero es el gráfico Kagi. Este gráfico va a ser el tema de este artículo.

La bolsa de valores -institución financiera desconocido en Japón del siglo XIX- se fundó en mayo de 1878. Hoy en día es conocida como la Bolsa de Tokio. Este acontecimiento tuvo un papel fundamental en la creación y desarrollo posterior de los gráficos Kagi. Europa y Estados Unidos llegaron a conocer los gráficos Kagi tras la publicación de "Beyond Candlesticks: New Japanese Charting Techniques Revealed" por Steve Nison en 1994.

La palabra japonesa "Kagi" significa una llave en forma de L que estaba en uso durante la época del desarrollo del gráfico. Además, hay una versión modificada del nombre; "gráfico de llave". En la publicación "Beyond Candlesticks" (Más allá de las velas) de Steve Nison, también puede encontrar nombres alternativos del gráfico: gráfico de rango de precios, gráfico de gancho (hook), gráfico delta o cadena.

¿Qué tiene de particular este gráfico? Su principal característica es que ignora la línea de tiempo dejando sólo el precio (a diferencia de las velas japonesas, barras y líneas). De modo que el gráfico oculta las pequeñas fluctuaciones de los precios, dejando solamente las más significativas.

El gráfico representa un conjunto de líneas Yang gruesas y líneas Yin finas que se alternan en función de la situación del mercado. Si el mercado se mueve en la misma dirección, se extiende la línea, alcanzando un nuevo rango de precios. Sin embargo, si el mercado se vuelve hacia atrás y alcanza una cantidad predefinida, se dibuja la línea de Kagi en la dirección opuesta en la nueva columna. Se establece la cantidad predefinida en puntos (se usan generalmente para los pares de divisas), o en el valor porcentual del precio actual (se usa generalmente para los acciones). El grosor de la línea varía según el avance (breakthrough) más cercano de máximo o mínimo.


1. Ejemplo de representación gráfica

Vamos a utilizar los datos del historial de EURUSD, H1 entre el 8 y el 11 de octubre.

En la figura 1 se muestra un ejemplo estándar con un umbral de inversión de 15 puntos:

Gráfico Kagi, EURUSD H1

Fig. 1. Gráfico Kagi, EURUSD H1

Como podemos observar, comenzó a bajar el precio comenzó a las 17:00. El movimiento bajista duró hasta las 21:00. A las 22:00, se mueve el precio hacia arriba desde 1.3566 y se ha cerrado a 1.3574. En otras palabras, el precio sube en 11 puntos. Esto no es suficiente para una inversión, pero el nuevo mínimo tampoco se ha alcanzado. El precio se desinfla durante las siguientes dos horas y, finalmente, a la 01:00 (9 de octubre), se observa un fuerte movimiento al alza, con un cierre a 1.3591 con 25 puntos (1.3591-1.3566). Esto significa que hay una inversión del precio.

La tendencia alcista continúa durante la siguiente hora. El precio alcanza 1.3599, reforzando la línea Yang gruesa. A las 3:00, los precios caen de forma súbita, cerrando a 1.3578, es decir, 21 puntos desde el Máximo anterior (1.3599-1.3578). Esto es más que suficiente para la inversión. La línea se mueve hacia abajo, pero conserva su forma (línea gruesa de Yang).

Hasta las 16:00, el precio se mueve hacia abajo y finalmente sobrepasa el mínimo más cercano y cambia de la línea Yang gruesa a la línea Yin fina. El valor mínimo de 1.3566 mencionado antes había servido en este caso como un precio de avance (breakthrough). El precio se sigue moviendo como una línea Yin y se cambia a Yang a las 14:00 el 10 de octubre, rompiendo el máximo más cercano de 1.3524 formado a las 23:00 (9 de octubre). Este pequeño ejemplo muestra cómo se forma el gráfico Kagi.


2. Principio de representación gráfica del indicador Kagi

Para conseguir que el indicador sea independiente del período de tiempo actual, se decidió copiar los datos del período de tiempo, en el cual se suponía que tenía que formarse el indicador, por separado y luego crear el indicador utilizando los datos obtenidos.

Esto permite examinar varios períodos de tiempo simultáneamente en un solo gráfico, ampliando los límites del análisis técnico en los gráficos Kagi. El indicador en sí se encuentra en una ventana independiente, pero también se pueden mostrar los datos en el gráfico principal. En otras palabras, la formación básica (aspecto estándar o modificado) se lleva a cabo en la ventana del indicador. Se copia el indicador en el cuadro principal, también se dibujan las etiquetas del precio y el tiempo (dependiendo de la configuración).

Como se mencionó anteriormente, el indicador dibuja el gráfico tanto en la versión estándar como en la versión modificada. La versión estándar se describió antes. Ahora, vamos a ver la versión modificada.

No sé si es una idea nueva, pero no he oído hablar de tal versión. La idea del filtro adicional consiste en que ahora no se filtran solamente los puntos de inversión, sino cada movimiento del gráfico. En otras palabras, el precio debe moverse en una distancia especificada para que se pueda formar el nuevo Máximo o Mínimo (que no debe confundirse con hombro / cintura). En general, sin importar como se mueve el precio, primero debe cubrir una distancia especificada. Después de eso, se determina si se trata de una continuación de la tendencia o de una inversión.

La figura 2 muestra el funcionamiento de este principio. Se muestra el gráfico modificado en azul, mientras que el estándar se muestra en rojo. Como podemos observar, el aspecto modificado responde a cambios en el movimiento del precio, filtrando más lentamente la mayoría de las señales de menor importancia.

Versión estándar y modificada de la generación del gráfico Kagi

Fig. 2. Versión modificada (línea azul) y estándar (línea roja) de la generación del gráfico Kagi

Además del gráfico Kagi, el indicador proporciona algunos elementos adicionales, tanto en la ventana del indicador como en el gráfico principal.

Dependiendo de la configuración, se pueden poner las etiquetas en la ventana del indicador. Estas etiquetas proporcionan datos sobre los precios de inversión. Se implementa la misma función mediante los niveles del precio, que (dependiendo de la configuración) se pueden distribuir uniformemente a lo largo de la ventana en todo el rango de precios utilizado para formar el indicador o en cada inversión del gráfico. Se pueden ajustar los colores de tres maneras: según el tipo de inversión (arriba - abajo), el tipo de línea (Yin - Yang) o ausencia de cambio de color.

Se proporcionan las etiquetas del precio de inversión, incluidas las temporales, en el gráfico principal. Estas etiquetas (dependiendo de la configuración) pueden ser de un solo color o cambiar el color de acuerdo con los colores de las líneas Yin o Yang.

Se implementa todo el código del indicador mediante las funciones que se comunican entre sí a través de las variables globales.

Se puede dividir el código en tres funciones principales y once adicionales. Se apoya la carga principal para calcular y rellenar los buffers de los trazados gráficos básicos y las matrices de los buffers adicionales en la función de la formación del gráfico Kagi en la ventana del indicador. Las otras dos funciones se encargan de proporcionar los datos: la primera copia los datos del tiempo, mientras que la otra copia de los datos de los precios de cada barra del período de tiempo seleccionado.

Las funciones adicionales restantes se encargan de llevar a cabo todos los trazados, la eliminación de los objetos, la descarga del indicador junto con la eliminación de todos los objetos del mismo, el cálculo de los parámetros de inversión, el dibujo de etiquetas en el gráfico principal y en la ventana del indicador, la creación de objetos gráficos de tipo "línea de tendencia", el dibujo de Kagi en el gráfico principal, así como la definición de la llegada de la nueva barra para iniciar la formación del indicador.


3. El Código y el algoritmo del indicador

Ahora, vamos a examinar el código del indicador y el algoritmo de su formación en detalle. El código es bastante grande y puede ser bastante difícil de entender para los programadores novatos. Las funciones que se comunican entre sí a través de las variables globales hacen que el código sea muy confuso. En esta parte del artículo, voy a explicar cada función y parte del código por separado. En primer lugar, voy a describir los ajustes del indicador y luego aportaré más detalles relativos a las funciones iniciales de copia de datos, el cálculo de los parámetros de inversión, la función principal de la formación y cálculo del gráfico Kagi y otras funciones adicionales.

3.1. Parámetros de entrada del indicador

Se inicia el código con la declaración del indicador en una ventana separada, así como 12 buffers y 8 construcciones gráficas del indicador. En primer lugar, vamos a definir qué 8 construcciones gráficas, incluyendo dos "histogramas" y seis "líneas", se han utilizado. Cada "histograma" construye su propia línea vertical. Una de las líneas se encarga de la línea de Yin, mientras que la otra es para la línea Yang.

Es algo más complicado con las "líneas", ya que hay tres para cada línea. Esto se hace por el hecho de que se dibuja la línea si hay otro punto que dibujado cerca de la primera. Es decir, necesitamos girar solamente dos trazados gráficos de tipo "línea" para dibujar dos líneas adyacentes entre sí. Sin embargo, si queremos que estas líneas omitan los puntos necesarios, nos hará falta girar el tercer trazado con los otros dos.

Se explica esto en la figura 3, donde se puede ver lo que sucede si se usan sólo dos trazados gráficos de tipo "línea":


 Fig. 3. Ejemplo de uso de dos y tres trazados gráficos de tipo "línea" para mostrar las líneas de los hombros y de las cinturas

A continuación, se crea el menú de los ajustes. En este caso, hay cinco enumeraciones (vamos a examinarlas en los parámetros de entrada).

El primer parámetro de entrada "period" es un período, en el cual se lleva a cabo el trazado, es seguido por "period_to_redraw" es el período de actualización del trazado del gráfico y el último parámetro "start_data" es el instante a partir del cual comienza el trazado.

Estos parámetros son seguidos por el trazado del gráfico y por los parámetros de etiquetado adicionales:

Estos parámetros son, a su vez seguidos por las declaraciones de buffers de indicadores, buffers adicionales para almacenar los valores del precio y el tiempo, variables auxiliares (stop_data, bars_copied, bars_copied_time, copy_history, copy_time), matrices para almacenar los datos acerca de la línea Yin o Yang en la cual se ha producido un cambio de movimiento en el gráfico, el tiempo y el precio de este cambio y el precio central (si se sustituye Yin por Yang en la barra o viceversa). Por último, se declara una de las variables globales más usadas que contiene datos acerca del número de cambios en el movimiento del gráfico "a".

//+------------------------------------------------------------------+
//|                                                         BKCV.mq5 |
//|                                   Azotskiy Aktiniy ICQ:695710750 |
//|                          https://www.mql5.com/ru/users/Aktiniy |
//+------------------------------------------------------------------+
//--- Build Kagi Chart Variable
#property copyright "Azotskiy Aktiniy ICQ:695710750"
#property link      "https://www.mql5.com/en/users/Aktiniy"
#property version   "1.00"
#property description "Build Kagi Chart Variable"
#property description " "
#property description "This indicator makes drawing a chart Kagi as a matter of indicator window, and in the main chart window"
#property indicator_separate_window
#property indicator_buffers 12
#property indicator_plots   8
//--- plot Yin
#property indicator_label1  "Yin"
#property indicator_type1   DRAW_HISTOGRAM2
#property indicator_color1  clrRed
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot Yin1
#property indicator_label2  "Yin1"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrRed
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Yin2
#property indicator_label3  "Yin2"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
#property indicator_style3  STYLE_SOLID
#property indicator_width3  1
//--- plot Yin3
#property indicator_label4  "Yin3"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrRed
#property indicator_style4  STYLE_SOLID
#property indicator_width4  1
//--- plot Yang
#property indicator_label5  "Yang"
#property indicator_type5   DRAW_HISTOGRAM2
#property indicator_color5  clrRed
#property indicator_style5  STYLE_SOLID
#property indicator_width5  2
//--- plot Yang1
#property indicator_label6  "Yang1"
#property indicator_type6   DRAW_LINE
#property indicator_color6  clrRed
#property indicator_style6  STYLE_SOLID
#property indicator_width6  2
//--- plot Yang2
#property indicator_label7  "Yang2"
#property indicator_type7   DRAW_LINE
#property indicator_color7  clrRed
#property indicator_style7  STYLE_SOLID
#property indicator_width7  2
//--- plot Yang3
#property indicator_label8  "Yang3"
#property indicator_type8   DRAW_LINE
#property indicator_color8  clrRed
#property indicator_style8  STYLE_SOLID
#property indicator_width8  2
//--- Enumerations as input data (for more attractive setting)
//--- Kagi charting type
enum kagi_type_enum
  {
   classic=0,  // Classic
   modified=1, // Modified
  };
//--- Type of the price used for construction
enum price_type_enum
  {
   c=0, // Close
   o=1, // Open
   h=2, // High
   l=3, // Low
  };
//--- Type of the used reversal
enum type_doorstep_enum
  {
   point=0,   // Point
   procent=1, // Percent
  };
//--- Type of levels location
enum levels_type_enum
  {
   cor=0, // Cornering
   equ=1, // Equal distance
  };
//--- Level colors change type (works when "Type of levels location"="Cornering")
enum levels_change_color_enum
  {
   up_down=0,  // Up & Down
   yin_yang=1, // Yin & Yang
   no=2,       // Don't change
  };
//--- input parameters
input ENUM_TIMEFRAMES period=PERIOD_CURRENT;                // Calculation period to build the chart
input ENUM_TIMEFRAMES period_to_redraw=PERIOD_M1;           // Refresh period chart
input datetime start_data=D'2013.07.10 00:00:00';           // Start time to build the chart
input kagi_type_enum kagi_type=classic;                     // The type to build Kagi chart
input price_type_enum price_type=c;                         // Price used to build chart
input type_doorstep_enum type_doorstep=point;               // Type calculate doorstep
input double   doorstep=25;                                 // Doorstep reversal
input color    color_yin=clrRed;                            // Color Yin line (indicator window)
input color    color_yang=clrRed;                           // Color Yang line (indicator window)
input char     width_yin=1;                                 // Width Yin line (indicator window)
input char     width_yang=2;                                // Width Yang line (indicator window)
input bool     levels_on_off=false;                         // Draw level (indicator window)
input levels_type_enum levels_type=cor;                     // Type of drawing levels (indicator window)
input uint     levels_number=6;                             // Number of levels  (indicator window)
input levels_change_color_enum levels_change_color=up_down; // Type change color of levels (indicator window)
input color    levels_first_color=clrBeige;                 // The first color of level (indicator window)
input color    levels_second_color=clrCoral;                // The second color of level (indicator window)
input bool     label_1=true;                                // Draw price label on (indicator window)
input uint     label_1_number=10;                           // The number of labels (indicator window)
input color    label_1_color=clrGreenYellow;                // The color of labels (indicator window)
input bool     label_2=true;                                // Draw price label on (main chart)
input color    label_2_color=clrGreenYellow;                // The color of labels (main chart)
input bool     time_line_draw=true;                         // Draw a timeline reversal (main chart)
input bool     time_separate_windows=false;                 // Draw a timeline reversal on indicator window
input bool     time_line_change_color=true;                 // Different color timeline on the Yin and Yang lines (main chart)
input color    time_first_color=clrRed;                     // The first color of timeline (main chart)
input color    time_second_color=clrGreenYellow;            // The second color of timeline (main chart)
input bool     kagi_main_chart=true;                        // Draw Kagi on main chart (main chart)
input color    color_yin_main=clrRed;                       // Color Yin line (main chart)
input color    color_yang_main=clrRed;                      // Color Yang line (main chart)
input char     width_yin_main=1;                            // Width Yin line (main chart)
input char     width_yang_main=2;                           // Width Yang line (main chart)
input long     magic_numb=65758473787389;                   // The magic number for drawing objects
//--- indicator buffers
double         YinBuffer1[];
double         YinBuffer2[];
double         Yin1Buffer[];
double         Yin2Buffer[];
double         Yin3Buffer[];
double         YangBuffer1[];
double         YangBuffer2[];
double         Yang1Buffer[];
double         Yang2Buffer[];
double         Yang3Buffer[];
//--- additional variables
double Price[]; // Buffer for storing the copied price data
double Time[];  // Buffer for storing the copied time data
//---
datetime stop_data;      // Current time
int bars_copied=0;       // Number of the already copied bars from the initial date
int bars_copied_time;    // Number of the already copied bars having the initial date
bool copy_history=false; // Price history copying result
bool copy_time=false;    // Time history copying result
//---
datetime time_change[];      // Array for writing the time when the chart movement started changing (up or down)
char time_line[];            // Array for storing the data on what line (Yin=0 or Yang=1) direction has changed
double time_change_price[];  // Array for writing the chart movement change price
double time_central_price[]; // Array for writing the average price during the chart movement change

uint a=0; // Variable for building the chart, number of chart reversals is fixed

  

3.2. Función de inicialización del indicador

Lo siguiente es la función de inicialización del indicador. Los buffers del indicador y su indexación (principalmente como series temporales; ya que el gráfico Kagi es más corto que el principal, es mejor dibujarlo al revés) se especifican en esta función, además del establecimiento de los valores que no se van a mostrar en la pantalla (EMPTY_VALUE = -1).

Ahora, asignamos el nombre del indicador y la precisión de su representación. Como se mencionó antes, se añade el número mágico al nombre. Esto se hace para permitir un funcionamiento correcto de la función ChartWindowFind(). De lo contrario, el objeto gráfico dibujado en la ventana del indicador se muestra solamente en el primer indicador ejecutado (en caso de utilizar varios indicadores en un solo gráfico).

A continuación, asignamos los nombres a las líneas del trazado, inhabilitamos la visualización de los actuales valores numéricos en la ventana del indicador, fijamos el color y el grosor de las líneas Yin y Yang y establecemos el número de niveles del precio que se muestran en la ventana del indicador.

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,YinBuffer1,INDICATOR_DATA);
   ArraySetAsSeries(YinBuffer1,true);
   SetIndexBuffer(1,YinBuffer2,INDICATOR_DATA);
   ArraySetAsSeries(YinBuffer2,true);
   SetIndexBuffer(2,Yin1Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin1Buffer,true);
   SetIndexBuffer(3,Yin2Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin2Buffer,true);
   SetIndexBuffer(4,Yin3Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yin3Buffer,true);
//---
   SetIndexBuffer(5,YangBuffer1,INDICATOR_DATA);
   ArraySetAsSeries(YangBuffer1,true);
   SetIndexBuffer(6,YangBuffer2,INDICATOR_DATA);
   ArraySetAsSeries(YangBuffer2,true);
   SetIndexBuffer(7,Yang1Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang1Buffer,true);
   SetIndexBuffer(8,Yang2Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang2Buffer,true);
   SetIndexBuffer(9,Yang3Buffer,INDICATOR_DATA);
   ArraySetAsSeries(Yang3Buffer,true);
//--- add the buffer for copying data on prices for calculation
   SetIndexBuffer(10,Price,INDICATOR_CALCULATIONS);
//--- add the buffer for copying data on bar open time for construction
   SetIndexBuffer(11,Time,INDICATOR_CALCULATIONS);

//--- set what values are not to be drawn
   for(char x=0; x<8; x++)
     {
      PlotIndexSetDouble(x,PLOT_EMPTY_VALUE,-1);
     }
//--- set the indicator's look
   IndicatorSetString(INDICATOR_SHORTNAME,"BKCV "+IntegerToString(magic_numb)); // Indicator name
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits); // Display accuracy
//--- assign names to graphical constructions
   PlotIndexSetString(0,PLOT_LABEL,"Yin");
   PlotIndexSetString(1,PLOT_LABEL,"Yin");
   PlotIndexSetString(2,PLOT_LABEL,"Yin");
   PlotIndexSetString(3,PLOT_LABEL,"Yin");
   PlotIndexSetString(4,PLOT_LABEL,"Yang");
   PlotIndexSetString(5,PLOT_LABEL,"Yang");
   PlotIndexSetString(6,PLOT_LABEL,"Yang");
   PlotIndexSetString(7,PLOT_LABEL,"Yang");
//--- prohibit display of the results of the current values for graphical constructions
   PlotIndexSetInteger(0,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(1,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(2,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(3,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(4,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(5,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(6,PLOT_SHOW_DATA,false);
   PlotIndexSetInteger(7,PLOT_SHOW_DATA,false);
//--- set color for Yin line
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(1,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(2,PLOT_LINE_COLOR,color_yin);
   PlotIndexSetInteger(3,PLOT_LINE_COLOR,color_yin);
//--- set color for Yang line
   PlotIndexSetInteger(4,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(5,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(6,PLOT_LINE_COLOR,color_yang);
   PlotIndexSetInteger(7,PLOT_LINE_COLOR,color_yang);
//--- set Yin line width
   PlotIndexSetInteger(0,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(1,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(2,PLOT_LINE_WIDTH,width_yin);
   PlotIndexSetInteger(3,PLOT_LINE_WIDTH,width_yin);
//--- set Yang line width
   PlotIndexSetInteger(4,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(5,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(6,PLOT_LINE_WIDTH,width_yang);
   PlotIndexSetInteger(7,PLOT_LINE_WIDTH,width_yang);
//--- set the number of levels in the indicator window
   IndicatorSetInteger(INDICATOR_LEVELS,levels_number);
//---
   return(INIT_SUCCEEDED);
  }


3.3. Función para copiar los datos

Ahora, vamos a analizar las funciones para copiar los datos.

En este caso, tenemos dos. La primera es para copiar los precios, mientras que la segunda es para copiar el tiempo de apertura de cada barra. Ambas funciones conservan sus valores en los buffers de cálculo del indicador declarados previamente.

En primer lugar, vamos a considerar la función que copia los precios. Los parámetros de entrada de la función: matriz de almacenamiento de datos, tiempo de inicio y de finalización de la copia de los datos (tiempo actual). El cuerpo de la función contiene las variables para dar respuesta a la función, se copia el número de datos (barras) en la matriz intermedia, la propia matriz intermedia dinámica y el número de barras que hay que copiar a la matriz intermedia. Se calcula el número de barras en función del número total de barras en el período de tiempo determinado y el número de barras (variable global) que fueron copiados en la llamada anterior a la función.

Si no es la primera vez que se copian los datos, se deben actualizar los datos de la última barra copiada. Para ello, restamos uno al número de barras copiadas y añadimos uno al número de barras recién copiadas. También cambiamos el tamaño de la matriz intermedia, preparándola para copiar las barras.

Dependiendo de la configuración, copiamos los precios a la matriz intermedia. Si la copia se hace correctamente, se copian los datos desde la matriz intermedia hasta el final de la matriz del buffer (matriz de la respuesta de la función), se asigna la respuesta positiva a la función y se actualiza la variable global almacenando los datos acerca del número de barras copiadas. Este tipo de copia permite copiar solamente unas cuantas de las últimas barras, reduciendo el tiempo dedicado a copiar los datos.

//+------------------------------------------------------------------+
//| Func Copy History                                                |
//+------------------------------------------------------------------+
bool func_copy_history(double &result_array[],
                       datetime data_start,
                       datetime data_stop)
  {
//---
   int x=false; // Variable for answer

   int result_copy=-1; // Number of copied data

   static double price_interim[]; // Temporary dynamic array for storing copied data
   static int bars_to_copy;       // Number of bars for copying

   bars_to_copy=Bars(_Symbol,period,data_start,data_stop); // Find out the current number of bars on the time interval
   bars_to_copy-=bars_copied; // Calculate the number of bars to be copied

   if(bars_copied!=0) // If it is not the first time the data has been copied
     {
      bars_copied--;
      bars_to_copy++;
     }

   ArrayResize(price_interim,bars_to_copy); // Change the size of the receiving array

   switch(price_type)
     {
      case 0:
         result_copy=CopyClose(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 1:
         result_copy=CopyOpen(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 2:
         result_copy=CopyHigh(_Symbol,period,0,bars_to_copy,price_interim);
         break;
      case 3:
         result_copy=CopyLow(_Symbol,period,0,bars_to_copy,price_interim);
         break;
     }

   if(result_copy!=-1) // If copying to the intermediate array is successful
     {
      ArrayCopy(result_array,price_interim,bars_copied,0,WHOLE_ARRAY); // Copy the data from the temporary array to the main one
      x=true; // assign the positive answer to the function
      bars_copied+=result_copy; // Increase the value of the processed data
     }
//---
   return(x);
  }

La siguiente función es la de copiar los datos del tiempo. Es diferente de la anterior por el hecho de que trata con otro tipo variable; datetime (que se convierte en doble cuando se copia en la matriz del buffer del tiempo; la matriz de la respuesta de la función). Otra diferencia es que no se usa la instrucción switch(), ya que no hace falta seleccionar los datos copiados.

//+------------------------------------------------------------------+
//| Func Copy Time                                                   |
//+------------------------------------------------------------------+
bool func_copy_time(double &result_array[],
                    datetime data_start,
                    datetime data_stop)
  {
//---
   int x=false; // Variable for answer
   int result_copy=-1; // Number of copied data

   static datetime time_interim[]; // Temporary dynamic array for storing copied data
   static int bars_to_copy_time; // Number of bars for copying

   bars_to_copy_time=Bars(_Symbol,period,data_start,data_stop); // Find out the current number of bars on the time interval
   bars_to_copy_time-=bars_copied_time; // Calculate the number of bars to be copied

   if(bars_copied_time!=0) // If it is not the first time the data has been copied
     {
      bars_copied_time--;
      bars_to_copy_time++;
     }
   ArrayResize(time_interim,bars_to_copy_time); // Change the size of the receiving array
   result_copy=CopyTime(_Symbol,period,0,bars_to_copy_time,time_interim);

   if(result_copy!=-1) // If copying to the intermediate array is successful
     {
      ArrayCopy(result_array,time_interim,bars_copied_time,0,WHOLE_ARRAY); // Copy the data from the temporary array to the main one
      x=true; // assign the positive answer to the function
      bars_copied_time+=result_copy; // Increase the value of the processed data
     }
//---
   return(x);
  }

3.4. Función para el cálculo del parámetro de inversión

Puesto que el parámetro de inversión puede ser un punto o un porcentaje, necesitamos la función que calcula el parámetro de inversión dependiendo de la configuración del indicador. La función tiene un solo parámetro; el precio para calcular el porcentaje de inversión. Primero se inicializa la variable de la respuesta mediante el tipo double (doble) y después de los cálculos, se convierte indirectamente al tipo int (entero) para la respuesta.

Se hace esto porque en los cálculos se usan los números de coma flotante, mientras que se debe presentar la respuesta en forma de enteros. Se implementa la selección en la función mediante la instrucción condicional if-else. Se lleva a cabo la comparación directamente con la variable externa input (parámetros del indicador). Se lleva a cabo el cálculo de los puntos mediante una ecuación simple. En primer lugar, se define el número total de puntos que ha enviado el precio. A continuación, se calcula el porcentaje especificado en base a este número y se asigna a la variable devuelta.

//+------------------------------------------------------------------+
//| Func Calculate Doorstep                                          |
//+------------------------------------------------------------------+
int func_calc_dorstep(double price)
  {
   double x=0; // Variable for answer
   if(type_doorstep==0) // If the calculation is to be performed in points
     {
      x=doorstep;
     }
   if(type_doorstep==1) // If the calculation is to be performed in percentage
     {
      x=price/_Point*doorstep/100;
     }
   return((int)x);
  }

3.5. La función principal: Dibujar el gráfico Kagi

Ya hemos examinado todas las funciones necesarias para el funcionamiento de la función principal; dibujar el gráfico Kagi en la ventana del indicador (es decir, rellenar los buffers del indicador). Los parámetros de entrada de la función consisten en matrices de datos. Dos de ellas son los buffers de cálculo descritos anteriormente (precio y tiempo copiados previamente), el resto son las matrices de los buffers de trazado del indicador.

Se declaran las variables necesarias para almacenar los datos en el trazado del gráfico de la función. Puesto que el gráfico se construye mediante el operador de bucle for, deberíamos disponer de los datos de la etapa de la pasada anterior finalizada. Se puede lograr esto mediante seis variables: line_move; a donde se movió el precio en la pasada anterior, line_gauge; calibre de línea (grosor de línea) -Yin o Yang, price_1 y price_2; precio anterior y actual considerado, price_down y price_up; precio anterior de hombro y cintura. Como podemos observar, se equipara price_1 inmediatamente al primer elemento de la matriz de precios copiado debido a que esta variable está involucrada en los cálculos antes de la comparación desde el principio del bucle.

Ya que las matrices de los buffers del trazado gráfico del indicador tienen el flag de indexación AS_SERIES, hay que rellenarlos en orden inverso. Para ello, se implementan matrices de tiempo con el tamaño adecuado. Las variables globales para el almacenamiento de los datos sobre del tiempo, los tipos de línea, "hombro" y "cintura", así como los precios de inversión se convierten después del mismo modo.

A continuación, hay que rellenar todas las matrices con valores "vacíos" (-1). Esto se hace mediante dos pequeños bucles. Es posible juntar todo en un solo bucle. Pero el uso de los dos hace más claras todas las acciones llevadas a cabo, sin cambiar mucho el tiempo de ejecución. En otras palabras, se rellenan los buffers gráficos y las matrices temporales del cálculo por separado.

Ahora, todas las variables están declaradas, convertidas y rellenas, de modo que se puede ejecutar el bucle principal. Es bastante grande (aunque el cálculo se realiza con la suficiente rapidez) e incluye el análisis de todas las barras previamente copiadas.

El bucle recorre todas las barras copiadas y rellena las matrices necesarias previamente declaradas para seguir trabajando con ellas. En primer lugar, vamos a definir todas las matrices utilizadas en el bucle:

Se asigna el valor del precio actual analizado desde el buffer Price a la variable price_2 antes de cada pasada del bucle para su posterior comparación en las instrucciones condicionales if-else. Después, se analiza la matriz del buffer de los datos copiados paso a paso y se rellenan las matrices mencionadas anteriormente. Cada instrucción condicional if-else lleva a cabo ciertas acciones dependiendo de las condiciones: dirección anterior de las líneas del gráfico (arriba o abajo) y el aspecto anterior de las líneas (Yin o Yang). A continuación, se comprueban las condiciones del movimiento (si el precio ha pasado de un cierto número de puntos) en función del tipo de trazado (estándar o modificado).

Si todo va bien, se reasignan o definen las nuevas variables (elementos de la matriz). Se define el tipo de línea (Yin o Yang) desde el principio. Dependiendo del movimiento y de las acciones anteriores, se lleva a cabo la distribución posterior.

Hay dos posibles movimientos del precio:

  1. El precio se mueve hacia arriba;
  2. El precio se mueve hacia abajo.

Hay también cuatro tipos de acciones previas en cada dirección:

  1. La línea anterior era Yin y se mueve hacia arriba;
  2. La línea anterior era Yang y se mueve hacia arriba;
  3. La línea anterior era Yin y se mueve hacia abajo;
  4. La línea anterior era Yang y se mueve hacia abajo.

Por tanto, tenemos ocho casos, aparte de las dos primeras definiciones del movimiento inicial del gráfico (apariencia de la primera línea).

Después de eso, se da por finalizado el bucle principal. Se lleva a cabo la reasignación (inversión) y el rellenado de los buffers para construir el gráfico en un bucle más pequeño que consiste en el número de inversiones del gráfico Kagi definidas previamente en el bucle principal y escritas en la variable "a". En cuanto a la distribución de los valores del precio máximo y mínimo y las líneas verticales, todo es muy sencillo: se lleva a cabo una simple inversión. En otras palabras, se asignan los valores primarios obtenidos previamente (matrices con índices 0, 1, 2, 3 ...) a los valores finales de los buffers (se utiliza un elemento con el índice "а", es decir, а, а-1, а-2, а-3 ... como valor final). Para evitar que se peguen las líneas de inversión (horizontales) entre sí, se lleva a cabo una rotación mediante la instrucción switch como se mencionó anteriormente.

Con esto se da por finalizado el trabajo de la función principal para el trazado del gráfico Kagi.

//+------------------------------------------------------------------+
//| Func Draw Kagi                                                   |
//+------------------------------------------------------------------+
void func_draw_kagi(double &array_input[],
                    double &arr_yin_1[],
                    double &arr_yin_2[],
                    double &arr_yin_lin1[],
                    double &arr_yin_lin2[],
                    double &arr_yin_lin3[],
                    double &arr_yang_1[],
                    double &arr_yang_2[],
                    double &arr_yang_lin1[],
                    double &arr_yang_lin2[],
                    double &arr_yang_lin3[],
                    double &arr_time[])
  {
//---
   a=0; // Variable for the chart construction fixing the number of chart reversals
   char line_move=0; // Previous price direction 1-up, -1-down
   char line_gauge=0; // Previous look of the line 1-thick yang, -1-thin yin
   double price_1=0,price_2=0; // Auxiliary variables for defining the price movement
   double price_down=-99999,price_up=99999; // Auxiliary variables for storing the reversal price values
   price_1=array_input[0];
//--- auxiliary arrays for the initial data storing before the reversal (transferring to the buffers)
   double yin_int_1[];
   double yin_int_2[];
   double lin_yin[];
   double yang_int_1[];
   double yang_int_2[];
   double lin_yang[];
//--- change the sizes of dynamic arrays
   ArrayResize(yin_int_1,bars_copied);
   ArrayResize(yin_int_2,bars_copied);
   ArrayResize(yang_int_1,bars_copied);
   ArrayResize(yang_int_2,bars_copied);
   ArrayResize(lin_yin,bars_copied);
   ArrayResize(lin_yang,bars_copied);
//--- time data storing arrays
   ArrayResize(time_change,bars_copied_time);
   ArrayResize(time_line,bars_copied_time); // Look of the line Yin = 0 or Yang = 1
   ArrayResize(time_change_price,bars_copied_time);
   ArrayResize(time_central_price,bars_copied_time);
//--- assign -1 (not displayed) value to the transferred buffers
   for(int z=0; z<bars_copied; z++)
     {
      arr_yin_1[z]=-1;
      arr_yin_2[z]=-1;
      arr_yin_lin1[z]=-1;
      arr_yin_lin2[z]=-1;
      arr_yin_lin3[z]=-1;
      arr_yang_1[z]=-1;
      arr_yang_2[z]=-1;
      arr_yang_lin1[z]=-1;
      arr_yang_lin2[z]=-1;
      arr_yang_lin3[z]=-1;
     }
//--- equate -1 (not displayed) value to the arrays
   for(int z=0; z<bars_copied; z++)
     {
      yin_int_1[z]=-1;
      yin_int_2[z]=-1;
      lin_yin[z]=-1;
      yang_int_1[z]=-1;
      yang_int_2[z]=-1;
      lin_yang[z]=-1;
      time_change[z]=-1;
      time_line[z]=-1;
      time_change_price[z]=-1;
      time_central_price[z]=-1;
     }
//--- function's main loop
   for(int z=0; z<bars_copied; z++)
     {
      price_2=array_input[z];
      //--- first, let's define the initial market direction
      //--- first THIN DESCENDING line
      if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) && line_move==0)
        {
         yin_int_1[a]=price_1;
         yin_int_2[a]=price_2;

         line_move=-1;
         line_gauge=-1;

         price_1=price_2;

         time_change[a]=(datetime)arr_time[z];
         time_line[a]=0;
        }
      //--- first THICK ASCENDING line
      if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) && line_move==0)
        {
         yang_int_1[a]=price_1;
         yang_int_2[a]=price_2;

         line_move=1;
         line_gauge=1;

         price_1=price_2;

         time_change[a]=(datetime)arr_time[z];
         time_line[a]=1;
        }
      //--- price moves DOWN
      //--- if the price moved DOWN before that, the line is THIN
      if(line_move==-1 && line_gauge==-1)
        {
         if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point>0))
           {
            yin_int_2[a]=price_2;

            line_move=-1;
            line_gauge=-1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=0;
           }
        }
      //--- if the price moved DOWN before that, the line is THICK
      if(line_move==-1 && line_gauge==1)
        {
         if(((price_1-price_2)/_Point>func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point>0))
           {
            if(price_2<price_down) // If the thick line crossed the lower shoulder when moving downwards
              {
               yin_int_1[a]=price_down;
               yin_int_2[a]=price_2;

               yang_int_2[a]=price_down;

               line_move=-1;
               line_gauge=-1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_central_price[a]=price_down;
               time_line[a]=0;
              }
            else //if(price_2>=price_down) // If the thick line has not crossed the lower shoulder when moving downwards
              {
               yang_int_2[a]=price_2;

               line_move=-1;
               line_gauge=1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
              }
           }
        }
      //--- if the price has moved UPWARDS before that, the line is THIN
      if(line_move==1 && line_gauge==-1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            yin_int_1[a]=price_1;
            yin_int_2[a]=price_2;

            lin_yin[a]=price_1;

            line_move=-1;
            line_gauge=-1;

            price_up=price_1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=0;
            time_change_price[a]=lin_yin[a];
           }
        }
      //--- if the price has moved UPWARDS before that, the line is THICK
      if(line_move==1 && line_gauge==1)
        {
         if((price_1-price_2)/_Point>func_calc_dorstep(price_2))
           {
            a++;
            if(price_2<price_down) // If the thick line has crossed the lower shoulder when moving downwards
              {
               yin_int_1[a]=price_down;
               yin_int_2[a]=price_2;

               yang_int_1[a]=price_1;
               yang_int_2[a]=price_down;

               lin_yang[a]=price_1;

               line_move=-1;
               line_gauge=-1;

               price_up=price_1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
               time_change_price[a]=lin_yang[a];
               time_central_price[a]=price_down;
              }
            else//if(price_2>=price_down) // If the thick line has not crossed the lower shoulder when moving downwards
              {
               yang_int_1[a]=price_1;
               yang_int_2[a]=price_2;

               lin_yang[a]=price_1;

               line_move=-1;
               line_gauge=1;

               price_up=price_1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
               time_change_price[a]=lin_yang[a];
              }
           }
        }
      //--- the price moves UP
      //--- if the price has moved UPWARDS before that, the line is THICK
      if(line_move==1 && line_gauge==1)
        {
         if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point<0))
           {
            yang_int_2[a]=price_2;

            line_move=1;
            line_gauge=1;

            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=1;
           }
        }

      //--- if the price has moved UPWARDS before that, the line is THIN
      if(line_move==1 && line_gauge==-1)
        {
         if(((price_1-price_2)/_Point<-func_calc_dorstep(price_2)) || (kagi_type==0 && (price_1-price_2)/_Point<0))
           {
            if(price_2>price_up) // If the thin line has not crossed the upper shoulder when moving upwards
              {
               yin_int_2[a]=price_up;

               yang_int_1[a]=price_up;
               yang_int_2[a]=price_2;

               line_move=1;
               line_gauge=1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_central_price[a]=price_up;
               time_line[a]=1;
              }
            else//if(price_2<=price_up) // If the thin line has not crossed the upper shoulder when moving upwards
              {
               yin_int_2[a]=price_2;

               line_move=1;
               line_gauge=-1;

               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
              }
           }
        }

      //--- if the price has moved DOWNWARDS before that, the line is THICK
      if(line_move==-1 && line_gauge==1)
        {
         if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
           {
            a++;

            yang_int_1[a]=price_1;
            yang_int_2[a]=price_2;

            lin_yang[a]=price_1;

            line_move=1;
            line_gauge=1;

            price_down=price_1;
            price_1=price_2;

            time_change[a]=(datetime)arr_time[z];
            time_line[a]=1;
            time_change_price[a]=lin_yang[a];
           }
        }

      //--- if the price has moved DOWNWARDS before that, the line is THIN
      if(line_move==-1 && line_gauge==-1)
        {
         if((price_1-price_2)/_Point<-func_calc_dorstep(price_2))
           {
            a++;
            if(price_2>price_up) // If the thin line has crossed the upper shoulder when moving upwards
              {
               yin_int_1[a]=price_1;
               yin_int_2[a]=price_up;

               yang_int_1[a]=price_up;
               yang_int_2[a]=price_2;

               lin_yin[a]=price_1;

               line_move=1;
               line_gauge=1;

               price_down=price_1;
               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=1;
               time_change_price[a]=lin_yin[a];
               time_central_price[a]=price_up;
              }
            else //if(price_2<=price_up) // If the thin line has not crossed the upper shoulder when moving upwards
              {
               yin_int_1[a]=price_1;
               yin_int_2[a]=price_2;

               lin_yin[a]=price_1;

               line_move=1;
               line_gauge=-1;

               price_down=price_1;
               price_1=price_2;

               time_change[a]=(datetime)arr_time[z];
               time_line[a]=0;
               time_change_price[a]=lin_yin[a];
              }
           }
        }

     }
//--- function's main loop
//--- assign actual values to drawing buffers
   uint y=a;
//--- auxiliary variables for storing data on filling the current buffer
   char yin=1;
   char yang=1;
   for(uint z=0; z<=a; z++)
     {
      arr_yin_1[z]=yin_int_1[y];
      arr_yin_2[z]=yin_int_2[y];

      switch(yin)
        {
         case 1:
           {
            arr_yin_lin1[z]=lin_yin[y];
            arr_yin_lin1[z+1]=lin_yin[y];
            yin++;
           }
         break;
         case 2:
           {
            arr_yin_lin2[z]=lin_yin[y];
            arr_yin_lin2[z+1]=lin_yin[y];
            yin++;
           }
         break;
         case 3:
           {
            arr_yin_lin3[z]=lin_yin[y];
            arr_yin_lin3[z+1]=lin_yin[y];
            yin=1;
           }
         break;
        }

      arr_yang_1[z]=yang_int_1[y];
      arr_yang_2[z]=yang_int_2[y];

      switch(yang)
        {
         case 1:
           {
            arr_yang_lin1[z]=lin_yang[y];
            arr_yang_lin1[z+1]=lin_yang[y];
            yang++;
           }
         break;
         case 2:
           {
            arr_yang_lin2[z]=lin_yang[y];
            arr_yang_lin2[z+1]=lin_yang[y];
            yang++;
           }
         break;
         case 3:
           {
            arr_yang_lin3[z]=lin_yang[y];
            arr_yang_lin3[z+1]=lin_yang[y];
            yang=1;
           }
         break;
        }
      y--;
     }
//---
  }


3.6. Función para la creación del objeto gráfico "línea de tendencia"

Ahora, vamos a analizar la función para la creación del objeto gráfico "línea de tendencia". Esta función es necesaria para poder dibujar el gráfico Kagi en el gráfico principal.

La función es muy sencilla. Contiene los parámetros de entrada necesarios para crear un objeto gráfico "línea de tendencia": nombre del objeto, primer y segundo precio y puntos de tiempo, así como el grosor de la línea y el color. El cuerpo de la función contiene la función para crear el objeto gráfico y seis funciones para cambiar las propiedades de la misma.

//+------------------------------------------------------------------+
//| Func Object Create Trend Line                                    |
//+------------------------------------------------------------------+
void func_create_trend_line(string name,
                            double price1,
                            double price2,
                            datetime time1,
                            datetime time2,
                            int width,
                            color color_line)
  {
   ObjectCreate(0,name,OBJ_TREND,0,time1,price1,time2,price2);
//--- set the line color
   ObjectSetInteger(0,name,OBJPROP_COLOR,color_line);
//--- set the line display style
   ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
//--- set the line width
   ObjectSetInteger(0,name,OBJPROP_WIDTH,width);
//--- display in the foreground (false) or background (true)
   ObjectSetInteger(0,name,OBJPROP_BACK,false);
//--- enable (true) or disable (false) the mode of continuing the line display to the left
   ObjectSetInteger(0,name,OBJPROP_RAY_LEFT,false);
//--- enable (true) or disable (false) the mode of continuing the line display to the right
   ObjectSetInteger(0,name,OBJPROP_RAY_RIGHT,false);
  }

3.7. Dibujar Kagi en el gráfico principal

La siguiente función que llama varias veces a la anterior es la función del trazado de Kagi en el gráfico principal. Se usan las variables globales examinadas previamente y que se han rellenado en la función principal del trazado del gráfico como variables de entrada: la matriz de los precios de inversión ("hombros" y "cinturas"), la matriz de cambio y los precios centrales (el precio para el cual se convierte la línea Yin en Yang o viceversa), la matriz del tiempo de inversión (que está en tiempo real, se usa el índice [z-1] de la matriz para marcar el inicio de la inversión), la matriz del tipo de línea, en la cual se produce la inversión (también está adelantada de un elemento, al igual que la matriz de tiempo).

El cuerpo de la función consiste en un bucle. Se divide el bucle en dos partes: el trazado de líneas verticales y horizontales. El primero de ellos también se divide en dos: el dibujo de las verticales, teniendo en cuenta el cambio de línea (cambio del precio central) y la ausencia de cambio. Tenga en cuenta los parámetros transferidos de la función de creación del objeto "línea de tendencia".

Se lleva a cabo la asignación del nombre repetidamente. Se inicia el nombre del objeto con un número mágico (necesario para eliminar los objetos de un determinado indicador), entonces se pone su tipo y, finalmente, se le asigna el índice. Se actualiza el índice en cada pasada del bucle.

//+------------------------------------------------------------------+
//| Func Kagi Main Chart                                             |
//+------------------------------------------------------------------+
void func_kagi_main_chart(double &price[],         // Shoulder prices array
                          double &central_price[], // Array of the prices of passing through the shoulders
                          datetime &time[],        // Current location time array ([-1] - start of shoulder)
                          char &type_line_end[])   // Line type by the start of shoulder formation
  {
//--- start of the loop
   for(uint z=1; z<=a; z++)
     {
      //--- check for the pass conditions (no pass)
      if(central_price[z]==-1)
        {
         if(type_line_end[z-1]==0 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   price[z],price[z+1],time[z],time[z],width_yin_main,color_yin_main);
           }
         if(type_line_end[z-1]==1 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   price[z],price[z+1],time[z],time[z],width_yang_main,color_yang_main);
           }
        }
      else //--- check for the pass conditions (pass is present)
        {
         if(type_line_end[z-1]==0 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   central_price[z],price[z],time[z],time[z],width_yin_main,color_yin_main);
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   central_price[z],price[z+1],time[z],time[z],width_yang_main,color_yang_main);
           }
         if(type_line_end[z-1]==1 && price[z+1]!=-1)
           {
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yin_v"+IntegerToString(z),
                                   central_price[z],price[z+1],time[z],time[z],width_yin_main,color_yin_main);
            func_create_trend_line(IntegerToString(magic_numb)+"_trend_yang_v"+IntegerToString(z),
                                   central_price[z],price[z],time[z],time[z],width_yang_main,color_yang_main);
           }
        }
      //--- check for the pass conditions (pass is present)
      //--- draw the horizontals
      if(type_line_end[z-1]==0)
        {
         func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                                price[z],price[z],time[z-1],time[z],width_yin_main,color_yin_main);
        }
      if(type_line_end[z-1]==1)
        {
         func_create_trend_line(IntegerToString(magic_numb)+"_trend_h"+IntegerToString(z),
                                price[z],price[z],time[z-1],time[z],width_yang_main,color_yang_main);
        }
      //--- draw the horizontals
     }
  }

3.8. Implementación de etiquetas adicionales

Como ya he mencionado anteriormente, el indicador implementa unas etiquetas adicionales. Vamos a examinar la función que proporciona estas etiquetas en el gráfico principal. Sólo hay dos tipos de etiquetas aquí: etiquetas del precio de inversión y del tiempo de inversión que se muestran mediante la "etiqueta de precio" y la "etiqueta vertical". Se pasan los siguientes parámetros de entrada: atributo del dibujo de la etiqueta del precio de inversión y el color de la etiqueta, atributos del dibujo de la etiqueta del tiempo de inversión y cambio de color de la etiqueta, el primer y segundo color del tiempo de inversión.

Se divide la función entera en dos partes: la primera parte se encarga de las etiquetas del tiempo, mientras que la segunda es para las etiquetas del precio. Ambas partes de la función consisten en los bucles limitados por el número de inversiones del gráfico (variable "a"). Se coloca la declaración condicional if-else del bucle. La declaración comprueba la necesidad de dibujar de acuerdo con los valores del indicador.

El primer bucle crea las etiquetas de tiempo, se lleva a cabo la definición del nombre del objeto al principio del bucle (el principio de generación de los nombres se ha descrito anteriormente). A continuación, se selecciona el color en función de la línea a partir de la matriz de tipo línea declarada a nivel global (si el parámetro está establecido) y se aplican otros parámetros a la línea.

El segundo bucle se encarga de crear las etiquetas del precio de inversión. En primer lugar, se genera el nombre del objeto. Después, se establece la selección del índice de la matriz de tiempo dependiendo de si se va a dibujar Kagi en el gráfico principal o no. Si esto no se hace, las etiquetas estarán "en el aire" y no quedará muy claro en qué lugar se ha producido la inversión. A continuación, se crea y se configura el objeto de tipo "etiqueta de precio".

//+------------------------------------------------------------------+
//| Func Label Main Chart                                            |
//+------------------------------------------------------------------+
void func_label_main_chart(bool label_print,
                           color label_color,
                           bool time_change_print,
                           bool time_change_color,
                           color time_color_first,
                           color time_color_second)
  {
   if(time_change_print==true)
     {
      for(uint z=1; z<=a; z++)
        {
         string name=IntegerToString(magic_numb)+"_time_2_"+IntegerToString(z);
         //--- create an object of a vertical line type
         ObjectCreate(0,name,OBJ_VLINE,0,time_change[z],0);
         //--- set the line color
         color color_line=clrBlack;
         if(time_change_color==true)
           {
            if(time_line[z]==0)color_line=time_color_first;
            if(time_line[z]==1)color_line=time_color_second;
           }
         else color_line=time_color_first;
         ObjectSetInteger(0,name,OBJPROP_COLOR,color_line);
         //--- set the line display style
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         //--- set the line width
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         //--- display on the foreground (false) or background (true)
         ObjectSetInteger(0,name,OBJPROP_BACK,false);
         //--- enable (true) or disable (false) the line display mode in the chart subwindows
         ObjectSetInteger(0,name,OBJPROP_RAY,time_separate_windows);
        }
     }
   if(label_print==true)
     {
      for(uint z=1; z<=a; z++)
        {
         string name=IntegerToString(magic_numb)+"_label_2_"+IntegerToString(z);
         uint numb_time;
         if(kagi_main_chart==true)numb_time=z;
         else numb_time=z-1;
         //--- create a label type object
         ObjectCreate(0,name,OBJ_ARROW_RIGHT_PRICE,0,time_change[numb_time],time_change_price[z]);
         //--- set the label color
         ObjectSetInteger(0,name,OBJPROP_COLOR,label_color);
         //--- set the edging line style
         ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
         //--- set the label size
         ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
         //--- display on the foreground (false) or background (true)
         ObjectSetInteger(0,name,OBJPROP_BACK,false);
        }
     }
  }

Ahora, vamos a ver cómo podemos poner las etiquetas en la ventana del indicador.

Todas las etiquetas de la ventana del indicador son en su mayoría de precios, y sólo hay dos tipos de ellas: las etiquetas del precio de inversión y los niveles del precio. Hay dos tipos de dibujo de los niveles del precio: en las inversiones del gráfico y a la misma distancia de toda la gama de precios del gráfico. El primer tipo puede cambiar el color de los niveles de dos maneras: en función del tipo de línea (Yin o Yang) y en función de la inversión (arriba o abajo).

Por tanto, la función en sí se divide en dos bucles: el primero se encarga de crear las etiquetas del precio de reversión y el segundo es para tratar de designar los niveles del precio. Este último se divide en dos tipos: la etiqueta en cada inversión o las etiquetas de todo el rango de precios en un mismo nivel.

Esta función es diferente de la anterior por el hecho de tener limitaciones en el número de etiquetas y niveles del precio debido a que los números grandes sobrecargan el gráfico, complicando su comprensión.

Debido a esta característica, los dos bucles están limitadas por el número de pasadas especificadas en la configuración del indicador (número de etiquetas y niveles del precio). Este enfoque es peligroso ya que el número de inversiones puede llegar a ser mucho más pequeño que el número de etiquetas del precio establecido en la configuración. Por consiguiente, se comprueba la presencia de la inversión durante cada pasada del bucle para dibujar una etiqueta o nivel de precio.

La única excepción es el dibujo de los niveles del precio a lo largo de toda la gama de precios en la misma distancia. Se lleva a cabo la generación del objeto gráfico "etiqueta de precio" en las coordenadas en orden inverso, es decir, las etiquetas se colocan desde la fecha actual hacia el pasado. Es igual para los niveles del precio: primero se generan los niveles del precio actual seguidos por los anteriores. Excepto los niveles del precio que no dependen de las inversiones del gráfico.

Se realizan los cambios de color del nivel del precio mediante las instrucciones condicionales if-else en función de la configuración.

//+------------------------------------------------------------------+
//| Func Label Indicator Window                                      |
//+------------------------------------------------------------------+
void func_label_indicator_window(bool label_print,         // Draw price labels
                                 bool levels_print,        // Draw levels
                                 char levels_type_draw,    // Type of drawing the levels by reversals or at an equal distance of the entire price range
                                 char levels_color_change) // Change line color
  {
   uint number=a;
   if(label_print==true)
     {
      for(uint z=0; z<=label_1_number; z++)
        {
         if(z<number)
           {
            string name=IntegerToString(magic_numb)+"_label_1_"+IntegerToString(z);
            //--- create label type object
            ObjectCreate(0,name,OBJ_ARROW_RIGHT_PRICE,ChartWindowFind(),(datetime)Time[(bars_copied_time-z-2)],time_change_price[number-z]);
            //--- set the label color
            ObjectSetInteger(0,name,OBJPROP_COLOR,label_1_color);
            //--- set the style of the edging line
            ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID);
            //--- set the label size
            ObjectSetInteger(0,name,OBJPROP_WIDTH,1);
            //--- display on the foreground (false) or background (true)
            ObjectSetInteger(0,name,OBJPROP_BACK,false);
           }
        }
     }
   if(levels_print==true)
     {
      if(levels_type_draw==0)
        {
         for(uint z=0; z<=levels_number; z++)
           {
            if(z<number)
              {
               IndicatorSetDouble(INDICATOR_LEVELVALUE,z,time_change_price[number-z]);
               if(levels_change_color==0)
                 {
                  double numb_even=z;
                  if(MathMod(numb_even,2)==0)
                    {
                     IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                    }
                  if(MathMod(numb_even,2)!=0)
                    {
                     IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
                    }
                 }
               if(levels_change_color==1)
                 {
                  if(time_line[number-z]==0)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                  if(time_line[number-z]==1)IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_second_color);
                 }
               if(levels_change_color==2)
                 {
                  IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
                 }
              }
           }
        }
      if(levels_type_draw==1)
        {
         double max_price=Price[ArrayMaximum(Price)];
         double min_price=Price[ArrayMinimum(Price,1,ArrayMinimum(Price)-1)];
         double number_difference=(max_price-min_price)/levels_number;
         NormalizeDouble(number_difference,_Digits);
         for(uint z=0; z<=levels_number; z++)
           {
            IndicatorSetDouble(INDICATOR_LEVELVALUE,z,(min_price+(z*number_difference)));
            IndicatorSetInteger(INDICATOR_LEVELCOLOR,z,levels_first_color);
           }
        }
     }
  }

3.9. Eliminación de objetos gráficos creados previamente

Ya sabemos que este indicador está lleno de objetos gráficos. Es el momento de pensar cómo podemos eliminarlos de forma rápida y eficiente.

Se ejecuta esta tarea mediante la función para la eliminación de los objetos gráficos. Como parámetros de la función, se usan el nombre inicial y el número de objetos. Al igual que durante la creación, el nombre del objeto debe contener el número mágico y el nombre de un tipo de objeto. La llamada de la función en el programa está limitada por el número de objetos, excediendo los que puedan existir. Sin embargo, esto no afecta a la funcionalidad del indicador.

//+------------------------------------------------------------------+
//| Func Delete Objects                                              |
//+------------------------------------------------------------------+
void func_delete_objects(string name,
                         int number)
  {
   string name_del;
   for(int x=0; x<=number; x++)
     {
      name_del=name+IntegerToString(x);
      ObjectDelete(0,name_del);
     }
  }

3.10. Función para el inicio del trazado del gráfico

Después de haber analizado todas las funciones para el cálculo y la construcción del gráfico Kagi, así como para la creación y eliminación de objetos, debemos considerar ahora otra pequeña función para el control de la llegada de una nueva barra. La función es bastante sencilla y tiene un solo parámetro de entrada; el período analizado. La respuesta de la función es muy sencilla también. Es de tipo bool y contiene la respuesta acerca de la presencia o no de una nueva barra. Se basa el cuerpo de la función en el operador switch que pasa el control a sus diferentes estados en función del período.

En el ejemplo, la función abarca todo el rango de los períodos, aunque sólo se puede utilizar un período.

Se ha tomado el algoritmo de la función a partir del código de IsNewBar: se compara el tiempo de apertura de la última barra con el valor del tiempo previamente definido. Si los valores son distintos, hay una nueva barra. Se asigna el nuevo valor como el que se ha definido previamente y se considera que la respuesta de la función es positiva. Si el tiempo de apertura de la última barra coincide con el valor del tiempo determinado previamente, entonces, todavía no ha aparecido una nueva barra y la respuesta de la función es negativa.

//+------------------------------------------------------------------+
//| Func New Bar                                                     |
//+------------------------------------------------------------------+
bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
//----
   static datetime old_Times[22];// array for storing old values
   bool res=false;               // analysis result variable  
   int  i=0;                     // old_Times[] array cell index    
   datetime new_Time[1];         // new bar time

   switch(period_time)
     {
      case PERIOD_M1:  i= 0; break;
      case PERIOD_M2:  i= 1; break;
      case PERIOD_M3:  i= 2; break;
      case PERIOD_M4:  i= 3; break;
      case PERIOD_M5:  i= 4; break;
      case PERIOD_M6:  i= 5; break;
      case PERIOD_M10: i= 6; break;
      case PERIOD_M12: i= 7; break;
      case PERIOD_M15: i= 8; break;
      case PERIOD_M20: i= 9; break;
      case PERIOD_M30: i=10; break;
      case PERIOD_H1:  i=11; break;
      case PERIOD_H2:  i=12; break;
      case PERIOD_H3:  i=13; break;
      case PERIOD_H4:  i=14; break;
      case PERIOD_H6:  i=15; break;
      case PERIOD_H8:  i=16; break;
      case PERIOD_H12: i=17; break;
      case PERIOD_D1:  i=18; break;
      case PERIOD_W1:  i=19; break;
      case PERIOD_MN1: i=20; break;
      case PERIOD_CURRENT: i=21; break;
     }
   // copy the time of the last bar to new_Time[0] cell  
   int copied=CopyTime(_Symbol,period_time,0,1,new_Time);
  
   if(copied>0) // all is well. Data has been copied
      {
      if(old_Times[i]!=new_Time[0])       // if the bar's old time is not equal to new one
         {
         if(old_Times[i]!=0) res=true;    // if it is not the first launch, true = new bar
         old_Times[i]=new_Time[0];        // store the bar's time
         }
      }
//----
   return(res);
  }

 

3.11. Las funciones OnCalculate() y OnChartEvent()

Todas las funciones descritas anteriormente se consolidan en la función con el mismo nombre; Func Consolidation. Se ejecuta esta función cada vez que aparece una nueva barra en la función OnCalculate() y cuando se pulsa la tecla "R" desde la función OnChartEvent(). 

Antes de que se genere o actualice el gráfico, se llama a la función para eliminar todos los objetos gráficos en la función de consolidación (Func Consolidation). Puesto que hay bastantes objetos y que están divididos entre las etiquetas del gráfico principal y las etiquetas del precio de la ventana del indicador, las líneas verticales que indican el tiempo de inversión, así como las líneas de tendencia Yin y Yang verticales y horizontales, el número de llamadas de la función es 7.

A continuación, se copian los datos del historial por precio y tiempo. Después se ejecuta la función principal para la construcción del gráfico Kagi. Luego, se llama a la función para la colocación de todas las etiquetas del precio en el gráfico principal y en la ventana del indicador. Por último, se genera Kagi en el gráfico principal y se ejecuta la función para redibujar los objetos.

//+------------------------------------------------------------------+
//| Func Consolidation                                               |
//+------------------------------------------------------------------+
void func_consolidation()
  {
//--- date of construction end
   stop_data=TimeCurrent();

//--- deleting all graphical objects belonging to the indicator
   func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));

//--- copy price data to the main buffer
   copy_history=func_copy_history(Price,start_data,stop_data);

//--- display information about the error when copying price data
   if(copy_history==false)Alert("Error of copy history Price");

//--- copy time data to the main buffer
   copy_time=func_copy_time(Time,start_data,stop_data);

//--- display a notification of the error occurred while copying time data
   if(copy_time==false)Alert("Error of copy history Time");

//--- construct Kagi chart in the indicator window
   func_draw_kagi(Price,YinBuffer1,YinBuffer2,Yin1Buffer,Yin2Buffer,Yin3Buffer,
                  YangBuffer1,YangBuffer2,Yang1Buffer,Yang2Buffer,Yang3Buffer,Time);

//--- draw labels on the main chart
   func_label_main_chart(label_2,label_2_color,time_line_draw,time_line_change_color,time_first_color,time_second_color);

//--- draw labels on the indicator chart
   func_label_indicator_window(label_1,levels_on_off,levels_type,levels_change_color);

//--- construct Kagi chart in the main window
   if(kagi_main_chart==true)func_kagi_main_chart(time_change_price,time_central_price,time_change,time_line);

//--- redraw the chart
   ChartRedraw(0);
//---
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//---
   if(func_new_bar(period_to_redraw)==true)
     {
      func_consolidation();
     }
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnChartEvent                                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // event ID  
                  const long& lparam,   // long type event parameter
                  const double& dparam, // double type event parameter
                  const string& sparam) // string type event parameter
  {
   if(id==CHARTEVENT_KEYDOWN) // Keyboard button pressing event
     {
      if(lparam==82) // "R" key has been pressed
        {
         func_consolidation();
        }
     }
  }

 

3.12. Función OnDeinit()

Se lleva a cabo la eliminación de todos los objetos en la función de desinicialización del indicador.

//+------------------------------------------------------------------+
//| OnDeinit                                                         |
//+------------------------------------------------------------------+ 
void OnDeinit(const int reason)
  {
//--- delete all graphical objects belonging to the indicator
   func_delete_objects(IntegerToString(magic_numb)+"_label_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_label_1_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_time_2_",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yin_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_yang_v",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
   func_delete_objects(IntegerToString(magic_numb)+"_trend_h",ObjectsTotal(0,-1,-1));
//--- redraw the chart
   ChartRedraw(0);
  }

Ahora, vamos a pasar al uso del indicador en la práctica.


4. Utilización del gráfico Kagi en la práctica

Hay muchas estrategias de trading basadas en el gráfico Kagi. Examinaremos algunas de ellas.

Vamos a comenzar con la estrategia más popular: vender cuando Yang cambia a Yin y comprar en el caso contrario. Se muestra esto en la figura 4:

La estrategia de compra y venta en los parámetros durante los cambios de línea

Fig. 4. Vender cuando Yang cambia a Yin y comprar en el caso contrario 

Como puede se puede observar en la figura 4 (EURUSD M30, 5 puntos), esta estrategia da buenos resultados. La figura muestra 8 puntos por 4 señales, el primero (1) muestra que la posición larga se debe abrir en 1.3518, lo que parece correcto ya que después, el precio alcanza aproximadamente 1.3560 abarcando 42 puntos por día. Este es un buen resultado.

El siguiente punto (2) recomienda vender a 1.3519. Como podemos observar, el precio se mueve en realidad hacia abajo cruzando el nivel de 1.3485 (y abarcando 34 puntos) durante aproximadamente dos horas.

Pasemos al punto (3). Se abre la posición larga en 1.3538 y el precio se mueve hacia arriba llegando a 1.3695. Por consiguiente, el beneficio ya es de 157 puntos para un día y medio. Por supuesto, estos son los máximos beneficios posibles, pero el resultado sigue siendo bastante bueno.

La siguiente estrategia de trading es la de rodar hacia atrás desde la línea de tendencia que se muestra en la figura. 5 (EURUSD M30, 5 puntos), del 07 al 18 de octubre:

Revirtiendo desde la línea de tendencia

Fig. 5. Revirtiendo desde la línea de tendencia

Podemos seguir moviéndonos y operar siguiendo los canales. Se puede analizar un ejemplo de la búsqueda de un canal en la figura 6 (EURUSD H1, 5 puntos), más o menos el mismo período:

Trading mediante los canales

Fig. 6. Trading mediante los canales

La estrategia menos popular basada en el hecho de que después de 7-10 sucesivos aumentos de "hombros" o disminuciones de "cinturas", habrá sin duda una inversión (caída o subida).

Se muestra esto en la figura 7 (GBPUSD H4, 25 puntos), del 10 de julio al 18 de octubre:

7-10 sucesivos aumentos de "hombros" o disminuciones de "cinturas"

Fig. 7. 7-10 sucesivos aumentos de "hombros" o disminuciones de "cinturas"

Como se puede observar en la imagen, a los siete hombros ascendentes les sigue una caída importante de alrededor de la mitad de la subida anterior (unos 300 puntos).

Vamos a examinar la estrategia de "trading mediante de una etiqueta de precio" para mostrar la necesidad de usar parámetros adicionales del indicador. La idea consiste en entrar al mercado cuando el precio es superior (comprar) o se mueve por debajo (vender) de la etiqueta del precio anterior.

Se muestra la estrategia en la figura 8 (GBPUSD H4, 30 puntos, construcción modificada):

Trading mediante una etiqueta de precio

Fig. 8. Trading mediante una etiqueta de precio 

Las flechas rojas en la figura 8 indican cuándo hay que comprar o vender. Las flechas se alejan de la etiqueta de precio anterior que indica las ubicaciones de las etiquetas de precios anteriores que fueron traspasadas.

Las etiquetas de tiempo sirven principalmente para señalar la dirección de la tendencia. Puesto que se puede cambiar el color de las etiquetas de tiempo en función del tipo de línea, y que el tipo de línea Yin o Yang indica la dirección de la tendencia o su inversión, el color puede ayudar a definir el estado de ánimo actual del mercado.

Por ejemplo, vamos a utilizar el gráfico de cotización de #IBM (H4, 1%, construcción estándar) que se muestra en la figura 9:

Definir la dirección de la tendencia mediante etiquetas de tiempo

Fig. 9. Definir la dirección de la tendencia mediante etiquetas de tiempo

El gráfico muestra que las líneas azules están presentes principalmente en la parte superior del gráfico, mientras que las rojas se encuentran en la parte inferior.


Conclusión

Se puede utilizar el gráfico Kagi con éxito para el trading en el mercado, que sea como base para la estrategia o como herramienta adicional para un análisis más preciso.

En este artículo, he examinado el código en sí y algunas características de la construcción del indicador. El objetivo principal ha sido la creación del indicador multifuncional que contiene todos los elementos necesarios con algunas características adicionales que se puedan desactivar.

Estaré encantado de considerar nuevas ideas y mejoras para el indicador y, tal vez, ponerlas en práctica en el futuro. Agradezco también sus comentarios y estaré encantado de responder a sus preguntas acerca del indicador.

Este artículo prosigue la serie dedicada al desarrollo de indicadores para la construcción de gráficos del pasado Se puede consultar el artículo anterior aquí. La serie se va a continuar, y espero volver a verle pronto. ¡Gracias por su interés! Le deseo un trading exitoso, así como unos códigos optimizados y estables.