Descargar MetaTrader 5

El enfoque orientado a objeto para construir paneles multiperíodo y multidivisa

26 diciembre 2013, 09:38
Marcin Konieczny
0
582

Introducción

Este artículo describe cómo puede usarse la programación orientada a objeto para crear paneles multiperíodo y multidivisa para Meta Trader 5. El objetivo principal es construir un panel universal que pueda ser usado para mostrar en pantalla diferentes tipos de datos como precios, cambios en los precios, valores de indicador o condiciones sell/buy personalizadas sin necesidad de modificar el código del propio panel. De esta forma, solo será necesario escribir un poco de código para personalizar el panel en la forma que queramos.

La solución que voy a mostrar funciona de dos formas:

  1. El modo multiperíodo, que permite ver los contenidos de la tabla calculados en el símbolo actual pero en diferentes períodos;
  2. El modo multidivisa, que permite ver los contenidos de la tabla calculados en el símbolo actual pero en diferentes símbolos;

Las siguientes imágenes muestran el panel en estos dos modos.

El primero funciona en el modo multiperíodo y muestra los siguientes datos:

  1. Precio actual;
  2. Cambio actual del precio de la barra;
  3. Cambio actual del precio de la barra como porcentaje;
  4. Cambio actual del precio de la barra como una flecha (arriba/abajo);
  5. Valor del indicador RSI(14);
  6. Valor del indicador RSI(10);
  7. Condición personalizada: SMA(20) > precio actual.

Figura 1. Modo multiperíodo

Figura 1. Modo multiperíodo


El segundo funciona en el modo multidivisa y muestra:

  1. Precio actual;
  2. Cambio actual del precio de la barra;
  3. Cambio actual del precio de la barra como porcentaje;
  4. Cambio actual del precio de la barra como flecha;
  5. Valor del indicador RSI(10);
  6. Valor del indicador RSI(14);
  7. Condición personalizada: SMA(20) > precio actual.

Figura 2. Modo multidivisa

Figura 2. Modo multidivisa


1. Implementación

El siguiente diagrama de clase describe el diseño de implementación del panel.

Figura 3. Diagrama de clase del panel

Figura 3. Diagrama de clase del panel


Permítanme que les describa los elementos del diagrama:

  1. CTable. Clase principal del panel. Es responsable de dibujar el panel y gestionar sus componentes.
  2. SpyAgent. Es un indicador encargado de "espiar" a otros símbolos (instrumentos). Cada agente se crea y se envía a un símbolo diferente. El agente reacciona al evento OnCalculate cuando llega un nuevo tick en el gráfico del símbolo y envía el evento CHARTEVENT_CUSTOM para informar al objeto CTable que debe actualizarse. La idea que subyace tras este enfoque está basada en el artículo "La implementación de un modo multi-divisa en Meta Trader 5. Puede encontrar todos los detalles técnicos ahí.
  3. CRow. La clase básica para todos los indicadores y condiciones usadas para crear el panel. Extendiendo esta clase es posible crear todos los componentes necesarios del panel.
  4. CPriceRow. Clase simple que extiende CRow y que se usa para mostrar en pantalla el precio de compra actual.
  5. CPriceChangeRow. Clase que extiende CRow usada para mostrar en pantalla el cambio del precio de la barra actual. Puede mostrar cambios en el precio, cambios en el porcentaje o flechas.
  6. CRSIRow. Clase que extiende CRow usada para mostrar en pantalla el valor del indicador RSI actual.
  7. CPriceMARow. Clase que amplía SMA > precio actual.

Las clases CTable y CRow así como el indicador SpyAgent son las partes principales del panel. CPriceRow, CPriceChangeRow, CRSIRow y CPriceMARow son los contenidos actuales del panel. La clase CRow es llamada para ser extendida por muchas clases nuevas para obtener el resultado deseado. Estas cuatro clases derivadas que hemos visto aquí son solo simples ejemplos de lo que se puede hacer y cómo se puede hacer.


2. SpyAgent

Comenzaremos con el indicador SpyAgent. Se usa solo en el modo multidivisa y es necesario para actualizar el panel adecuadamente cuando llega un nuevo tick en otros gráficos. No voy a entrar mucho más en los detalles de este concepto. Estos se describen en el artículo "La implementación de un modo multidivisa en Meta Trader 5".

El indicador SpyAgent se ejecuta en el gráfico del símbolo especificado y envía dos eventos: el evento de inicialización y el de nuevo tick. Ambos eventos son el tipo CHARTEVENT_CUSTOM. Para gestionar estos eventos tenemos que usar el controlador OnChartEvent(...) (lo veremos más tarde en este artículo).

Veamos el código de SpyAgent:

//+------------------------------------------------------------------+
//|                                                     SpyAgent.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property indicator_chart_window
#property indicator_plots 0

input long   chart_id=0;        // chart id
input ushort custom_event_id=0; // event id
//+------------------------------------------------------------------+
//| Función de iteración del indicador                               |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
   if(prev_calculated==0)
      EventChartCustom(chart_id,0,0,0.0,_Symbol); // envía el evento de inicialización
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // envía el evento de nuevo tick

   return(rates_total);
  }
Es muy simple. Lo único que hace es recibir nuevos ticks y enviar eventos CHARTEVENT_CUSTOM.


3. CTable

CTable es la clase central del panel. Almacena información sobre la configuración del panel y gestiona sus componentes. Actualiza (redibuja) el panel cuando es necesario.

Veamos cómo se declara CTable:

//+------------------------------------------------------------------+
//| Clase CTable                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // Distancia desde el borde derecho del gráfico
   int               yDistance;    // Distancia desde la parte superior del gráfico
   int               cellHeight;   // Altura de la celda de la tabla
   int               cellWidth;    // Anchura de la celda de la tabla
   string            font;         // Nombre de la fuente
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // lista de los objetos de la fila
   bool              tfMode;       // ¿está en modo multiperíodo?

   ENUM_TIMEFRAMES   timeframes[]; // matriz de período para el modo multiperíodo
   string            symbols[];    // matriz de pares de divisas para el modo multidivisa

   //--- métodos privados
   //--- establece los parámetros por defecto de la tabla
   void              Init();
   //--- dibuja la etiqueta de texto en la celda especificada de la tabla
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- devuelve la representación textual de un período dado
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- constructor del modo multiperíodo
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- constructor del modo multidivisa
                     CTable(string &symb[]);
   //--- destructor
                    ~CTable();
   //--- redibuja la tabla
   void              Update();
   //--- métodos para establecer los parámetros de la tabla
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- Anexa el objeto CRow al final de la tabla
   void              AddRow(CRow *row);
  };

Como puede ver, todos los componentes del panel (filas) se guardan como punteros CRow, por lo que cada componente que queramos añadir al panel debe extender la clase CRow. CRow puede considerarse como un contrato entre el panel y sus componentes. CTable no contiene ningún código para el cálculo de sus celdas. Esto es responsabilidad de las clases que extienden CRow. CTable es solo una estructura para ubicar los componentes de CRow y redibujarlos cuando sea necesario.

Vamos a ver los métodos de CTable. La clase tiene dos constructores. El primero se usa para el modo multiperíodo y es muy simple. Solo tenemos que suministrar la matriz de períodos que queremos mostrar en pantalla.

//+------------------------------------------------------------------+
//| Constructor del modo multiperíodo                                |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- copia todos los períodos a su propia matriz
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;   

//--- rellena la matriz de símbolos con el símbolo del gráfico actual
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- establece los parámetros por defecto
   Init();
  }

El segundo constructor se usa para el modo multidivisa y toma una matriz de símbolos (instrumentos). Este también envía SpyAgents. Los adjunto uno por uno a los gráficos apropiados.

//+------------------------------------------------------------------+
//| Constructor del modo multidivisa                                 |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- copia todos los símbolos a su propia matriz
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;   

//--- rellena la matriz de períodos con el período actual
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- establece los parámetros por defecto
   Init();

//--- envía SpyAgents a cada símbolo solicitado
   for(int x=0; x<ArraySize(symbols); x++)
      if(symbols[x]!=Symbol()) // don't send SpyAgent to own chart
         if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE)
           {
            Print("Error al establecer SpyAgent en "+symbols[x]);
            return;
           }
  }

El método Init crea la lista de filas (como un objeto CList - CList es una lista dinámica de tipos CObject) y establece los valores por defecto para las variables internas de CTable (fuente, tamaño de fuente, color, dimensión de la celda y distancia desde la esquina superior derecha del gráfico).

//+------------------------------------------------------------------+
//| Establece los parámetros por defecto de la tabla                 |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- crea la lista para almacenar los objetos de la fila
   rowList=new CList;

//--- establece los parámetros por defecto
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

El destructor es muy simple. Borra la lista de filas y todos los objetos del gráfico (etiquetas) creadas por el panel.

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CTable::~CTable()
  {
   int total=ObjectsTotal(0);

//--- elimina todas las etiquetas de texto del gráfico (todos los nombres de objeto que comienzan por el prefijo nameBase)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- borra la lista de filas y libera memoria
   delete(rowList);
  }

El método AddRow anexa la nueva fila a la lista de filas. Tenga en cuenta que rowList es un objeto CList que se redimensiona automáticamente. Este método también llama al método Init para cada objeto CRow que se añade. Es necesario que el objeto inicialice adecuadamente sus variables internas. Por ejemplo, puede utilizar la llamada de Init para crear controladores de indicador o archivo.

//+------------------------------------------------------------------+
//| Anexa una nueva fila al final de la tabla                        |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

El método Update es un poco más complicado. Se usa para redibujar el panel.

Básicamente, consta de tres partes que son:

  • Dibujar la primera columna (los nombres de las filas)
  • Dibujar la primera fila (los nombres de los períodos o símbolos dependiendo del modo seleccionado)
  • Dibujar las celdas internas (los valores de los componentes)

Conviene tener en cuenta que pedimos a cada componente calcular su propio valor en base al símbolo y período suministrado. También permitimos que el componente decida qué fuente y color se debe usar.

//+------------------------------------------------------------------+
//| Redibuja la tabla                                                |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // número de filas
   int columns;              // número de columnas

   if(tfMode)
      columns=ArraySize(timeframes);
   else
      columns=ArraySize(symbols);

//--- dibuja la primera columna (nombres de las filas)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- nota: pedimos al objeto de la fila que devuelva su nombre
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- dibuja la primera fila (nombres de los períodos o pares de divisas)
   for(int x=0; x<columns; x++)
     {
      if(tfMode)
         DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor);
      else
         DrawLabel(columns-x-1,0,symbols[x],font,fontColor);
     }

//--- dibuja las celdas de la tabla interiores
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- en modo multiperíodo utiliza el símbolo actual y distintos períodos
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- en modo multi-divisa utiliza el símbolo actual y distintos períodos
            tf=Period();
            symbol=symbols[x];
           }

         //--- nota: pedimos al objeto de la fila que devuelva su fuente, 
         //--- color y valor calculado actual para el período y símbolo
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- fuerza el redibujado del gráfico
   ChartRedraw();
  }

El mátodo DrawLabel se usa para dibujar etiquetas en la celda especificada del panel. Primero comprueba si ya existe una etiqueta para esta celda. Si no, crea una nueva.

A continuación establece todas las propiedades de la etiqueta que sean necesarias y sus textos.

//+------------------------------------------------------------------+
//| Dibuja la etiqueta de texto en la celda especificada de la tabla |
//+------------------------------------------------------------------+ 
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- crea un único nombre para esta celda
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- crea la etiqueta
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- establece las propiedades de la etiqueta
   ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER);
   ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth);
   ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight);
   ObjectSetString(0,name,OBJPROP_FONT,font);
   ObjectSetInteger(0,name,OBJPROP_COLOR,col);
   ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize);

//--- establece el texto de la etiqueta
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

No presentaremos aquí otros métodos ya que son muy simples y menos importantes. Puede descargar todo el código al final del artículo.


4. Extendiendo CRow

CRow es una clase básica para todos los componentes que puede ser usada por el panel.

Veamos el código de la clase CRow:

//+------------------------------------------------------------------+
//| Clase CRow                                                       |
//+------------------------------------------------------------------+
//| Clase básica para crear filas de tabla personalizadas            |
//| Deben obviarse uno o más métodos de CRow                         |
//| al crear sus propias filas de la tabla                           |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- método de inicialización por defecto
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- método por defecto para obtener el valor del string a mostrar en pantalla en la celda de la tabla
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- método por defecto para obtener el color para una celda de la tabla
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- método por defecto para obtener el nombre de la fila
   virtual string GetName() { return("-"); }

   //--- método por defecto para obtener la fuente de la tabla
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

Extiende CObject porque solo CObject puede almacenarse en una estructura CList. Tiene cinco métodos que están casi vacíos. Para ser más precisos, la mayoría de ellos solo devuelven valores por defecto. Estos métodos son obviados cuando se extiende CRow. No necesitamos obviarlos todos, solo los que queremos.

Como ejemplo, vamos a crear el componente de panel más simple posible, el componente precio de compra actual. Puede usarse en el modo multidivisa para mostrar en pantalla los precios actuales de varios instrumentos.

Para conseguir esto creamos una clase CPriceRow como se muestra a continuación:

//+------------------------------------------------------------------+
//| Clase CPriceRow                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- Pasa por alto el método default GetValue(..) de CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- pasa por alto el método por defecto GetName() de CRow
   virtual string    GetName();
  };
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetValue(..) de CRow         |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- obtiene el precio actual
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetName() de CRow            |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

Los métodos que hemos elegido pasar por alto aquí son GetValue y GetName. GetName simplemente devuelve el nombre para esta fila que será mostrado en pantalla en la primera columna del panel. GetValue obtiene el último tick del símbolo especificado y devuelve el último precio de compra. Eso es todo lo que necesitamos.

Fue muy sencillo. Hagamos algo diferente. Construiremos ahora un componente que muestre el valor RSI actual.

El código es similar al anterior:

//+------------------------------------------------------------------+
//| Clase CRSIRow                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // período RSI 
   string            symbols[];        // matriz de símbolos
   ENUM_TIMEFRAMES   timeframes[];     // matriz de períodos
   int               handles[];        // matriz de controladores RSI 

   //--- encuentra el controlador de indicador para un símbolo y período dado
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CRSIRow(int period);

   //--- pasa por alto el método por defecto GetValue(..) de CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- pasa por alto el método por defecto GetName() de CRow
   virtual string    GetName();

   //--- pasa por alto el método por defecto Init(..) de CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| pasa por alto el método por defecto Init(..) de CRow             |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size); 

//--- copia los contenidos de las matrices en las propias matrices
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs); 

//--- obtiene los controladores RSI para todos los símbolos o períodos usados
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| pasa por alto el método por defecto GetValue(..) de CRow         |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- obtiene el controlador del indicador RSI
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- obtiene el valor actual de RSI 
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| pasa por alto el método por defecto GetName() de CRow            |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| encuentra el controlador del indicador                           |
//| para un período y símbolo dado                                   |
//+------------------------------------------------------------------+
int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

Tenemos algunos métodos nuevos aquí. El constructor permite suministrar el período RSI y lo guarda como variable miembro. El método Init se usa para crear el controlador del indicador RSI. Estos controladores se guardan en la matriz handle []. EL método GetValue copia el último valor del buffer de RSI y lo devuelve. Se usa el método privado GetHandle para encontrar el controlador de indicador adecuado en la matriz handle []. GetName se explica por sí mismo,

Como podemos ver, construir los componentes del panel es muy fácil. De la misma forma, podemos crear componentes para casi cualquier condición que queramos. No tiene por qué ser el valor del indicador. A continuación se muestra una condición personalizada basada en SMA. Comprueba si el precio actual se encuentra por encima de la media móvil y muestra en pantalla "Si" o "No".

//+------------------------------------------------------------------+
//| CPriceMARow class                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // período de la media móvil
   int               maShift;  // cambio de la media móvil
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA o LWMA
   string            symbols[];        // smatriz de símbolos
   ENUM_TIMEFRAMES   timeframes[];     // matriz de perídos de tiempo
   int               handles[];        // matriz de los controladores de MA

   //--- encuentra el controlador de l indicador para un símbolo y período de tiempo dado
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

public:
   //--- constructor
                     CPriceMARow(ENUM_MA_METHOD type,int period,int shift);

   //--- Pasa por alto el método por defecto GetValue(..) deCRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // Pasa por alto el método por defecto GetName() de CRow
   virtual string    GetName();

   //--- Pasa por alto el método por defecto Init(..) de CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| constructor de la clase CPriceMARow                              |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto Init(..) de CRow             |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);   

//--- copia los contenidos de las matrices en las propias matrices
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);  

//---obtiene los controladores de MA para todos los símbolos o períodos de tiempo
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetValue(..) de CRow         |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//---obtiene el controlador del indicador de MA
   int handle=GetHandle(symbol,tf);

   if(handle==INVALID_HANDLE) return("err");

//--- obtiene el último valor de MA 
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- obtiene el último precio
   if(!SymbolInfoTick(symbol,tick)) return("-");

//---comprobando la condición: precio > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetName() de CRow            |
//+------------------------------------------------------------------+
string CPriceMARow::GetName()
  {
   string name;

   switch(maType)
     {
      case MODE_SMA: name = "SMA"; break;
      case MODE_EMA: name = "EMA"; break;
      case MODE_SMMA: name = "SMMA"; break;
      case MODE_LWMA: name = "LWMA"; break;
     }

   return("Price>"+name+"("+IntegerToString(maPeriod)+")");
  }
//+------------------------------------------------------------------+
//| encuentra el controlador del indicador para un símbolo           |
//| y período de tiempo dado                                         |
//+------------------------------------------------------------------+
int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf)
  {
   for(int i=0; i<ArraySize(timeframes); i++)
      if(symbols[i]==symbol && timeframes[i]==tf)
         return(handles[i]);

   return(INVALID_HANDLE);
  }

El código es más largo porque la media móvil tiene tres parámetros: período, cambio y tipo. GetName es un poco más complicado ya que construye el nombre según el tipo y período de MA. GetValue trabaja casi de la misma forma que en el caso de CRSIRow, pero en lugar de devolver el valor del indicador devuelve "Sí" si el precio es superior a SMA o "No" si es inferior.

El método de ejemplo es un poco más complicado. Es la clase CPriceChangeRow la que muestra el cambio actual del precio en la barra. Funciona de tres formas:

  • Muestra las flechas (verde arriba o roja abajo);
  • Muestra el cambio del precio como valor (verde o rojo);
  • Muestra el cambio del precio como porcentaje (verde o rojo);

El código es como se muestra a continuación:

//+------------------------------------------------------------------+
//| CPriceChangeRow class                                            |
//+------------------------------------------------------------------+
class CPriceChangeRow : public CRow
  {
private:
   bool              percentChange;
   bool              useArrows;

public:
   //--- constructor
                     CPriceChangeRow(bool arrows,bool percent=false);

   //--- Pasa por alto el método por defecto GetName() de CRow
   virtual string    GetName();

   //--- Pasa por alto el método por defecto GetFont() de CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- Pasa por alto el método por defecto GetValue(..) de CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- Pasa por alto el método por defecto GetColor(..) de CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| Constructor de la clase CPriceChangeRow                          |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetName() de CRow            |
//+------------------------------------------------------------------+
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetFont() de CRow            |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- usamos las fuentes Wingdings para dibujar flechas (arriba/abajo)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetValue(..) de CRow         |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- obtiene abierto y cerrado de la barra actual
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- cambio del precio de la barra actual
   double change=close[0]-open[0];

   if(useArrows)
     {
      if(change > 0) return(CharToString(233)); // returns up arrow code
      if(change < 0) return(CharToString(234)); // returns down arrow code
      return(" ");
        }else{
      if(percentChange)
        {
         //--- calcula el cambio de porcentaje 
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Pasa por alto el método por defecto GetColor(..) de CRow         |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- obtiene abierto y cerrado de la barra actual
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite);
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite);

   if(close[0] > open[0]) return(clrLime);
   if(close[0] < open[0]) return(clrRed);
   return(clrWhite);
  }

El constructor tiene dos parámetros. El primero decide si muestra las flechas. Si es verdadero, el segundo parámetro es descartado. Si es falso, el segundo parámetro decide si muestra los cambios de porcentaje o solo los cambios de precio.

Para esta clase decidí pasar por alto cuatro métodos de CRow: GetName, GetValue, GetColor y GetFont. GetName es el más simple y solo devuelve el nombre. GetFont se usa porque permite la posibilidad de mostrar en pantalla flechas u otros caracteres de las fuentes Wingdings. GetColor devuelve el color lima cuando el precio sube y rojo cuando cae. Devuelve el color blanco cuando se queda en la posición o en caso de errores. GetValue obtiene los precios abierto y cerrado de la última barra, calcula la diferencia y la devuelve. En el modo flecha devuelve los códigos de caracteres Wingdings de las flechas arriba y abajo.


5. Cómo usarlo todo

Para usar el panel necesitamos crear un nuevo indicador. Vamos a llamarlo TableSample.

Los eventos que tenemos que controlar son:

También necesitamos un puntero hacia el objeto CTable que será creado dinámicamente en OnInit(). En primer lugar, tenemos que decidir qué modo vamos a usar (multiperíodo o multidivisa). El ejemplo de código a continuación muestra el modo multidivisa, pero todo lo que se necesita para el modo multiperíodo está también aquí en los comentarios. Para el modo multidivisa, necesitamos crear una matriz de símbolos y pasarla al constructor de CTable. Para el modo multiperíodo, necesitamos crear una matriz de períodos y pasarla al segundo constructor de CTable.

Después de esto tenemos que crear todos los componentes necesarios y añadirlos al panel usando el método AddRow. Opcionalmente, pueden ajustarse los parámetros del panel. Después de todo, necesitamos dibujar el panel por primera vez, por lo que llamamos a Update al final de OnInit(). OnDeinit es simple. Lo único que hace es borrar el objeto CTable, lo que origina la llamada al contructor CTable.

OnCalculate(...) son OnChartEvent(...) idénticos. Solo llaman al método Update. OnChartEvent(...) solo es necesario si el panel funciona en el modo multidivisa. En este modo, gestiona eventos descubiertos por SpyAgent. En el modo multiperíodo solo se necesita a OnCalculate(...) ya que tenemos que monitorizar solo el símbolo actual del gráfico.

//+------------------------------------------------------------------+
//|                                                  TableSample.mq5 |
//|                                                 Marcin Konieczny |
//|                                                                  |
//+------------------------------------------------------------------+
#property copyright "Marcin Konieczny"
#property version   "1.00"
#property indicator_chart_window
#property indicator_plots 0

#include <Table.mqh>
#include <PriceRow.mqh>
#include <PriceChangeRow.mqh>
#include <RSIRow.mqh>
#include <PriceMARow.mqh>

CTable *table; // puntero al objeto CTable 
//+------------------------------------------------------------------+
//| Unción de inicialización del indicador                           |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- períodos usados en la tabla (en el modo multiperíodo)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- símbolos usados en la tabla (en el modo multidivisa)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- creación del objeto CTable  
//   table = new CTable(timeframes); // modo multi-período
   table=new CTable(symbols); // modo multi-divisa

//--- añadiendo filas a la tabla
   table.AddRow(new CPriceRow());               // muestra el precio actual
   table.AddRow(new CPriceChangeRow(false));     // muestra el cambio del precio en la última barra
   table.AddRow(new CPriceChangeRow(false,true)); // muestra el cambio del porcentaje del precio en la última barra
   table.AddRow(new CPriceChangeRow(true));      // muestra el cambio del precio como flechas
   table.AddRow(new CRSIRow(14));                // muestra RSI(14)
   table.AddRow(new CRSIRow(10));                // muestra RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // muestra si SMA(20) > precio actual

//--- estableciendo los parámetros de la tabla
   table.SetFont("Arial",10,clrYellow);  // fuente, tamaño, color
   table.SetCellSize(60, 20);           // anchura, altura
   table.SetDistance(10, 10);           // distancia desde la esquina superior derecha del gráfico

   table.Update(); // fuerza el redibujado de la tabla

   return(0);
  }
//+------------------------------------------------------------------+
//| Función de deinicialización del indicador                        |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- llama al destructor de la tabla y libera memoria
   delete(table);
  }
//+------------------------------------------------------------------+
//| Función de iteración del indicador                               |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- actualiza la table: recalcula/redibuja
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Controlador de OnChartEvent                                      |
//| Controla los eventos CHARTEVENT_CUSTOM enviados                  |
//| por los indicadores SpyAgent                                     |
//| ¡solo se necesita en el modo multidivisa!                        |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // actualiza la tabla: recalcula/redibuja
  }
//+------------------------------------------------------------------+

Después de adjuntar este indicador al gráfico comienza a actualizarse y podemos ver finalmente el panel funcionando.


6. Instalación

Se necesita compilar todos los archivos. SpyAgent y TableSample son indicadores y deben copiarse en terminal_data_folder\MQL5\Indicators. El resto de archivos son de tipo include y deben situarse en terminal_data_folder\MQL5\Include. Para ejecutar el panel adjunte el indicador TableSample a cualquier gráfico. No es necesario adjuntar los de SpyAgent. Estos se ejecutarán automáticamente.


Conclusión

Este artículo proporciona una implementación orientada a objeto del panel multiperíodo y multidivisa para Meta Trader 5. Muestra cómo conseguir un diseño fácilmente extensible y permite construir paneles personalizados con poco esfuerzo.

Todo el código utilizado en este artículo puede descargarse más abajo.


Traducción del inglés realizada por MetaQuotes Software Corp.
Artículo original: https://www.mql5.com/en/articles/357

Archivos adjuntos |
Las bases de la programación orientada a objetos Las bases de la programación orientada a objetos

No necesita saber qué es poliformismo, encapsulación, etc. para usar la programación orientada a objetos (OOP)... puede simplemente utilizar estas funciones. Este artículo trata las bases de la OOP con ejemplos prácticos.

Cree sus propios paneles gráficos en MQL5 Cree sus propios paneles gráficos en MQL5

La funcionalidad del programa MQL5 viene determinada tanto por sus ricas características como por una interfaz de usuario muy desarrollada. A veces, la percepción visual es más importante que el funcionamiento rápido y estable. Esta es una guía paso a paso para que pueda crear por sí mismo paneles gráficos sobre la base de las clases de la librería estándar.

Sistemas de trading simples usando indicadores semáforo Sistemas de trading simples usando indicadores semáforo

Si examinamos en profundidad cualquier sistema de trading complejo veremos que está basado en un conjunto de simples señales de trading. Por tanto, no es necesario que los programadores con menos experiencia comiencen a escribir complejos algorítmicos inmediatamente. Este artículo proporciona un ejemplo de un sistema de trading que utiliza indicadores semáforo para realizar las transacciones.

Estrategias con órdenes Expert Advisor multiuso Estrategias con órdenes Expert Advisor multiuso

Este artículo se centra en torno a las estrategias que activamente usan órdenes pendientes, un metalenguaje que puede usarse para describir formalmente tales estrategias, así como en el uso de un Expert Advisor multiuso cuya operativa se basa en dichas descripciones.