Dibujar los canales; visión interna y externa

Dmitriy Skub | 1 abril, 2014


Introducción

Supongo que no es ninguna exageración decir que los canales representan la herramienta más popular para el análisis del mercado y la toma de decisiones de trading por detrás de los promedios móviles. En el primer artículo de esta serie, dedicado a los canales, vamos a hablar de la base matemática y teórica de la implementación de un indicador que dibuja un canal definido por tres extremos en la pantalla del terminal de cliente.

A primera vista, el dibujo de un canal parece una tarea fácil, ya que se basa en la ecuación de una línea recta, que se enseña en primaria. Sin embargo, su implementación práctica en el terminal de cliente implica muchas preguntas que no pueden tener una respuesta sencilla.

¿Cuál es la mejor manera de organizar los ajustes de los valores extremos y hacer el seguimiento de sus cambios? ¿Qué hacer y cómo dibujar un canal cuando su parte media se encuentra en las barras que faltan? ¿Qué sucede cuando el valor extremo izquierdo de un canal es un viernes y el derecho es un lunes, y entre los dos se encuentran los días festivos sin barras? ¿Cómo podemos obtener los valores actuales de los bordes de un canal?

En el primero de esta serie de artículos sobre los canales, se va a responder a estas y a algunas otras preguntas. Este artículo incluye también la implementación del dibujo de los canales a través de tres extremos determinados, y mediante las clases estándar y el enfoque orientado a objetos. Vamos a implementar la herramienta del dibujo del canal en forma de un indicador.


Definir los extremos

En realidad, se determina la posición de un canal en un gráfico mediante un mínimo de tres extremos. Esta puede ser una definición de un extremo: es el valor máximo o mínimo de una función en un intervalo determinado. El punto que alcanza un extremo se llama punto extremo. Respectivamente, si el punto extremo alcanza un mínimo, se le llama punto mínimo, y si alcanza un máximo, se le llama punto máximo.

En el análisis matemático se define otro término; el extremo local (el mínimo y el máximo, respectivamente). En el punto máximo (mínimo), el valor de la función es superior (inferior) a los valores de todos los puntos adyacentes. La definición está tomada de la Wikipedia (traducida del ruso).

Para dibujar los canales, necesitamos los extremos locales. Vamos a mostrarlo gráficamente, sin entrar en las fórmulas matemáticas. En la figura 1 a continuación, hay tres extremos locales señalados con niveles de precios rojos. Los puntos rectangulares muestran dos máximos y un mínimo:

Figura 1. Ejemplos de extremos locales

Figura 1. Ejemplos de extremos locales

No se señalan todos los extremos del gráfico, solo están los más relevantes. Para los gráficos de velas o de barras, es conveniente usar el término "fractal" para definir los extremos; cuando varias barras adyacentes hacia la izquierda y la derecha son estrictamente descendentes o ascendentes (véase la figura 1).

Puesto que no tenemos la intención de realizar una herramienta de dibujo automática del canal, vamos a definir la posición de los extremos cómo se muestra en la figura 1; mediante la posición en los ejes del tiempo y de los precios. Lo más apropiado para este propósito son las etiquetas de precios; los objetos gráficos especiales del terminal de cliente de MetaTrader 5. Una etiqueta de precio posee las propiedades de las coordenadas del tiempo y del precio, que permiten identificar claramente un punto extremo en un gráfico.


El objeto para almacenar los extremos: la clase TExtremum

Lo primero que haremos es el desarrollo de una clase contenedora para almacenar los extremos y una clase para manejar un conjunto de extremos. Puesto que vamos a usar las clases estándar incluidas en el terminal tanto como sea posible, la clase TExtremum será heredada de la clase estándar CObject. A continuación, se muestra el código de nuestra clase:

class TExtremum : public CObject
{
private:
  datetime  extr_datetime;              // data/time in an extremum point
  double    extr_price;                 // price in an extremum point
        
protected:
  virtual int  Compare(const CObject* _node, int _mode = 0) const;

public:
  void      TExtremum();               // constructor
  void      ~TExtremum();              // destructor
  void      SetExtremum(datetime _time, double _price);  // change date/time and price in an extremum point
  void      SetDateTime(datetime _time);                 // change date/time in an extremum point
  void      SetPrice(double _price);  // change price in an extremum point

public:
  datetime  GetDateTime() const;      // get date/time in an extremum point
  double    GetPrice() const;         // get price in an extremum point

public:
  virtual bool  SaveExtremum(string _dt_name, string _p_name);  // save extremum
  virtual bool  LoadExtremum(string _dt_name, string _p_name);  // load extremum
  virtual bool  DeleteExtremum(string _dt_name, string _p_name);// delete extremum
};

La mayoría de los métodos son irrelevantes, y no vale la pena prestar atención a su implementación. Debemos detenernos en el método TExtremum::Compare. Se declara este método en la clase CObject y se utiliza para ordenar una lista. Lo hemos implementado del siguiente modo:

//---------------------------------------------------------------------
//  Comparing two extremums by time:
//---------------------------------------------------------------------
int TExtremum::Compare(const CObject* _node, int _mode = 0) const
{
  datetime  temp = ((TExtremum* )_node).GetDateTime();
  datetime  curr = GetDateTime();
  if(curr > temp)
  {
    return(_mode > 0 ? 1 : -1);
  }
  else if(curr < temp)
  {
    return(_mode > 0 ? -1 : 1);
  }

  return(0);
}

Se utiliza el parámetro _mode para configurar la dirección del orden. Si es positivo, entonces es directa (orden ascendente), de lo contrario, es inversa (orden descendente).

Además, hay dos métodos que sirven para guardar y cargar los extremos. Vamos a almacenar nuestros extremos en variables globales. Los métodos son los siguientes:

//---------------------------------------------------------------------
//  Save extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::SaveExtremum(string _dt_name, string _p_name)
{
  datetime  dt_result = GlobalVariableSet(_dt_name, (double)extr_datetime);
  datetime  p_result = GlobalVariableSet(_p_name, (double) extr_price);
  if(dt_result != 0 && p_result != 0)
  {
    return(true);
  }

  return(false);
}

//---------------------------------------------------------------------
//  Load extremum (date/time):
//---------------------------------------------------------------------
bool TExtremum::LoadExtremum(string _dt_name, string _p_name)
{
  double  dt_temp, p_temp;
  bool    result = GlobalVariableGet(_dt_name, dt_temp);
  result &= GlobalVariableGet(_p_name, p_temp);
  if(result != false)
  {
    extr_datetime = (datetime)dt_temp;
    extr_price = p_temp;
    return(true);
  }

  return(false);
}

Los dos métodos de lectura y escritura en las variables globales TExtremum::LoadExtremum y TExtremum::SaveExtremum devuelven 'true' en caso de una ejecución satisfactoria.


Manejar la lista de los extremos: la clase TExtremumList

Ya que necesitamos almacenar y ordenar los extremos por el tiempo, tenemos que heredar la clase TExtremumList a partir de la clase estándar CList. Con esta herencia obtenemos un manipulador universal de los extremos, sin límites de cantidad o de tipo. Esto permite ampliar aún más el número de canales dibujados. Por ejemplo, si hay una regresión no lineal mediante varios extremos, podemos aumentar el número de dibujos del canal.

A continuación, se muestra el código de esta clase:

class TExtremumList : public CList
{
private:
  string              channel_prefix;     // channel name (prefix)
  ENUM_TIMEFRAMES      chart_timeframe;    // current timeframe
  string              chart_symbol;       // work symbols of the chart

protected:
  string    MakeDTimeName(int _nmb);     // get name for saving/reading data/time of an extremum
  string    MakePriceName(int _nmb);     // get name for saving/reading price of an extremum

public:
  void      TExtremumList();             // конструктор
  void     ~TExtremumList();             // деструктор
  void     SetChannelParams(string _pref, string _symbol = NULL, ENUM_TIMEFRAMES _curr_tf = PERIOD_CURRENT);
  void     AddExtremum(datetime _time, double  _price);
  void     DeleteAllExtremum();
  void     SaveExtremumList();
  void     LoadExtremumList();
  int      FindExtremum(datetime _dt);  // search extremum by specified time

public:
  datetime GetDateTime(int _index);
  double   GetPrice(int _index);
};

El método principal de la clase es TExtremumList::AddExtremum. Sirve para añadir un nuevo extremo a la lista. Después de añadir un extremo, se ordenan los extremos en la lista mediante el tiempo del punto extremo. El código de este método se muestra a continuación:

void TExtremumList::AddExtremum(datetime _time, double  _price)
{
//  Create extremum:
  TExtremum*    extr = new TExtremum();
  extr.SetExtremum(_time, _price);

//  Add it in the list:
  Add(extr);

//  Sort:
  Sort(1);
}

Se usan los siguientes métodos de la clase base: CList::Add; para añadir un nuevo elemento a la lista, y CList::Sort; para ordenar los elementos de la lista. Se usa el método TExtremum::Compare en CList::Sort.

Vamos a echar un vistazo al método de búsqueda de un extremo con el correspondiente tiempo en la lista TExtremumList::FindExtremum. El código del método se muestra a continuación:

int TExtremumList::FindExtremum(datetime _dt)
{
  int           k = 0;
  TExtremum*    extr = (TExtremum*)(GetFirstNode());
  while(extr != NULL)
  {
    if(extr.GetDateTime() == _dt)
    {
      return(k);
    }
    extr = (TExtremum*)(GetNextNode());
  }
  return(-1);                     // extremum not found
}

A continuación, se utilizan los método de la clase base: CList::GetFirstNode; para obtener el primer elemento de la lista (si la lista está vacía, devuelve el puntero cero), y CList::GetNextNode; para obtener el siguiente elemento de la lista (si no existe el elemento siguiente y la lista está finalizada, se devuelve el puntero cero).

Observación:

Hay un puntero del elemento actual en los datos internos de la lista de clase CList. Este puntero cambia al llamar a los métodos para moverse en la lista (CList::GetFirstNode, CList::GetNextNode, CList::GetPrevNode, etc.) Si no se ha llamado a ninguno de estos métodos antes, el puntero del elemento actual apunta al primero.

En el caso de encontrar un extremo con el tiempo indicado, el método TExtremumList::FindExtremum indexa el elemento encontrado. Si no existe dicho elemento, devuelve -1.

Los métodos TExtremum::MakeDTimeName y TExtremum::MakePriceName son adicionales. Sirven para obtener los nombres de las variables globales que se usan al guardar y leer los extremos. Esta es la implementación de estos métodos:

string TExtremumList::MakeDTimeName(int _nmb)
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_DTime_Extr", _nmb);
  return(name);
}

string TExtremumList::MakePriceName( int _nmb )
{
  string    name;
  StringConcatenate(name, channel_prefix, "_", channel_symbol, "_", channel_timeframe, "_Price_DTime_Extr", _nmb);
  return(name);
}

Un ejemplo del nombre obtenido: "MainChannel_EURUSD_5_DTime_Extr1". Dicho símbolo corresponde a un punto extremo temporal del canal MainChannel (nombre convencional), al símbolo EURUSD, al período de tiempo 5M y al número de extremo 1. Se asigna el número de un extremo en el orden ascendiente de su tiempo, empezando desde 1. De hecho, es el índice desplazado con 1 en el orden ascendente de la lista.

En la siguiente figura, se muestra un ejemplo de almacenamiento del valor de tres extremos en el terminal:

Figura 2. Los extremos almacenados en las variables globales

Figura 2. Los extremos almacenados en las variables globales

Se adjuntan las clases descritas antes en el archivo ExtremumClasses.mqh de este artículo.


Indicador para establecer los extremos manualmente: ExtremumHandSet

Bien, disponemos de todo lo necesario para desarrollar el primer indicador, mediante el cual vamos a establecer la posición de los extremos en el modo manual. Se adjunta el código del indicador en el archivo ExtremumHandSet.MQ5 del artículo. Analicemos su dibujo en detalle.

En primer lugar, vamos a imaginar gráficamente lo que queremos ver en la pantalla:

Figura 3. El indicador de establecimiento de los extremos

Figura 3. El indicador de establecimiento de los extremos

Mediante las etiquetas de precios de la izquierda, establecemos las posiciones de los extremos en los ejes de tiempo y precio del gráfico. El indicador debe determinar la posición de estas etiquetas en el gráfico, mostrar temporalmente los puntos extremos en la pantalla y almacenarlos en las variables globales del terminal de cliente en el formato descrito anteriormente. Además, el indicador debe hacer un seguimiento del movimiento de las etiquetas de precios en el gráfico y corregir los puntos extremos cargados temporalmente.

El seguimiento del movimiento de las etiquetas de precios en el gráfico se hace una vez cada segundo. De este modo, el sistema no dependerá de las cotizaciones entrantes y los días laborables y festivos.

En primer lugar, vamos a conectar las librerías necesarias:

//---------------------------------------------------------------------
//  Included libraries:
//---------------------------------------------------------------------
#include  <TextDisplay.mqh>
#include  <ExtremumClasses.mqh>

La primera librería contiene las clases que se utilizan para la organización y la visualización de la información del texto en la pantalla (véase el artículo "Cree su propia Observación del Mercado usando las clases de la librería estándar"). Usándola, vamos a mostrar los valores de los puntos extremos temporales.

Después añadimos los parámetros de entrada del indicador (aquí, se describen solo los principales):

input string  PrefixString = "MainChannel";
//---------------------------------------------------------------------
input color   ExtremumPointColor = Yellow;
//---------------------------------------------------------------------
input bool    ShowInfo = true;

El primer parámetro PrefixString establece un prefijo que se utiliza para componer los nombres de las variables globales durante la escritura o lectura de un extremo. También ofrece la posibilidad de usar varios indicadores de este tipo en un solo gráfico. Lo único que hay que hacer es asignarles distintos prefijos.

El parámetro ExtremumPointColor establece el color de las etiquetas de los precios de la izquierda que determinan la posición de los extremos. Las etiquetas de los precios deben ser de un color determinado. Se comprueba esta condición en el indicador. Se ignoran las etiquetas con parámetros diferentes.

El parámetro ShowInfo controla la visualización de la información del texto acerca de los puntos extremos indicados en la pantalla.

A continuación, vamos a crear los objetos para la visualización de la información y el manejo de los extremos:

TableDisplay    TitlesDisplay;    // displaying information on the screen
//---------------------------------------------------------------------
TExtremumList*  PrevExtr_List;    // list of previous extremums
TExtremumList*  CurrExtr_List;    // list of current extremums
TExtremumList*  NewExtr_List;     // list of new extremums

Se inicializan estos objetos en las siguientes líneas:

PrevExtr_List = new TExtremumList();
PrevExtr_List.SetChannelParams(PrefixString, Symbol(), Period());
PrevExtr_List.LoadExtremumList();

CurrExtr_List = PrevExtr_List;

NewExtr_List = new TExtremumList();
NewExtr_List.SetChannelParams(PrefixString, Symbol(), Period());

Hemos cargado los extremos en la lista PrevExtr_List a partir de las variables globales mediante el método TExtremumList::LoadExtremumList. Esto almacenará los extremos para compararlos con los nuevos, cuya lectura se hará a partir del gráfico, al arrastrar las etiquetas de los precios en la pantalla.

Se usa la lista CurrExtr_List como lista actual; almacena los extremos actuales. Puesto que al principio solo disponemos de los extremos leídos a partir las variables globales, se consideran como las actuales.

En la lista NewExtr_List vamos a escribir los nuevos extremos que se encuentran en el gráfico.

Vamos a echar un vistazo a las funciones principales que se usan en el indicador. Se usa la primera función FindExtremumPoints para la lectura y la comprobación de los parámetros de las etiquetas de los precios que determinan los extremos:

bool FindExtremumPoints(long _chart_id)
{
  string  name;

//  1. Search for the total number of objects with specified parameters and write them to the list:
  int total_objects = ObjectsTotal(_chart_id, -1, OBJ_ARROW_LEFT_PRICE);
  if(total_objects <= 0)
  {
    return(false);
  }

  NewExtr_List.Clear();
  for(int i = 0; i < total_objects; i++)
  {
    name = ObjectName(_chart_id, i, -1, OBJ_ARROW_LEFT_PRICE);

    if( IsGraphicObjectGood(_chart_id, name, OBJ_ARROW_LEFT_PRICE, ExtremumPointColor) == true)
    {
      NewExtr_List.AddExtremum(ObjectGetInteger( _chart_id, name, OBJPROP_TIME),
                               ObjectGetDouble(_chart_id, name, OBJPROP_PRICE));
    }
  }

//  2. If three extremums are found, we can try to draw a channel:
  if(NewExtr_List.Total() == 3)
  {

//  Save the list of new extremums:
    NewExtr_List.SaveExtremumList();
    return(true);
  }

  NewExtr_List.Clear();
  return(false);
}

En primer lugar, se borra la lista NewExtr_List llamando al método TExtremumList::Clear, y a continuación se le añaden todos los puntos extremos con los parámetros indicados. Si se encuentran tres puntos, entonces la lista los almacena en las variables globales y la función devuelve "true".

La otra función CheakExtremumMoving, hace el seguimiento del movimiento de los puntos extremos en el gráfico. Si se mueve por lo menos un punto a lo largo del eje del tiempo, esta función devuelve "true".

A continuación, se muestra su código:

//---------------------------------------------------------------------
//  Check whether extremums have been moved on the screen:
//---------------------------------------------------------------------
bool CheakExtremumMoving()
{
  if(FindExtremumLines(0) == true)
  {
    int  count = NewExtr_List.Total();
    int  index;
    for(int i = 0; i < count; i++)
    {
      index = CurrExtr_List.FindExtremum(NewExtr_List.GetDateTime(i));

//  If a new extremum is found:
      if(index == -1)
      {
        PrevExtr_List = CurrExtr_List;
        CurrExtr_List = NewExtr_List;
        return(true);
      }
    }
    CurrExtr_List = PrevExtr_List;
  }

  return(false);
}

Hemos considerado el modo manual de establecimiento de los puntos extremos. Tenemos un indicador preparado y que permite controlar este proceso y escribir los puntos en las variables globales. Se adjunta el código completo del indicador en el archivo ExtremumHandSet.mq5. Ahora podemos pasar a la parte principal; dibujar un canal.


Dibujar un canal: un poco de teoría

Un canal lineal está formado por dos líneas paralelas que pasan necesariariamente por los puntos extremos. Además, una línea debe pasar por dos puntos, mientras que la otra debe pasar por el punto restante y ser paralela a la primera línea. Se puede ver esto en esta sencilla figura:

Dibujar un canal mediante tres puntos extremos

Figura 4. Dibujar un canal mediante tres puntos extremos

De lo que aprendimos en geometría, solo se puede dibujar una línea recta entre dos puntos. Esta línea es de color rojo en la figura 4. Pasa por dos puntos con las siguientes coordenadas; (T1,P1) y (T2,P2); se señalan los puntos con las letras A y B. La ecuación de esta línea es:

(1)   P(t) = P1 + (t - T1)*(P2 - P1) / (T2 - T1); en este caso, P(t) es el precio calculado en el tiempo 't'.


Tenemos que dibujar otra línea recta paralela a la primera y que pase por el punto C (el tercer extremo). Esta línea es de color verde en la figura 4. Dado que los puntos T1 y T2 son los mismos para ambas líneas, debemos encontrar los valores de P1' y P2' (véase la figura 4).

Antes de continuar, tenemos que hacer una observación importante. El gráfico del terminal no muestra los "parones" del tiempo. Por ejemplo, en los días festivos, cuando no llegan cotizaciones al terminal, se tienen que mostrar como interrupciones del precio. Y es malo que no lo sean. ¿Qué sentido tiene ver un gráfico vació? No obstante, si usamos el tiempo absoluto en la ecuación anterior, obtendremos un canal erróneo.

Afortunadamente, esta situación tiene solución. Si cambiamos el tiempo absoluto por el número relativo de barras, entonces podremos utilizar estas coordenadas para dibujar un canal, ya que la numeración de las barras no puede tener interrupciones (de hecho, es el índice en la matriz de precios).

Si vamos más allá y suponemos que el punto A de la figura 4 siempre está en la coordenada cero (barra cero) del eje del tiempo, nuestra ecuación será aún más sencilla. Así que, T1=0, T3=B3 y Т2=В2. B3 y B2 representan los números de barras en relación al punto T1 (es decir, el punto cero). Está claro que esta suposición no lleva a una inclinación de la línea. De modo que obtenemos la siguiente ecuación de una línea recta que pasa por los puntos A y B:

(2)   P(n) = P1 + n * (P2-P1) / B2, donde P(n) es el precio calculado para la barra número 'n'.


Así que, conocemos los valores de P1, P2, P3 y B2, B3. Ahora tenemos que averiguar los valores de P1' y P2'. Combinando las dos ecuaciones y resolviéndolas, obtenemos las siguientes fórmulas, mediante las cuales podemos despejar las incógnitas:

(3)   P1' = P3 - B3 * (P2 - P1) / B2

(4)   P2' = P2 - P1 + P1'


Al encontrar el valor de P1' y sustituirlo en la fórmula (4), obtendremos el valor de P2'. Disponemos ahora de la base teórica para el dibujo de un canal. Vamos a comenzar con su implementación.


Dibujar los bordes del canal: la clase TChannelBorderObject

Esta clase es una derivada de la clase estándar CChartObjectTrend. Sirve para almacenar todos los parámetros relacionado con los bordes de un canal, así como dibujar o eliminar las líneas de los bordes y controlar los parámetros gráficos de estas líneas.

A continuación, se muestra el código de esta clase:

class TChannelBorderObject : public CChartObjectTrend
{
//  General properties of a border:
private:
  bool             is_created;       // whether the graphical object is created on the screen
  long             chart_id;         // identifier of the chart window
  int              window;           // identifier of the subwindow

//  Parameters of a border line:
private:
  string           border_name;      // name of the border line
  color            border_color;     // color of the border line
  int              border_width;     // thickness of the border line
  ENUM_LINE_STYLE   border_style;     // style of the border line

//  Coordinates of a border:
private:
  datetime         point_left;       // time of the left point (T1)
  datetime         point_right;      // time of the right point (T2)
  double           price_left;       // price of the left point (P1)
  double           price_right;      // price of the right point (P2)

public:
  void     TChannelBorderObject();  // constructor
  void    ~TChannelBorderObject();  // destructor
  bool     IsCreated();             // check whether the line is created

//  Creating/deleting a line:
public:
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right, color _color, int _width, ENUM_LINE_STYLE _style);
  bool     CreateBorder(long _chart_id, int _window, string _name, datetime _t_left, datetime _t_right, 
                           double _p_left, double _p_right);
  bool     CreateBorder(datetime _t_left, datetime _t_right, double _p_left, double _p_right);
  bool     RemoveBorder();          // delete line from the chart

//  Setting parameters of the line:
public:
  void     SetCommonParams(long _chart_id, int _window, string _name);
  bool     SetBorderParams(color _color, int _width, ENUM_LINE_STYLE _style);
  bool     SetBorderColor(color _color);
  bool     SetBorderWidth(int _width);
  bool     SetBorderStyle(ENUM_LINE_STYLE _style);

//  Getting values on the line:
  double   GetPrice(datetime _dt); // get price value in the specified position of the border line
};

Esta clase no requiere ningún comentario especial.

Preste atención únicamente al método para obtener el precio del borde en un punto determinado:

//---------------------------------------------------------------------
//  Get price value in the specified position of the border line:
//---------------------------------------------------------------------
double TChannelBorderObject::GetPrice(datetime _dt)
{
//  If the graphical object is created:
  if(is_created == true)
  {
    return(ObjectGetValueByTime( chart_id, border_name, _dt));
  }
  return(0.0);
}

Se usa aquí la función del terminal ObjectGetValueByTime; devuelve el valor del precio en un tiempo determinado. Se recomienda usar las posibilidades del terminal en lugar de calcular el valor mediante fórmulas matemáticas.


Dibujar un canal: la clase TSlideChannelObject

Esta clase es una derivada de la clase estándar CList. Su finalidad es la siguiente:

El código de esta clase es demasiado grande para mostrarlo todo aquí. Los que quieran, pueden verlo en el archivo SlideChannelClasses.mqh adjunto a este artículo. Analicemos algunas partes de este código.

En primer lugar, obtiene los valores de B2 y B3 en los puntos T2 y T3 respectivamente (véase la figura 4). Se usa el siguiente código:

//  Get relative shifts in bars relatively to the extremum points:
  total_bars = Bars(symbol, time_frame);     // total number of bars in history
  if(total_bars == 0)
  {
    return(false);                           // channel cannot be drawn
  }
  double  B2 = Bars(symbol, time_frame, point_left, point_right);
  double  B3 = Bars(symbol, time_frame, point_left, point_middle);

Para evitar el caso de las llamadas a barras inexistentes, usamos la función de terminal Bars que devuelve el número de barras en el historial para un determinado símbolo y período. Si aún no se ha creado la información, la función devuelve el valor cero; se usa para la comprobación.

Si la función devuelve un valor distinto de cero, entonces podemos obtener los valores de B2 y B3. Se hace mediante la función Bars pero llamándola de otra forma. Establecemos los límites del tiempo y obtenemos el número de barras en este intervalo. Puesto que nuestro borde izquierdo es el mismo, obtenemos el desplazamiento en barras para los puntos T2 y T3. El desplazamiento para el punto T1 es siempre igual a cero.

Ahora podemos calcular todos los puntos de las líneas del canal. Pueden haber hasta nueve, ya que nuestro canal mostrará (además de las líneas de los bordes inferior y superior) la línea del medio y las líneas de las zonas de porcentaje alrededor de los bordes y la línea del medio.


Analicemos la parte principal del cálculo. Se hace el cálculo por completo en el método TSlideChannelObject::CalcChannel.

//  Coefficient of the line inclination:
  koeff_A = (price_right - price_left) / B2;

//  Price value on the AB line in the point T3:
  double  P3_AB = price_left + B3 * koeff_A;

// Determine the channel type - 2MAX_1MIN или 1MAX_2MIN:
  if(P3_AB > price_middle)              // 2MAX_1MIN
  {
    channel_type = CHANNEL_2MAX_1MIN;

    left_prices[BORDER_UP_INDEX] = price_left;
    right_prices[BORDER_UP_INDEX] = price_right;
        
    left_prices[BORDER_DN_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_DN_INDEX] = left_prices[BORDER_DN_INDEX] + (price_right - price_left);
  }
  else if(P3_AB < price_middle)         // 1MAX_2MIN
  {
    channel_type = CHANNEL_1MAX_2MIN;

    left_prices[BORDER_DN_INDEX] = price_left;
    right_prices[BORDER_DN_INDEX] = price_right;
        
    left_prices[BORDER_UP_INDEX] = price_middle - B3 * koeff_A;
    right_prices[BORDER_UP_INDEX] = left_prices[BORDER_UP_INDEX] + (price_right - price_left);
  }
  else
  {
    return( false );                      // channel cannot be drawn (all extremums are on the same line)
  }

left_prices y right_prices son las matrices que almacenan las coordenadas del precio de las nueve líneas del canal. Ya se conocen las coordenadas del tiempo de todas las líneas del canal.

En primer lugar, determina el coeficiente de la pendiente de la línea (véase la fórmula (2)) koeff_A. Después calculamos el valor del precio de la línea AB en el punto T3 (véase la figura 4). Se hace para determinar el tipo de canal especificado para el dibujo; mediante dos máximos y un mínimo o mediante dos mínimos y un máximo. Comprobamos cuál es el punto más alto en el eje de los precios; el punto C o el punto con las coordenadas (P3', T3). En función de la posición obtenida, el canal será del primer tipo o del segundo.

Un vez determinadas las coordenadas de las dos líneas principales del canal (superior e inferior), el cálculo de las coordenadas de las otras siete líneas ya no es una tarea difícil. Por ejemplo, calculamos las coordenadas de la línea del medio mediante las coordenadas de los bordes superior e inferior del canal, del siguiente modo:

  left_prices[BORDER_MD_INDEX] = (left_prices[BORDER_DN_INDEX] + left_prices[BORDER_UP_INDEX ]) / 2.0;
  right_prices[BORDER_MD_INDEX] = (right_prices[BORDER_DN_INDEX] + right_prices[BORDER_UP_INDEX]) / 2.0;

Solo tiene que sacar el valor medio a partir de los bordes superior e inferior del canal.


Indicador para dibujar un canal mediante extremos especificados: SlideChannel

Bueno, ya tenemos la clase para dibujar un canal. Vamos a escribir ahora un indicador que va a leer los parámetros de los extremos a partir de las variables globales y dibujar un canal en un gráfico. Se ve así:

Figura 5. Ejemplo de un canal dibujado mediante los extremos

Figura 5. Ejemplo de un canal dibujado mediante los extremos

La información acerca del canal dibujado también se muestra aquí; su ancho, la distancia en puntos desde el precio actual hasta los bordes del canal y la línea del medio.

Vamos a conectar las librerías necesarias:

#include  <TextDisplay.mqh>
#include  <SlideChannelClasses.mqh>

La primera librería contiene las clases que se utilizan para la organización y la visualización de la información del texto en la pantalla (véase el artículo "Cree su propia Observación del Mercado usando las clases de la librería estándar"). Usándola, vamos a mostrar los valores de los puntos extremos temporales.

Después añadimos los parámetros de entrada del indicador (aquí, se describen solo los principales):

input string          PrefixString = "MainChannel";
//---------------------------------------------------------------------
input ENUM_TIMEFRAMES  ExtremumTimeFrame = PERIOD_CURRENT;
//---------------------------------------------------------------------
input bool            ShowInfo = true;

El primer parámetro PrefixString, al igual que en el primer indicador ExtremumHandSet, establece un prefijo que se usa para componer el nombre de las variables globales durante la lectura de los extremos. También ofrece la posibilidad de usar varios indicadores de este tipo en un solo gráfico. Lo único que hay que hacer es asignarles distintos prefijos.

El parámetro ExtremumTimeFrame establece el período de tiempo que se va a usar para la lectura de los puntos extremos a partir de las variables globales. Es un parámetro muy útil. Permite dibujar canales simultáneos en distintos períodos de tiempo. Por ejemplo, si establecemos los extremos a partir de H1, podemos dibujar el mismo canal en el período de tiempo M5. Para ello, solo tenemos que añadir nuestro indicador para dibujar los canales en el gráfico de M5; y de modo simultaneo mostrará todos los cambios.

El parámetro ShowInfo controla la visualización de la información del texto acerca de los parámetros del canal en la pantalla.

A continuación, vamos a crear los objetos para la visualización de la información y el dibujo del canal:

TableDisplay         ChannalDisplay;  // displaying of general information about a channel on the screen
TableDisplay         BordersDisplay;  // displaying information about the borders of a channel on the screen
//---------------------------------------------------------------------
TSlideChannelObject  Channel;         // drawing of a channel

Se inicializa el objeto para dibujar un canal del siguiente modo:

  Channel.CreateChannel(PrefixString, 0, 0, Symbol(), period_current, curr_left_point, curr_middle_point, 
                        curr_right_point, curr_left_price, curr_middle_price, curr_right_price);
  Channel.SetBorderWidth(BorderWidth );
  Channel.SetmiddleWidth(middleLineWidth);
  Channel.SetUpBorderColor(UpBorderColor);
  Channel.SetDnBorderColor(DnBorderColor);
  Channel.SetmiddleColor(middleLineColor );
  Channel.ShowBorderZone(ShowBorderPercentageLines);
  Channel.BorderZonePercentage( PercentageZoneSize);
  Channel.Showmiddle(ShowmiddleLine);
  Channel.ShowmiddleZone( ShowmiddlePercentageLines);
  Channel.middleZonePercentage(PercentagemiddleZoneSize);

En este caso, en primer lugar, creamos un canal llamando al método TSlideChannelObject::CreateChannel, y luego establecemos los parámetros necesarios de la línea del canal. El orden no importa, puede hacerlo al revés; estableciendo los parámetros y luego creando el canal.

El parámetro period_current es el período utilizado durante la lectura de los extremos a partir de las variables globales. Puede ser distinto del período del gráfico actual.

Vamos a echar un vistazo a las funciones principales que se usan en el indicador. Se usa la primera función GetExtremums para la lectura de la posición de los extremos y para la actualización del canal en función de los valores obtenidos:

void GetExtremums()
{
  double  temp;
  string  name;

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr2");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_middle_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_DTime_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_point = (datetime)temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr1");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_left_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol( ), "_", period_current, "_Price_Extr2");
  if( GlobalVariableGet(name, temp) != false )
  {
    curr_middle_price = temp;
  }

  StringConcatenate(name, PrefixString, "_", Symbol(), "_", period_current, "_Price_Extr3");
  if( GlobalVariableGet(name, temp) != false)
  {
    curr_right_price = temp;
  }

//  Update the position of channel:
  Channel.SetExtremums(curr_left_point, curr_middle_point, curr_right_point, 
                       curr_left_price, curr_middle_price, curr_right_price);
}

Usamos el método TSlideChannelObject::SetExtremums para actualizar el canal en la pantalla. Este método calcula de nuevo las coordenadas de las líneas del canal y vuelve a dibujar el canal en la pantalla.

En el siguiente vídeo, se muestra un ejemplo del dibujo de un canal en distintos períodos de tiempo:


En realidad, el orden de inicio de los indicadores no importa, pero es lógico empezar primero con el indicador ExtremumHandSet, luego añadir tres etiquetas de precios de la izquierda de color amarillo (se establece el color de las etiquetas en los parámetros del indicador, se asigna el color amarillo por defecto), y después iniciar el indicador SlideChannel que dibuja el canal mediante los extremos indicados.

Para dibujar un canal mediante los extremos del primer gráfico simultáneamente, hay que establecer un período de tiempo en el parámetro ExtremumTimeFrame, del indicador SlideChannel, igual al período del gráfico en el cual se han establecido los extremos.

Este es el resultado de separar la función de establecimiento de los puntos extremos del canal de la función de su dibujo en la pantalla del terminal.


Conclusión

Hemos tenido en cuenta un ciclo completo; desde el establecimiento de la posición del canal en la pantalla hasta su dibujo. Todo esto parece no ser tan complicado, especialmente cuando usamos las clases estándar y la programación orientada a objetos.

Pero hay una duda: ¿cómo debemos usar los canales para trabajar en el mercado? En primer lugar, son necesarios para el análisis técnico del estado actual de un instrumento financiero. Y en segundo lugar, después del análisis, hacen falta para la toma de decisiones. Los canales pueden ser de gran ayuda en eso.

Es posible desarrollar un Expert Advisor semiautomático que analiza los bordes de un canal para la apertura o el cierre de una posición. Puede funcionar rebasando los bordes o retrocediendo a partir de los mismos. Este será el tema del siguiente artículo: Los métodos de trabajo con un canal; retroceder y rebasar.