English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Abordagem orientada a objetos para construção de painéis de múltiplos períodos de tempo e múltiplas moedas

Abordagem orientada a objetos para construção de painéis de múltiplos períodos de tempo e múltiplas moedas

MetaTrader 5Exemplos | 21 janeiro 2014, 13:43
1 954 0
Marcin Konieczny
Marcin Konieczny

Introdução

Este artigo descreve como a programação orientada a objetos pode ser usada para criar painéis de múltiplos períodos de tempo múltiplas moedas para o MetaTrader 5. O principal objetivo é construir um painel universal, que pode ser utilizado para exibição de diversos tipos diferentes de dados, tal como preços, variação de preços, valores de indicador ou condições personalizadas de compra/venda, sem a necessidade de modificar o código do painel em si. Desta forma, é necessário pouca codificação para personalizar o painel de qualquer forma que desejarmos.

A solução que será descrita funciona em dois modos:

  1. Modo de múltiplos períodos de tempo - permite visualizar o conteúdo da tabela calculado em relação ao símbolo atual, mas em diferentes períodos de tempo;
  2. Modo de múltiplas moedas - permite visualizar o conteúdo da tabela calculado em relação ao período de tempo atual, mas em diferentes símbolos.

As imagens a seguir apresentam o painel nesses dois modos.

A primeira utiliza o modo de múltiplos períodos de tempo e exibe os seguintes dados:

  1. Preço atual.
  2. Variação de preço da barra atual.
  3. Variação de preço da barra atual (%).
  4. Variação do preço da barra atual na forma de seta (para cima/baixo).
  5. Valor do indicador RSI(14).
  6. Valor do indicador RSI(10).
  7. Condição personalizada: SMA(20) > preço atual.

Figura 1. Modo de múltiplos períodos de tempo

Figura 1. Modo de múltiplos períodos de tempo


A segunda imagem opera no modo de múltiplas moedas e exibe:

  1. Preço atual.
  2. Variação de preço da barra atual.
  3. Variação de preço da barra atual (%).
  4. Variação de preço da barra atual na forma de seta.
  5. Valor do indicador RSI(10).
  6. Valor do indicador RSI(14).
  7. Condição personalizada: SMA(20) > preço atual.

Figura 2. Modo de múltiplas moedas

Figura 2. Modo de múltiplas moedas


1. Implementação

O diagrama de classe a seguir descreve o projeto de implementação do painel.

Figura 3. Diagrama de classe do painel

Figura 3. Diagrama de classe do painel


Esta é a descrição dos elementos do diagrama:

  1. CTable. É a principal classe do painel. É responsável por desenhar o painel e administrar os seus componentes.
  2. SpyAgent. É um indicador responsável por "espionar" outros símbolos (instrumentos). Cada agente é criado e enviado a um símbolo diferente. O agente reage ao evento OnCalculate quando um novo tick chega ao gráfico do símbolo e envia o evento CHARTEVENT_CUSTOM para informar ao objeto CTable que ele deve ser atualizado. A ideia completa por trás desta abordagem foi baseada no artigo "A implementação de um Modo Multi-currency (múltiplas moedas) no MetaTrader 5". Nele você pode encontrar todos os detalhes técnicos.
  3. CRow. Classe de base para todos os indicadores e condições utilizadas para criar o painel. Ao ampliar esta classe, é possível criar todos os componentes necessários do painel.
  4. CPriceRow. Classe simples que amplia a CRow, usada para exibir o preço de lance atual.
  5. CPriceChangeRow. Classe que amplia a CRow, usada para exibição da variação de preço da barra atual. Pode exibir as variações de preço, variações percentuais ou setas.
  6. CRSIRow. Classe que amplia a CRow, usada para exibição do valor atual do RSI indicator.
  7. CPriceMARow. Classe que amplia a CRow. Exibe uma condição personalizada: SMA > preço atual.

As classes CTable e CRow bem como o indicador SpyAgent são as principais partes do painel. CPriceRow, CPriceChangeRow, CRSIRow e CPriceMARow são o verdadeiro conteúdo do painel. A classe CRow é projetada para ser ampliada por muitas novas classes para que se obtenha o resultado desejado. As quatro classes derivadas que foram apresentadas são exemplos simples daquilo que pode ser feito e de que maneira pode ser feito.


2. SpyAgent

Iniciaremos com o indicador SpyAgent. Ele é utilizado apenas no modo de múltiplas moedas e é necessário para atualizar o painel adequadamente, quando um novo tick chega a outros gráficos. Não entraremos em muitos detalhes sobre este conceito. Eles estão descritos no artigo "A implementação de um Modo Multi-currency (múltiplas moedas) no MetaTrader 5".

O indicador SpyAgent é executado no gráfico do símbolo especificado e envia dois eventos: evento de inicialização e evento de novo tick. Ambos os eventos são do tipo CHARTEVENT_CUSTOM. Para lidar com esses eventos, precisamos utilizar o handler OnChartEvent(...) (ele será apresentado mais tarde neste artigo).

Vamos observar o código do 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
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
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); // sends initialization event
   else
      EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // sends new tick event

   return(rates_total);
  }
Ele é muito simples. A única tarefa que ele realiza é receber novos ticks e enviar eventos CHARTEVENT_CUSTOM.


3. CTable

CTable é a principal classe do painel. Ela armazena informações sobre as configurações do painel e administra os seus componentes. Quando necessário, ela atualiza (redesenha) o painel.

Vamos observar a declaração da CTable:

//+------------------------------------------------------------------+
//| CTable class                                                     |
//+------------------------------------------------------------------+
class CTable
  {
private:
   int               xDistance;    // distance from right border of the chart
   int               yDistance;    // distance from top of the chart
   int               cellHeight;   // table cell height
   int               cellWidth;    // table cell width
   string            font;         // font name
   int               fontSize;
   color             fontColor;

   CList            *rowList;      // list of row objects
   bool              tfMode;       // is in multi-timeframe mode?

   ENUM_TIMEFRAMES   timeframes[]; // array of timeframes for multi-timeframe mode
   string            symbols[];    // array of currency pairs for multi-currency mode

   //--- private methods
   //--- sets default parameters of the table
   void              Init();
   //--- draws text label in the specified table cell
   void              DrawLabel(int x,int y,string text,string font,color col);
   //--- returns textual representation of given timeframe
   string            PeriodToString(ENUM_TIMEFRAMES period);

public:
   //--- multi-timeframe mode constructor
                     CTable(ENUM_TIMEFRAMES &tfs[]);
   //--- multi-currency mode constructor
                     CTable(string &symb[]);
   //--- destructor
                    ~CTable();
   //--- redraws table
   void              Update();
   //--- methods for setting table parameters
   void              SetDistance(int xDist,int yDist);
   void              SetCellSize(int cellW,int cellH);
   void              SetFont(string fnt,int size,color clr);
   //--- appends CRow object to the of the table
   void              AddRow(CRow *row);
  };

Como você pode ver, todos os componentes do painel (linhas) estão armazenados como uma lista de ponteiros CRow, de forma que todos os componentes que desejarmos adicionar ao painel deve ampliar a classe CRow. CRow pode ser visto como um contrato entre o painel e os seus componentes. A CTable não contém nenhum código para cálculo de suas células. Isto é responsabilidade das classes que ampliam a CRow. CTable é somente uma estrutura para conter os componentes CRow e redesenhá-los quando necessário.

Vamos analisar os métodos da CTable. A classe possui dois construtores. O primeiro é utilizado para o modo de múltiplos períodos de tempo e é muito simples. Apenas temos que fornecer uma série de períodos de tempo que desejamos exibir.

//+------------------------------------------------------------------+
//| Multi-timeframe mode constructor                                 |
//+------------------------------------------------------------------+
CTable::CTable(ENUM_TIMEFRAMES &tfs[])
  {
//--- copy all timeframes to own array
   ArrayResize(timeframes,ArraySize(tfs),0);
   ArrayCopy(timeframes,tfs);
   tfMode=true;
   
//--- fill symbols array with current chart symbol
   ArrayResize(symbols,ArraySize(tfs),0);
   for(int i=0; i<ArraySize(tfs); i++)
      symbols[i]=Symbol();

//--- set default parameters
   Init();
  }

O segundo construtor é utilizado para o modo de múltiplas moedas e utiliza uma série de símbolos (instrumentos). Este construtor também envia SpyAgents. Ele os anexa, um a um, aos gráficos adequados.

//+------------------------------------------------------------------+
//| Multi-currency mode constructor                                  |
//+------------------------------------------------------------------+
CTable::CTable(string &symb[])
  {
//--- copy all symbols to own array
   ArrayResize(symbols,ArraySize(symb),0);
   ArrayCopy(symbols,symb);
   tfMode=false;
   
//--- fill timeframe array with current timeframe
   ArrayResize(timeframes,ArraySize(symb),0);
   ArrayInitialize(timeframes,Period());

//--- set default parameters
   Init();

//--- send SpyAgents to every requested symbol
   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 in setting of SpyAgent on "+symbols[x]);
            return;
           }
  }

O método Init cria a lista de linhas (na forma do objeto CList - CList é uma lista dinâmica de tipos CObject) e estabelece os valores padrão para as variáveis internas da CTable (fonte, tamanho da fonte, cor, dimensão da célula e distância a partir do cantor superior direito do gráfico).

//+------------------------------------------------------------------+
//| Sets default parameters of the table                             |
//+------------------------------------------------------------------+
CTable::Init()
  {
//--- create list for storing row objects
   rowList=new CList;

//--- set defaults
   xDistance = 10;
   yDistance = 10;
   cellWidth = 60;
   cellHeight= 20;
   font="Arial";
   fontSize=10;
   fontColor=clrWhite;
  }

O destrutor é muito simples. Ele apaga a lista de linhas e exclui todos os objetos do gráfico (etiquetas) criados pelo painel.

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

//--- remove all text labels from the chart (all object names starting with nameBase prefix)
   for(int i=total-1; i>=0; i--)
      if(StringFind(ObjectName(0,i),nameBase)!=-1)
         ObjectDelete(0,ObjectName(0,i));

//--- delete list of rows and free memory
   delete(rowList);
  }

O método AddRow anexa a nova linha à lista de linhas. Note que rowList é um objeto CList, o qual se redimensiona automaticamente. Este método também chama o método Init para cada objeto CRow adicionado. É necessário permitir que o objeto inicialize as suas variáveis internas adequadamente. Por exemplo, ele poderá utilizar a chamada Init para criar um indicador ou identificadores de arquivos.

//+------------------------------------------------------------------+
//| Appends new row to the end of the table                          |
//+------------------------------------------------------------------+
CTable::AddRow(CRow *row)
  {
   rowList.Add(row);
   row.Init(symbols,timeframes);
  }

O método Atualizar é um pouco mais complicado. Ele é usado para redesenhar o painel.

Basicamente, consiste em três partes, que são:

  • Desenhar a primeira coluna (o nome das linhas);
  • Desenhar a primeira linha (os nomes dos períodos de tempo ou símbolos, dependendo do modo selecionado);
  • Desenhar as células internas (os valores dos componentes).

Vale a pena notar que nós pedimos que cada componente calcule os seus próprios valores com base no símbolo e período de tempo fornecidos. Também deixamos que o componente decida qual fonte e cor devem ser usadas.

//+------------------------------------------------------------------+
//| Redraws the table                                                |
//+------------------------------------------------------------------+
CTable::Update()
  {
   CRow *row;
   string symbol;
   ENUM_TIMEFRAMES tf;

   int rows=rowList.Total(); // number of rows
   int columns;              // number of columns

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

//--- draw first column (names of rows)
   for(int y=0; y<rows; y++)
     {
      row=(CRow*)rowList.GetNodeAtIndex(y);
      //--- note: we ask row object to return its name
      DrawLabel(columns,y+1,row.GetName(),font,fontColor);
     }

//--- draws first row (names of timeframes or currency pairs)
   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);
     }

//--- draws inside table cells
   for(int y=0; y<rows; y++)
      for(int x=0; x<columns; x++)
        {
         row=(CRow*)rowList.GetNodeAtIndex(y);

         if(tfMode)
           {
            //--- in multi-timeframe mode use current symbol and different timeframes
            tf=timeframes[x];
            symbol=_Symbol;
           }
         else
           {
            //--- in multi-currency mode use current timeframe and different symbols
            tf=Period();
            symbol=symbols[x];
           }

         //--- note: we ask row object to return its font, 
         //--- color and current calculated value for given timeframe and symbol
         DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf));
        }

//--- forces chart to redraw
   ChartRedraw();
  }

O método DrawLabel é usado para desenhar etiquetas de texto na célula especificada do painel. Primeiramente, ele verifica se já existe uma etiqueta para esta célula. Se não existir, ele cria uma nova.

Em seguida, ele define todas as propriedades de etiqueta necessárias e o seu texto.

//+------------------------------------------------------------------+
//| Draws text label in the specified cell of the table              |
//+------------------------------------------------------------------+  
CTable::DrawLabel(int x,int y,string text,string font,color col)
  {
//--- create unique name for this cell
   string name=nameBase+IntegerToString(x)+":"+IntegerToString(y);

//--- create label
   if(ObjectFind(0,name)<0)
      ObjectCreate(0,name,OBJ_LABEL,0,0,0);

//--- set label properties
   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);

//--- set label text
   ObjectSetString(0,name,OBJPROP_TEXT,text);
  }

Os outros métodos não serão apresentados aqui, porque são muito simples e menos importantes. O código completo pode ser baixado ao final do artigo.


4. Ampliar CRow

CRow é a classe base para todos os componentes que podem ser utilizados pelo painel.

Vamos observar o código da classe CRow:

//+------------------------------------------------------------------+
//| CRow class                                                       |
//+------------------------------------------------------------------+
//| Base class for creating custom table rows                        |
//| one or more methods of CRow should be overriden                  |
//| when creating own table rows                                     |
//+------------------------------------------------------------------+
class CRow : public CObject
  {
public:
   //--- default initialization method
   virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { }

   //--- default method for obtaining string value to display in the table cell
   virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); }

   //--- default method for obtaining color for table cell
   virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); }
   
   //--- default method for obtaining row name
   virtual string GetName() { return("-"); }

   //--- default method for obtaining font for table cell
   virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); }
  };

Ela amplia CObject, porque apenas CObjects podem ser armazenados em uma estrutura CList. Ela possui cinco métodos, os quais estão quase vazios. Para sermos mais exatos, a maioria deles apenas retorna valores padrão. Esses métodos são projetados para serem substituídos durante a ampliação da CRow. Não precisamos substituir todos eles, apenas aqueles que quisermos.

Como exemplo, vamos criar o componente de painel mais simples possível - componente preço de lance atual. Ele pode ser utilizado no modo de múltiplas moedas para exibir preços atuais de vários instrumentos.

Para fazer isso, criados uma classe CPriceRow, que tem o seguinte aspecto:

//+------------------------------------------------------------------+
//| CPriceRow class                                                  |
//+------------------------------------------------------------------+
class CPriceRow : public CRow
  {
public:
   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

  };
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   MqlTick tick;

//--- gets current price
   if(!SymbolInfoTick(symbol,tick)) return("-");

   return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceRow::GetName()
  {
   return("Price");
  }

Os métodos que decidimos substituir aqui foram GetValue e GetName. GetName simplesmente retorna o nome para esta linha, o qual pode ser exibido na primeira coluna do painel. GetValue obtém o tick mais recente no símbolo especificado e retorna o preço de lance mais recente. Isto é tudo que precisamos.

Fazer isso foi simples. Vamos tentar algo diferente. Agora, construiremos um componente que exibe o valor RSI atual.

O código é similar ao anterior:

//+------------------------------------------------------------------+
//| CRSIRow class                                                    |
//+------------------------------------------------------------------+
class CRSIRow : public CRow
  {
private:
   int               rsiPeriod;        // RSI period
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of RSI handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

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

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CRSIRow::CRSIRow(int period)
  {
   rsiPeriod=period;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets RSI handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];

//--- gets RSI indicator handle
   int handle=GetHandle(symbol,tf);

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

//--- gets current RSI value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");

   return(DoubleToString(value[0],2));
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
string CRSIRow::GetName()
  {
   return("RSI("+IntegerToString(rsiPeriod)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
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);
  }

Aqui nós temos alguns novos métodos. O construtor permite fornecer o período RSI e o armazena como uma variável membro. O método Init é utilizado para criar identificadores do indicador RSI. Esses identificadores são armazenados na série handles[]. O método GetValue copia o último valor do buffer do RSI e o retorna. O método privado GetHandle é usado para encontrar o identificador de indicador adequado na série handles[]. GetName (obter nome) é autoexplicativo.

Como podemos ver, construir os componentes de painel é muito fácil. Da mesma maneira, podemos criar componentes para praticamente qualquer condição personalizada. Não é necessário que seja o valor do indicador. Abaixo, apresentamos uma condição personalizada baseada em SMA. Ela verifica se o preço atual está acima da média móvel e exibe "Sim" ou "Não".

//+------------------------------------------------------------------+
//| CPriceMARow class                                                |
//+------------------------------------------------------------------+
class CPriceMARow : public CRow
  {
private:
   int               maPeriod; // period of moving average
   int               maShift;  // shift of moving average
   ENUM_MA_METHOD    maType;   // SMA, EMA, SMMA or LWMA
   string            symbols[];        // symbols array
   ENUM_TIMEFRAMES   timeframes[];     // timeframes array
   int               handles[];        // array of MA handles

   //--- finds the indicator handle for a given symbol and timeframe
   int               GetHandle(string symbol,ENUM_TIMEFRAMES tf);

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

   //--- overrides default GetValue(..) method of CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   // overrides default GetName() method CRow
   virtual string    GetName();

   //--- overrides default Init(..) method from CRow
   virtual void      Init(string &symb[],ENUM_TIMEFRAMES &tfs[]);
  };
//+------------------------------------------------------------------+
//| CPriceMARow class constructor                                    |
//+------------------------------------------------------------------+
CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift)
  {
   maPeriod= period;
   maShift = shift;
   maType=type;
  }
//+------------------------------------------------------------------+
//| Overrides default Init(..) method from CRow                      |
//+------------------------------------------------------------------+
void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[])
  {
   int size=ArraySize(symb);
   
   ArrayResize(symbols,size);
   ArrayResize(timeframes,size);
   ArrayResize(handles,size);
   
//--- copies arrays contents into own arrays
   ArrayCopy(symbols,symb);
   ArrayCopy(timeframes,tfs);
  
//--- gets MA handles for all used symbols or timeframes
   for(int i=0; i<ArraySize(symbols); i++)
      handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE);
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method of CRow                    |
//+------------------------------------------------------------------+
string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double value[1];
   MqlTick tick;

//--- obtains MA indicator handle
   int handle=GetHandle(symbol,tf);

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

//--- gets the last MA value
   if(CopyBuffer(handle,0,0,1,value)<0) return("-");
//--- gets the last price
   if(!SymbolInfoTick(symbol,tick)) return("-");

//--- checking the condition: price > MA
   if(tick.bid>value[0])
      return("Yes");
   else
      return("No");
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method of 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)+")");
  }
//+------------------------------------------------------------------+
//| finds the indicator handle for a given symbol and timeframe      |
//+------------------------------------------------------------------+
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);
  }

O código é maior, porque a Média Móvel possui três parâmetros: período, mudança e tipo. GetName é um pouco mais complicado, visto que ele constrói o nome com base no tipo e período da média móvel. GetValue funciona quase da mesma forma que o CRSIRow, mas em vez de retornar o valor do indicador, ele retorna "Sim", caso o preço esteja acima da SMA ou "Não, se estiver abaixo.

O último exemplo é um pouco mais complexo. Ele é a classe CPriceChangeRow, a qual exibe a variação de preço da barra atual. Ela funciona em três modos:

  • Exibição de setas (verde, para cima, ou vermelho, para baixo);
  • Exibição de variação de preço em valor (verde ou vermelho);
  • Exibição de variação de preço em porcentagem (verde ou vermelho).

O código tem o seguinte aspecto:

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

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

   //--- overrides default GetName() method from CRow
   virtual string    GetName();

   //--- overrides default GetFont() method from CRow
   virtual string    GetFont(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetValue(..) method from CRow
   virtual string    GetValue(string symbol,ENUM_TIMEFRAMES tf);

   //--- overrides default GetColor(..) method from CRow
   virtual color     GetColor(string symbol,ENUM_TIMEFRAMES tf);

  };
//+------------------------------------------------------------------+
//| CPriceChangeRow class constructor                                |
//+------------------------------------------------------------------+
CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false)
  {
   percentChange=percent;
   useArrows=arrows;
  }
//+------------------------------------------------------------------+
//| Overrides default GetName() method from CRow                     |
//+------------------------------------------------------------------+
 string CPriceChangeRow::GetName()
  {
   return("PriceChg");
  }
//+------------------------------------------------------------------+
//| Overrides default GetFont() method from CRow                     |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf)
  {
//--- we use Wingdings font to draw arrows (up/down)
   if(useArrows)
      return("Wingdings");
   else
      return("Arial");
  }
//+------------------------------------------------------------------+
//| Overrides default GetValue(..) method from CRow                  |
//+------------------------------------------------------------------+
string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   if(CopyClose(symbol,tf,0, 1, close) < 0) return(" ");
   if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" ");

//--- current bar price change
   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)
        {
         //--- calculates percent change
         return(DoubleToString(change/open[0]*100.0,3)+"%");
           }else{
         return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
        }
     }
  }
//+------------------------------------------------------------------+
//| Overrides default GetColor(..) method from CRow                  |
//+------------------------------------------------------------------+
color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf)
  {
   double close[1];
   double open[1];

//--- gets open and close of current bar
   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);
  }

O construtor possui dois parâmetros. O primeiro decide se as setas serão ou não exibidas. Se for verdadeiro, o segundo parâmetro é descartado. Se falso, o segundo parâmetro decide se serão exibidas variações percentuais ou somente variações de preço.

Para esta classe, decidimos substituir quatro métodos da CRow: GetName, GetValue, GetColor e GetFont. GetName é o mais simples e apenas retorna o nome. GetFont é usado porque permite a exibição de setas ou outros caracteres da fonte Wingdings. GetColor retorna a cor verde, quando o preço sobe, e vermelha, quando desce. A cor branca é retornada quando o preço permanece inalterado ou em caso de erros. GetValue obtém os preços de abertura e fechamento da última barra, calcula a diferença e a retorna. No modo de seta, ele retorna códigos de seta para cima e para baixo da fonte Wingdings.


5. Como utilizar tudo isso

Para utilizar o painel, precisamos criar um novo indicador. Vamos chamá-lo de TableSample.

Os eventos que precisamos manipular são:

Também precisamos de um ponteiro para o objeto CTable, que será criado dinamicamente em OnInit(). Primeiramente, devemos decidir qual modo usar (múltiplos períodos de tempo ou múltiplas moedas). A amostra de código abaixo exibe o modo de múltiplas moedas, mas tudo o que for necessário para o modo de múltiplos períodos de tempo também está nos comentários. Para o modo de múltiplas moedas, precisamos criar uma série de símbolos e passá-la ao construtor CTable. Para o modo de múltiplos períodos de tempo, precisamos criar uma série períodos de tempo e passá-la ao segundo construtor CTable.

Após fazer isso, temos que criar todos os componentes necessários e adicioná-los ao painel utilizando o método AddRow. Opcionalmente, os parâmetros do painel podem ser ajustados. Após tudo isso, precisamos desenhar o painel pela primeira vez. Assim, chamamos Atualizar (Update) ao fim do OnInit(). OnDeinit é simples. A única coisa que ele faz é apagar o objeto CTable, o que faz com que o destrutor CTable seja chamado.

OnCalculate(...) e OnChartEvent(...) são idênticos. Eles apenas chamam o método Atualizar. OnChartEvent(...) é apenas necessário se o painel trabalhar no modo de múltiplas moedas. Neste modo, ele manipula os eventos levantados por SpyAgents. No modo de múltiplos períodos de tempo, somente o OnCalculate(...) é necessário, porque apenas temos que monitorar o símbolo do gráfico atual.

//+------------------------------------------------------------------+
//|                                                  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; // pointer to CTable object
//+------------------------------------------------------------------+
//| Indicator initialization function                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- timeframes used in table (in multi-timeframe mode)
   ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1};

//--- symbols used in table (in multi-currency mode)
   string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" };
//-- CTable object creation 
//   table = new CTable(timeframes); // multi-timeframe mode
   table=new CTable(symbols); // multi-currency mode

//--- adding rows to the table
   table.AddRow(new CPriceRow());               // shows current price
   table.AddRow(new CPriceChangeRow(false));     // shows change of price in the last bar
   table.AddRow(new CPriceChangeRow(false,true)); // shows percent change of price in the last bar
   table.AddRow(new CPriceChangeRow(true));      // shows change of price as arrows
   table.AddRow(new CRSIRow(14));                // shows RSI(14)
   table.AddRow(new CRSIRow(10));                // shows RSI(10)
   table.AddRow(new CPriceMARow(MODE_SMA,20,0));  // shows if SMA(20) > current price

//--- setting table parameters
   table.SetFont("Arial",10,clrYellow);  // font, size, color
   table.SetCellSize(60, 20);           // width, height
   table.SetDistance(10, 10);           // distance from upper right chart corner

   table.Update(); // forces table to redraw

   return(0);
  }
//+------------------------------------------------------------------+
//| Indicator deinitialization function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- calls table destructor and frees memory
   delete(table);
  }
//+------------------------------------------------------------------+
//| Indicator iteration function                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const int begin,
                const double &price[])
  {
//--- update table: recalculate/repaint
   table.Update();
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| OnChartEvent handler                                             |
//| Handles CHARTEVENT_CUSTOM events sent by SpyAgent indicators     |
//| nedeed only in multi-currency mode!                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   table.Update(); // update table: recalculate/repaint
  }
//+------------------------------------------------------------------+

Após anexar este indicador ao gráfico, ele começa a atualizar e finalmente podemos ver o painel em funcionamento.


6. Instalação

Todos os arquivos precisam ser compilados. SpyAgent e TableSample são indicadores e devem ser copiados para terminal_data_folder\MQL5\Indicators. Os arquivos restantes são arquivos incluídos e devem ser colocados dentro de terminal_data_folder\MQL5\Include. Para executar o painel, anexe o indicador TableSample a qualquer gráfico. Não há necessidade de anexar SpyAgents. Eles serão iniciados automaticamente.


Conclusão

O artigo apresentou uma implementação orientada a objetos dos painéis de múltiplos períodos de tempo e de múltiplas moedas do MetaTrader 5. Ele mostra como chegar a um projeto, o qual pode ser ampliado facilmente e permite a construção de painéis personalizados com pouco esforço.

Todos os códigos apresentados neste artigo podem ser baixados a seguir.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/357

Sistema de negociação simples com o uso de indicadores semáforo Sistema de negociação simples com o uso de indicadores semáforo
Se examinarmos por completo qualquer sistema de negócio complexo, veremos que é baseado em um conjunto simples de sinais de negócio. Consequentemente, não há necessidade para que novos desenvolvedores comecem imediatamente a escrever algoritmos complexos. Este artigo fornece um exemplo de um sistema de negócio que utiliza indicadores semáforo para realizar negócios.
Os Fundamentos da programação orientada a objetos Os Fundamentos da programação orientada a objetos
Você não precisa saber o que são polimorfismo, encapsulação, etc. tudo sobre o uso da programação orientada a objeto (OOP)... você pode simplesmente usar estes recursos. Este artigo cobre o básico de OOP com exemplos práticos.
Crie o seu próprio robô de negociação em 6 passos! Crie o seu próprio robô de negociação em 6 passos!
Se você não sabe como as classes de negócio são construídas, e se assusta com as palavras "Programação orientada a objeto", então, este artigo é para você. Na realidade, você não precisa saber os detalhes para escrever seu próprio módulo de sinais de negociação. Apenas siga algumas regras simples. Todo o resto será feito pelo Assistente MQL5, e você terá um robô de negócio pronto para uso!
Crie seus próprios painéis gráficos no MQL5 Crie seus próprios painéis gráficos no MQL5
A usabilidade do programa MQL5 é determinada tanto por sua rica funcionalidade como pela interface de usuário gráfica elaborada. A percepção visual, algumas vezes, é mais importante do que uma operação rápida e estável. Aqui está um guia passo-a-passo para você mesmo criar painéis de exibição com base nas classes da Biblioteca padrão.