Indicador para el la representación del gráfico de punto y figura

Dmitriy Zabudskiy | 7 mayo, 2014

Introducción

Hay muchos tipos de gráficos que proporcionan informaciones acerca del estado actual del mercado. Muchos de ellos, como el gráfico de punto y figura, se remontan a tiempos remotos.

Este tipo de gráfico es conocido desde finales del siglo XIX. Fue mencionado por primera vez por Charles Dow en su editorial del Wall Street Journal del 20 de julio 1901, calificándolo como el método "libro". Y a pesar de que Dow mencionó que el método "libro" remontaba a 1886, fue el primero en establecer oficialmente las pautas de su uso en la actualidad.

Aunque Dow solo describió el libro en sus editoriales, podemos encontrar muchos libros que describen este método con detalle. Uno de los libros que me gustaría recomendar a los traders principiantes es el libro de Thomas J. Dorsey titulado "Point and Figure Charting: The Essential Application for Forecasting and Tracking Market Prices" (Gráficos de punto y figura: La aplicación básica para la predicción y seguimiento del mercado).

 

Descripción

El gráfico de punto y figura es un conjunto de columnas verticales: las columnas marcadas con X representan precios que están subiendo y la columnas con O representan precios que están bajando. Su peculiaridad es que el dibujo depende del precio, no del tiempo. Por tanto, habiendo retirado un valor de los datos del gráfico (tiempo), obtenemos gráficos con líneas de tendencia dibujadas a 45 grados.

Se dibujan los gráficos de punto y figura mediante valores predefinidos:

Así que seleccionamos un punto de inicio y colocamos una X para una subida del precio o una O para una bajada del precio, con la condición de que el valor del cambio del precio sea igual al tamaño de celda multiplicado por el importe de inversión. Además, si el precio continua moviéndose en la misma dirección con un cambio de valor igual al tamaño de celda, añadimos una X a la parte superior de la columna de las X para una subida o una O a la parte inferior de la columna de las O en caso de bajada, respectivamente. Si el precio se mueve en la dirección opuesta con un valor igual al tamaño de celda multiplicado por el importe de inversión, ponemos una X para una subida del precio o una O para una bajada del precio, iniciando así una nueva columna de las X o una nueva columna de las O, respectivamente.

Para mayor comodidad, la figura de punto y figura se suele dibujar en papel cuadriculado. Para comprender mejor, echemos un vistazo a un pequeño ejemplo de un gráfico de punto y figura. Supongamos que tenemos los siguientes datos:

Fecha Precio máximo Precio mínimo
07.03.2013 12:00 - 07.03.2013 20:00 1.3117 1.2989
07.03.2013 20:00 - 08.03.2013 4:00 1.3118 1.3093
08.03.2013 4:00 - 08.03.2013 12:00 1.3101 1.3080
08.03.2013 12:00 - 08.03.2013 20:00 1.3134 1.2955

Dibujaremos el gráfico de punto y figura, suponiendo que el tamaño de celda es igual a 10 y el importe de inversión es igual a 3:

Veamos su representación a continuación:

Fig. 1. Gráfico de velas japonesas (izquierda) y gráfico de punto y figura (derecha)

Fig. 1. Gráfico de velas japonesas (izquierda) y gráfico de punto y figura (derecha)

El ejemplo anterior del gráfico de punto y figura es muy bruto y se proporciona aquí para ayudar a los principiantes a entender mejor el concepto.

 

El principio de la representación gráfica

Existen varias técnicas de trazado para la representación del gráfico de punto y figura, ya hemos descrito una de ellas antes. Estas técnicas de representación gráfica se diferencian por los datos que usan. Por ejemplo, podemos usar datos diarios sin tener en cuenta los movimientos intradía, obteniendo así un dibujo bruto. O podemos tener en cuenta los datos del movimiento del precio intradía y así obtenemos un dibujo más detallado y más suave.

Para conseguir un gráfico de punto y figura más suave y más detallado, se decidió usar los datos de cada minuto para los cálculos y el trazado ya que los movimientos del precio dentro de un minuto no son muy significativos y por lo general no superan seis puntos, y a veces dos o tres puntos. De modo que usaremos los datos del precio de apertura en la barra de cada minuto.

El principio del gráfico en sí es bastante sencillo:

Vamos a determinar ahora el estilo del gráfico de punto y figura. El lenguaje MQL5 acepta siete estilos de dibujo de indicadores: línea, sección (segmento), histograma, flecha (símbolo), barras y velas japonesas.

Las flechas (símbolos) serían perfectas para una representación gráfica ideal, pero este estilo requiere un número variable de buffers de indicador (que sencillamente no es compatible con MQL5) o un número enorme de los mismos ya que la representación gráfica de cada una de las X u O en una columna requiere un buffer de indicador por separado. Esto significa que si decide utilizar este estilo, es necesario definir la volatilidad y tener suficientes recursos de memoria.

Así que hemos optado por las velas japonesas como estilo del gráfico, precisamente, velas japonesas en color. Para diferenciar las columnas de las X de las columnas de las O se supone que hay que utilizar diferentes colores. De modo que el indicador solo requiere cinco buffers, permitiendo un uso eficiente de los recursos disponibles.

Se dividen las columnas en tamaños de celda mediante líneas horizontales. El resultado que obtenemos es bueno:

Fig. 2. Gráfico usando el indicador para EURUSD con un período de tiempo diario.

Fig. 2. Gráfico usando el indicador para EURUSD con un período de tiempo diario.

 

El algoritmo del indicador

En primer lugar, tenemos que determinar los parámetros de entrada del indicador. Puesto que el gráfico de punto y figura no tiene en cuenta el tiempo y usamos datos para dibujar desde las barras por minutos, tenemos que determinar la cantidad de datos a procesar para evitar un uso innecesario de los recursos del sistema. Además, no tiene sentido usar todo el historial para dibujar un gráfico de punto y figura. Así que introducimos el primer parámetro; History (el historial). Tendrá en cuenta el número de barras de cada minuto para el cálculo.

Por otra parte, tenemos que determinar el "Tamaño de celda" y el "Importe de inversión". Para ello, vamos a introducir las variables Cell y CellForChange, respectivamente. También introduciremos el parámetro de color ColorUp para las X y ColorDow para las O. Y finalmente, el último parámetro será el color de línea; LineColor.

// +++ Program start +++
//+------------------------------------------------------------------+
//|                                                         APFD.mq5 |
//|                                            Aktiniy ICQ:695710750 |
//|                                                    ICQ:695710750 |
//+------------------------------------------------------------------+
#property copyright "Aktiniy ICQ:695710750"
#property link      "ICQ:695710750"
#property version   "1.00"
//--- Indicator plotting in a separate window
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots   1
//--- plot Label1
#property indicator_label1  "APFD"
#property indicator_type1   DRAW_COLOR_CANDLES
#property indicator_style1  STYLE_SOLID
#property indicator_color1  clrRed,clrGold
#property indicator_width1  1
//--- Set the input parameters
input int   History=10000;
input int   Cell=5;
input int   CellForChange=3;
input color ColorUp=clrRed;
input color ColorDown=clrGold;
input color LineColor=clrAqua;
//--- Declare indicator buffers
double CandlesBufferOpen[];
double CandlesBufferHigh[];
double CandlesBufferLow[];
double CandlesBufferClose[];
double CandlesBufferColor[];
//--- Array for copying calculation data from the minute bars
double OpenPrice[];
// Variables for calculations
double PriceNow=0;
double PriceBefore=0;
//--- Introduce auxiliary variables
char   Trend=0;      // Direction of the price trend
double BeginPrice=0; // Starting price for the calculation
char   FirstTrend=0; // Direction of the initial market trend
int    Columns=0;    // Variable for the calculation of columns
double InterimOpenPrice=0;
double InterimClosePrice=0;
double NumberCell=0; // Variable for the calculation of cells
double Tick=0;       // Tick size
double OldPrice=0;   // Value of the last calculation price
//--- Create arrays to temporary store data on column opening and closing prices
double InterimOpen[];
double InterimClose[];
// +++ Program start +++

Veamos ahora la función OnInit(). Enlazará los buffers del indicador con matrices unidimensionales. También estableceremos el valor del indicador sin procesar para una visualización más precisa y calcular el valor de la variable auxiliar Tick (tamaño de un tick) para los cálculos.

Además, estableceremos la combinación de colores y la indexación de órdenes en los buffers del indicador como series temporales. Es imprescindible para calcular correctamente los valores del indicador.

// +++ The OnInit function +++
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,CandlesBufferOpen,INDICATOR_DATA);
   SetIndexBuffer(1,CandlesBufferHigh,INDICATOR_DATA);
   SetIndexBuffer(2,CandlesBufferLow,INDICATOR_DATA);
   SetIndexBuffer(3,CandlesBufferClose,INDICATOR_DATA);
   SetIndexBuffer(4,CandlesBufferColor,INDICATOR_COLOR_INDEX);
//--- Set the value of the indicator without rendering
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0);
//--- Calculate the size of one tick
   Tick=SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_SIZE);
//--- Set the color scheme
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,0,ColorUp);
   PlotIndexSetInteger(0,PLOT_LINE_COLOR,1,ColorDown);
//--- Set the indexing order in arrays as time series
   ArraySetAsSeries(CandlesBufferClose,true);
   ArraySetAsSeries(CandlesBufferColor,true);
   ArraySetAsSeries(CandlesBufferHigh,true);
   ArraySetAsSeries(CandlesBufferLow,true);
   ArraySetAsSeries(CandlesBufferOpen,true);
//--- Check the input parameter for correctness
   if(CellForChange<2)
      Alert("The CellForChange parameter must be more than 1 due to plotting peculiarities");
//---
   return(0);
  }
// +++ The OnInit function +++

Hemos llegado al "corazón" del indicador, la función OnCalculate() donde se llevan a cabo los cálculos. Se dividen los cálculos de los valores del indicador en seis funciones básicas que se pueden llamar desde OnCalculate(). Vamos a echarles un vistazo:

1.  Función para copiar los datos

Esta función copia los datos a partir de las barras de los minutos a una matriz para los cálculos. Primero, cambiamos el tamaño de la matriz receptora y luego copiamos los precios de apertura en ella mediante la función CopyOpen().

//+------------------------------------------------------------------+
//| Function for copying data for the calculation                    |
//+------------------------------------------------------------------+
int FuncCopy(int HistoryInt)
  {
//--- Resize the array for copying calculation data
   ArrayResize(OpenPrice,(HistoryInt));
//--- Copy data from the minute bars to the array
   int Open=CopyOpen(Symbol(),PERIOD_M1,0,(HistoryInt),OpenPrice);
//---
   return(Open);
  }

2.  Función para calcular el número de columnas

Esta función calcula el número de columnas para el gráfico punto y figura.

Se llevan a cabo los cálculos en un bucle que recorre el número de barras en un período de un minuto que fueron copiadas en la función anterior. El bucle en sí consiste en tres bloques elementales para diferentes tipos de tendencia:

Se usará la tendencia indefinida una vez solo para determinar el movimiento inicial del precio. Se determinará la dirección del movimiento del precio cuando el valor absoluto de la diferencia entre el precio actual del mercado y el precio inicial supere el valor del "tamaño de celda" multiplicado por el "importe de inversión".

Si hay un cambio hacia abajo, se identificará como una tendencia bajista y se asignará la entrada correspondiente en la variable Trend. De una forma exactamente opuesta se identifica una tendencia alcista. Además, se incrementará el valor de la variable del número de columnas, ColumnsInt.

Una vez identificada la tendencia actual, establecemos dos condiciones para cada dirección. Si el precio continúa moviéndose en la tendencia actual por valores iguales al tamaño de celda, el valor de la variable ColumnsInt se mantendrá igual. Si se invierte el precio en el tamaño de celda multiplicado por el importe de inversión, aparecerá una nueva columna y el valor de la variable ColumnsInt aumentará en uno.

Y así sucesivamente hasta que se identifiquen todas las columnas. 

Para redondear el número de celdas en el bucle, usaremos la función MathRound() que nos permite redondear los valores resultantes al número entero más próximo. Opcionalmente, se puede sustituir esta función por la función MathFloor() (redondear al entero más próximo hacia abajo) o la función MathCeil() (redondear al entero más próximo hacia arriba), dependiendo del dibujo requerido.

//+------------------------------------------------------------------+
//| Function for calculating the number of columns                   |
//+------------------------------------------------------------------+
int FuncCalculate(int HistoryInt)
  {
   int ColumnsInt=0;

//--- Zero out auxiliary variables
   Trend=0;                 // Direction of the price trend
   BeginPrice=OpenPrice[0]; // Starting price for the calculation
   FirstTrend=0;            // Direction of the initial market trend
   Columns=0;               // Variable for the calculation of columns
   InterimOpenPrice=0;
   InterimClosePrice=0;
   NumberCell=0;            // Variable for the calculation of cells
//--- Loop for the calculation of the number of main buffers (column opening and closing prices)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Downtrend
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
           }
         //--- Uptrend
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice;
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
           }
         BeginPrice=InterimClosePrice;
         ColumnsInt++;
         FirstTrend=Trend;
        }
      //--- Determine further actions in case of the downtrend
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice+(Cell*Tick);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
      //--- Determine further actions in case of the uptrend
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            ColumnsInt++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpenPrice=BeginPrice-(Cell*Tick);
            InterimClosePrice=BeginPrice-(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=-1;
            BeginPrice=InterimClosePrice;
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClosePrice=BeginPrice+(NumberCell*Cell*Tick);
            InterimClosePrice=NormalizeDouble(InterimClosePrice,Digits());
            Trend=1;
            BeginPrice=InterimClosePrice;
           }
        }
     }
//---
   return(ColumnsInt);
  }

3.  Función para colorear las columnas

Esta función está destinada a colorear las columnas, según sea necesario, mediante una combinación predeterminada de colores. Para ello, vamos a escribir un bucle que recorre el número de columnas y asigna el color adecuado a las columnas pares e impares, teniendo en cuenta el valor inicial de la tendencia (columna inicial).

//+------------------------------------------------------------------+
//| Function for coloring columns                                    |
//+------------------------------------------------------------------+
int FuncColor(int ColumnsInt)
  {
   int x;
//--- Fill the buffer of colors for drawing
   for(x=0; x<ColumnsInt; x++)
     {
      if(FirstTrend==-1)
        {
         if(x%2==0) CandlesBufferColor[x]=1; // All even buffers of color 1
         if(x%2>0) CandlesBufferColor[x]=0;  // All odd buffers of color 0
        }
      if(FirstTrend==1)
        {
         if(x%2==0) CandlesBufferColor[x]=0; // All odd buffers of color 0
         if(x%2>0) CandlesBufferColor[x]=1;  // All even buffers of color 1
        }
     }
//---
   return(x);
  }

4.  Función para determinar el tamaño de la columna

Una vez hayamos determinado el número de columnas a utilizar y hayamos establecido los colores necesarios, tenemos que determinar la altura de las columnas. Para ello, vamos a crear matrices temporales, InterimOpen[] y InterimClose[], en las cuales almacenaremos los precios de apertura y cierre de cada columna. El tamaño de estas matrices será igual al número de columnas.

A continuación, tendremos un bucle que es prácticamente idéntico al bucle de la función FuncCalculate(), la diferencia está en que, además de todo lo anterior, almacena también los precios de apertura y cierre para cada columna. Se implementa esta división con el fin de conocer el número de columnas en el gráfico con antelación. En teoría, debemos establecer inicialmente un mayor número de columnas a propósito para la asignación de memoria de la matriz y solo utilizar un bucle. Pero en este caso, tendríamos un mayor uso de los recursos de la memoria.

Veamos ahora con más detalle cómo se determina la altura de una columna. Después de que el precio haya recorrido una distancia igual al número requerido de tamaños de celda, calcularemos su número, redondeando al número entero más próximo. A continuación, añadimos el número total de tamaños de celda de la columna actual al precio de apertura de la columna, obteniendo así el precio de cierre de la columna, que será también el último precio utilizado. Se utilizará en todas las etapas posteriores.

//+------------------------------------------------------------------+
//| Function for determining the column size                         |
//+------------------------------------------------------------------+
int FuncDraw(int HistoryInt)
  {
//--- Determine the sizes of temporary arrays
   ArrayResize(InterimOpen,Columns);
   ArrayResize(InterimClose,Columns);
//--- Zero out auxiliary variables
   Trend=0;                 // Direction of the price trend
   BeginPrice=OpenPrice[0]; // Starting price for the calculation
   NumberCell=0;            // Variable for the calculation of cells
   int z=0;                 // Variable for indices of temporary arrays
//--- Loop for filling the main buffers (column opening and closing prices)
   for(int x=0; x<HistoryInt; x++)
     {
      if(Trend==0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
        {
         //--- Downtrend
         if(((BeginPrice-OpenPrice[x])/Tick)>0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
           }
         //--- Uptrend
         if(((BeginPrice-OpenPrice[x])/Tick)<0)
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice;
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits()); // Normalize the number of decimal places
            Trend=1;
           }
         BeginPrice=InterimClose[z];
        }
      //--- Determine further actions in case of the downtrend
      if(Trend==-1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice+(Cell*Tick);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
      //--- Determine further actions in case of the uptrend
      if(Trend==1)
        {
         if(((BeginPrice-OpenPrice[x])/Tick)>0 && (Cell*CellForChange)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            z++;
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimOpen[z]=BeginPrice-(Cell*Tick);
            InterimClose[z]=BeginPrice-(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=-1;
            BeginPrice=InterimClose[z];
           }
         if(((BeginPrice-OpenPrice[x])/Tick)<0 && (Cell)<fabs((BeginPrice-OpenPrice[x])/Tick))
           {
            NumberCell=fabs((BeginPrice-OpenPrice[x])/Tick)/Cell;
            NumberCell=MathRound(NumberCell);
            InterimClose[z]=BeginPrice+(NumberCell*Cell*Tick);
            InterimClose[z]=NormalizeDouble(InterimClose[z],Digits());
            Trend=1;
            BeginPrice=InterimClose[z];
           }
        }
     }
//---
   return(z);
  }

5.  Función para la inversión de la matriz

La función invierte la matriz de datos de la columna obtenida con el fin de representar el gráfico de derecha a izquierda mediante programación. Se lleva a cabo la inversión de la matriz en un bucle, con los valores High (máximo) y Low (mínimo) asignados a las velas. Se hace esto debido a que el indicador solo se muestra para las velas que tengan todos sus buffers de indicador distintos de cero.

//+------------------------------------------------------------------+
//| Function for array reversal                                      |
//+------------------------------------------------------------------+
int FuncTurnArray(int ColumnsInt)
  {
//--- Variable for array reversal
   int d=ColumnsInt;
   for(int x=0; x<ColumnsInt; x++)
     {
      d--;
      CandlesBufferOpen[x]=InterimOpen[d];
      CandlesBufferClose[x]=InterimClose[d];
      if(CandlesBufferClose[x]>CandlesBufferOpen[x])
        {
         CandlesBufferHigh[x]=CandlesBufferClose[x];
         CandlesBufferLow[x]=CandlesBufferOpen[x];
        }
      if(CandlesBufferOpen[x]>CandlesBufferClose[x])
        {
         CandlesBufferHigh[x]=CandlesBufferOpen[x];
         CandlesBufferLow[x]=CandlesBufferClose[x];
        }
     }
//---
   return(d);
  }

6.  Función para dibujar líneas horizontales

Esta función genera una cuadrícula de "celdas" usando líneas horizontales (objetos). Al principio de la función, determinamos los valores del precio máximo y mínimo a partir de la matriz de cálculo de datos. Se usan estos valores más adelante para trazar gradualmente las líneas hacia arriba o hacia abajo desde el punto de inicio.

//+------------------------------------------------------------------+
//| Function for drawing horizontal lines                            |
//+------------------------------------------------------------------+
int FuncDrawHorizontal(bool Draw)
  {
   int Horizontal=0;
   if(Draw==true)
     {
      //--- Create horizontal lines (lines for separation of columns)
      ObjectsDeleteAll(0,ChartWindowFind(),OBJ_HLINE); // Delete all old horizontal lines
      int MaxPriceElement=ArrayMaximum(OpenPrice);     // Determine the maximum price level
      int MinPriceElement=ArrayMinimum(OpenPrice);     // Determine the minimum price level
      for(double x=OpenPrice[0]; x<=OpenPrice[MaxPriceElement]+(Cell*Tick); x=x+(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      for(double x=OpenPrice[0]-(Cell*Tick); x>=OpenPrice[MinPriceElement]; x=x-(Cell*Tick))
        {
         ObjectCreate(0,DoubleToString(x,Digits()),OBJ_HLINE,ChartWindowFind(),0,NormalizeDouble(x,Digits()));
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_COLOR,LineColor);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_STYLE,STYLE_DOT);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_SELECTED,false);
         ObjectSetInteger(0,DoubleToString(x,Digits()),OBJPROP_WIDTH,1);
         Horizontal++;
        }
      ChartRedraw();
     }
//---
   return(Horizontal);
  }

Ahora que hemos descrito todas las funciones básicas, veamos en qué orden se les llama en OnCalculate():

// +++ Main calculations and plotting +++
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- Reverse the array to conveniently get the last price value
   ArraySetAsSeries(close,true);
//---
   if(prev_calculated==0)
     {
      //--- Start the function for copying data for the calculation
      int ErrorCopy=FuncCopy(History);
      //--- In case of error, print the message
      if(ErrorCopy==-1)
        {
         Alert("Failed to copy. Data is still loading.");
         return(0);
        }
      //--- Call the function for calculating the number of columns
      Columns=FuncCalculate(History);
      //--- Call the function for coloring columns
      int ColorCalculate=FuncColor(Columns);
      //--- Call the function for determining column sizes
      int z=FuncDraw(History);
      //--- Start the function for array reversal
      int Turn=FuncTurnArray(Columns);
      //--- Start the function for drawing horizontal lines
      int Horizontal=FuncDrawHorizontal(true);
      //--- Store the value of the last closing price in the variable
      OldPrice=close[0];
     }
//--- If the price is one box size different from the previous one, 
//--- the indicator is recalculated
   if(fabs((OldPrice-close[0])/Tick)>Cell)
      return(0);
//--- return value of prev_calculated for next call
   return(rates_total);
  }
// +++ Main calculations and plotting +++

Hemos llegado al final del código principal del indicador. Sin embargo, debido al inconveniente de contener números complejos, a veces es necesario volver a cargarlo.

Para implementar esto, usaremos la función OnChartEvent() que controla los eventos de pulsar la tecla "С"; borrar y la tecla  "R"; redibujar. Para borrar, se asigna el valor cero a alguno de los buffers de indicador. La función para redibujar el gráfico representa una repetición de los cálculos anteriores y una asignación de los valores a los buffers de indicador.

// +++ Secondary actions for the "С" key - clear and the "R" key - redraw +++
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- 67 - The "C" key code clears the indicator buffer
      if(lparam==67)
        {
         for(int x=0; x<Bars(Symbol(),PERIOD_CURRENT); x++)
            CandlesBufferOpen[x]=0;
         ChartRedraw();
        }
      // 82 - The "R" key code redraws the indicator
      if(lparam==82)
        {
         //--- Start the copying function
         int ErrorCopy=FuncCopy(History);
         //--- In case of error, print the message
         if(ErrorCopy==-1)
            Alert("Failed to copy data.");
         //--- Call the function for calculating the number of columns
         Columns=FuncCalculate(History);
         //--- Call the function for coloring columns
         int ColorCalculate=FuncColor(Columns);
         //--- Call the function for determining column sizes
         int z=FuncDraw(History);
         //--- Start the function for array reversal
         int Turn=FuncTurnArray(Columns);
         //--- Start the function for drawing horizontal lines
         int Horizontal=FuncDrawHorizontal(true);
        }
     }
  }
//+------------------------------------------------------------------+
// +++ Secondary actions for the "С" key - clear and the "R" key - redraw +++

Ahora podemos respirar hondo ya que hemos terminado con la descripción del algoritmo y el código del indicador y podemos proceder a echar un vistazo a algunos patrones de gráficos de punto y figura que generan señales para la ejecución de las operaciones de trading.

 

Señales estándar

En el trading hay dos enfoques de gráficos de punto y figura: basados en patrones y basados en las líneas de soporte y resistencia. Este último se caracteriza en que las líneas de soporte y resistencia se dibujan con un ángulo de 45 grados (esto no es siempre así en un indicador diseñado, ya que se dibuja mediante velas japonesas cuyo tamaño cambia en función del tamaño del gráfico principal, que puede provocar una distorsión del ángulo).

Veamos ahora los patrones como tal:

  1. Patrones de "Doble techo" (Double Top) y "Doble suelo" (Double Bottom).

    El "doble techo" se produce cuando el precio aumenta, luego cae formando una columna de O y vuelve a aumentar, superando la columna anterior en un "tamaño de celda". Este patrón es una señal de compra.

    El "doble suelo" es exactamente lo contrario del patrón "doble techo". Existe cierta caída del precio (columna de O) seguida de una columna de X y otra de O que cae en un "tamaño de celda" por debajo de la columna anterior, formando así una señal de venta.

    Fig. 3. Patrones de "Doble techo" y "Doble suelo".

    Fig. 3. Patrones de "Doble techo" y "Doble suelo".

  2. Patrones de "Triple techo" (Triple Top) y "Triple suelo" (Triple Bottom).

    Estos patrones son menos frecuentes pero representan señales muy fuertes. En esencia, son similares a los patrones "doble techo" y "doble suelo", siendo su continuación. Antes de enviar la señal, repiten los movimientos de los dos patrones anteriores.

    Se produce un "triple techo" cuando el precio aumenta dos veces hasta el mismo nivel y luego rompe este nivel hacia arriba, representando una señal de compra.

    El patrón "triple suelo" es lo contrario de "triple techo" y se produce cuando el precio cae dos veces hasta el mismo nivel y luego rompe este nivel hacia abajo, enviando así una señal de venta.

    Fig. 4. Patrones de "Triple techo" y "Triple suelo".

    Fig. 4. Patrones de "Triple techo" y "Triple suelo".

  3. Patrones de "ruptura de triángulo simétrico": subida y bajada.

    Todos recordamos los patrones de análisis técnico. El patrón de "ruptura de triángulo simétrico" es parecido al "triángulo simétrico" del análisis técnico. Un ruptura al alza (como se muestra en la siguiente figura a la izquierda) es una señal de compra. A la inversa, una ruptura a la baja es una señal de venta (figura de la derecha).

    Fig. 5. "Ruptura de triángulo simétrico": subida y bajada.

    Fig. 5. "Ruptura de triángulo simétrico": subida y bajada.

  4. Patrones de "catapulta alcista" y "catapulta bajista".

    Las "catapultas" son de algún modo parecidas a los patrones "triángulo ascendente" y "triángulo descendente" del análisis técnico. Sus señales son básicamente iguales; mientras el precio rompe por encima o debajo del lado paralelo del triángulo, se produce una señal de compra o venta, respectivamente. En el caso de la "catapulta alcista", el precio rompe hacia arriba, tratándose de una señal de compra (figura de la izquierda), mientras que en el caso de la "catapulta bajista", el precio rompe hacia abajo, tratándose de una señal de venta (figura de la derecha).

    Fig. 6. Patrones de "catapulta alcista" y "catapulta bajista".

    Fig. 6. Patrones de "catapulta alcista" y "catapulta bajista".

  5. Patrón de la "línea de tendencia de 45 grados".

    El patrón de la "línea de tendencia de 45 grados" crea una línea de soporte o de resistencia. Si hay una ruptura de dicha línea, obtenemos o una señal de venta (como se muestra en la figura de la derecha) o una señal de compra (figura de la izquierda).

    Fig. 7. Patrón de la "línea de tendencia de 45 grados".

    Fig. 7. Patrón de la "línea de tendencia de 45 grados".

Hemos repasado los patrones y señales del gráfico de punto y figura estándar. Veamos ahora algunos de ellos en el gráfico del indicador proporcionado al principio de este artículo:

Fig. 8. Identificación de patrones en el gráfico de punto y figura.

Fig. 8. Identificación de patrones en el gráfico de punto y figura.

 

Conclusión

Hemos llegado a la última parte del artículo. Me gustaría señalar que el gráfico de punto y figura no ha desaparecido con el tiempo y se sigue utilizando activamente, lo que demuestra una vez más su importancia.

El indicador desarrollado, a pesar de no estar exento de inconvenientes, tales como el uso de diagramas de bloques en lugar de los habituales X y O y la imposibilidad de probarlo (o más bien, un funcionamiento erróneo) en el Probador de Estrategias, proporciona unos resultados gráficos bastante precisos.

También quiero resaltar que este algoritmo, en una versión ligeramente modificada, puede usarse potencialmente para el gráfico Renko, como para la integración de ambos tipos de gráfico en un solo código con opciones de menú, permitiéndole seleccionar el tipo de gráfico apropiado. No descarto la posibilidad de dibujar el gráfico directamente en la ventana principal, que requiere otra vez una pequeña modificación del código.

En general, el objetivo de este artículo es compartir mis ideas acerca del desarrollo del indicador. Siendo este mi primer artículo, agradecería cualquier comentario u opinión. ¡Gracias por su interés en mi artículo!