Download MetaTrader 5

Escrevendo um livro de ofertas de scalping com base na biblioteca gráfica CGraphic

23 outubro 2017, 11:05
Vasiliy Sokolov
0
865

Sumário

Introdução

Este artigo é uma continuação da descrição da biblioteca para trabalhar com o livro de ofertas, publicada há dois anos. Desde então, em MQL5, surgiu o acesso ao histórico de ticks. Além disso, graças à MetaQuotes, foi desenvolvida a biblioteca CGraphic, para visualização de dados personalizados como gráfico estatístico complexo. As tarefas realizadas pela CGraphic são semelhantes às da função plot em linguagem de programação  R. O trabalho com esta biblioteca é descrito em detalhes num artigo separado

O aparecimento desses recursos permitiu modernizar bastante o livro de ofertas previamente proposto. Na nova versão, além da tabela de ordens, agora são exibidos o gráfico de ticks e as transações Last:

Fig. 1. Livro de ofertas com gráfico de ticks.

Lembremos que a biblioteca anteriormente proposta consistia em dois grandes módulos: classe CMarketBook para trabalhar com o livro de ofertas e o painel gráfico exibido nele. Desde então, o código passou por muitas melhorias e mudanças. Houve correções de bugs, enquanto a parte gráfica do livro de ofertas adquiriu sua própria biblioteca gráfica CPanel, simples e leve.

Mas, voltemos à CGraphic e sua capacidade de desenhar gráficos complexos e gráficos de linha numa janela separada. Parece que esses recursos específicos podem ser úteis apenas para solucionar problemas estatísticos. Mas não é assim! Neste artigo, tentarei mostrar como você pode aproveitar as funcionalidades da CGraphic em projetos, longe das estatísticas, por exemplo, ao criar um livro de ofertas de scalping.

Alterações feitas após a liberação da versão anterior

Depois da publicação do artigo "MQL5 cookbook: implementando seu próprio livro de ofertas", muitas vezes usei a CMarketBook na prática e no processo encontrei uma série de erros no código. Aos poucos fui modificando a interface, e como resultado apareceram as seguintes alterações:

  1. Inicialmente, todos os gráficos no livro de ofertas eram muito minimalistas. As células na tabela de preços eram exibidas com apenas algumas classes simples. Depois de algum tempo, essas classes adquiriram recursos adicionais, cuja simplicidade e leveza facilitavam a concepção de outros tipos de painéis. O resultado foi um conjunto de classes, uma biblioteca CPanel, que se separou para ser usada num projeto independente. Ela está localizada na pasta Include.
  2. Aprimorada a aparência do livro de ofertas. Por exemplo, em vez do triângulo pequeno, apareceria um botão quadrado grande que abria e fechava o livro. Corrigido o erro de sobreposição de itens, uma vez que, ao reabrir a tabela, os itens do livro de ofertas eram exibidos acima dos já exibidos nele.
  3. Adicionadas as configurações que posicionam o botão de abertura/encerramento do livro de ofertas ao longo dos eixos X e Y do gráfico. Muitas vezes, como o nome do instrumento estava fora do padrão e havia um painel de negociação adicional, o botão para abrir/fechar o livro encerrava outros elementos ativos do gráfico. Agora, essa sobreposição pode ser evitada, uma vez que podemos definir manualmente a localização dos botões.
  4. A classe CMarketBook também mudou muito. Foram corrigidos os erros: saída da matriz (array out of range), o livro vazio ou parcialmente preenchido, divisão por zero ao mudar o símbolo. A classe CMarketBook se tornou um módulo independente e está localizada no diretório MQL5\Include\Trade;
  5. Feita um série de pequenas mudanças para melhorar a estabilidade geral do indicador.

É nesta versão melhorada e complementada que trabalharemos para transformá-la gradualmente num livro de ofertas de scalping.

Visão geral da biblioteca gráfica CPanel

A criação de interfaces do usuário em MQL5 já foi assunto de muitos artigos. Entre eles se destaca a série de Anatoly Kajárskovo "Interfaces gráficas do usuário", após a qual é difícil dizer algo novo sobre esse tópico. Portanto, não entraremos em detalhes na construção da interface gráfica do usuário. Mas, como já mencionado, a parte gráfica do livro de ofertas se transformou na biblioteca CPanel. É necessário descrever sua arquitetura base, porque um item gráfico especial será criado com base nela, nomeadamente, o gráfico de ticks. Nós o juntaremos à tabela de preços, fazendo uma painel com vários itens.

Bem, examinaremos em detalhes a CPanel, para entender os princípios que utilizaremos mais tarde. Em MQL5, os elementos gráficos são apresentados por várias primitivas gráficas. Isto é:

  • etiqueta;
  • botão;
  • caixa de edição;
  • rótulo retangular;
  • etiqueta bitmap.

Todas elas têm uma série de propriedades semelhantes. Por exemplo, o rótulo retangular, o botão e a caixa de edição podem ser configurados de modo que não difiram uns dos outros externamente. Assim, qualquer primitiva gráfica pode estar baseada em qualquer item. Por exemplo, em vez de um botão, pode-se exibir uma caixa de entrada, enquanto que, em lugar de um rótulo retangular,pode-se mostrar um botão. Essa substituição passará desapercebida, e o usuário, ao clicar na caixa de edição, pensará que está clicando num botão.

Claro, essa substituição pode parecer estranha, complicando a compreensão global dos princípios da construção da interface do usuário. Porém, é preciso entender que, além de as características comuns, cada elemento de base tem sua própria característica única. Por exemplo, não se pode fazer com que a caixa de edição seja transparente, o que não acontece com o rótulo retangular. Isso permite criar itens com um aspecto único sobre as mesmas classes.

Explicaremos isto com um exemplo. Suponhamos que queremos criar um painel gráfico com uma etiqueta simples:

Fig. 2. Exemplo de forma com título sem borda.

Mas se quisermos marcar o contorno do texto usando bordas, vamos ter problemas, porque a etiqueta não possui propriedades de "borda". A solução é simples, quer dizer, em lugar de usar uma etiqueta, empregaremos um botão! Aqui está essa forma com esse botão:

Fig. 3. Exemplo de forma com título e borda.

Podem acontecer muitas coisas desse genro durante a criação da interface gráfica do usuário. É impossível prever o que o usuário precisará em realidade. Portanto, o mais correto é dar ao usuário a oportunidade de determinar em qual primitiva deve ser baseado item gráfico.

É assim que a biblioteca de itens gráficos CPanel está criada. Na sua essência, a CPanel é um conjunto de classes, classes essas que representam um item de uma GUI de alto nível. Para inicializar esse tipo de item, é necessário especificar o tipo primitiva gráfica em que será baseado. Cada uma dessas classes tem um pai comum, isto é, a classe cNode, ela desempenha apenas uma função, a de armazenar o tipo da primitiva base. Seu único construtor protegido solicita digitar este tipo no momento da criação do item. 

Existem poucos elementos gráficos de alto nível. "Singularidade" dependente do conjunto de propriedades que precisam ser fornecidas ao elemento básico universal para torná-lo único. Assim, um elemento universal na CPanel é a classe CElChart. Como todas as outras classes, ele é herdado da cNode e contém métodos para configurar as seguintes propriedades:

  • comprimento e altura do elemento;
  • coordenadas X e Y do item em relação ao gráfico;
  • largura e altura do item;
  • cor de fundo e o borda do item (se tais propriedades são suportadas);
  • tipo de borda do item (se um tal propriedade é suportada);
  • texto dentro do item, seu tamanho e fonte, justificação (se tais propriedades são suportadas).

CElChart fornece métodos para definir várias propriedades, mas não garante que essas propriedades realmente sejam exibidas. O fato de CElChart suportar certa propriedade é determinado pelo elemento base. Como no caso da CNode, CElChart pede indicar o tipo de primitiva gráfica em que será baseado. Assim, usando apenas uma CElChart podem ser criados como uma forma regular, por exemplo, um botão ou uma caixa de texto. Na prática, isso é muito conveniente.

Exemplo: desenhemos um painel como na Figura 3. Para fazer isso, precisaremos de dois itens, isto é, uma tela de fundo com borda e um quadro de texto. Ambos são cópias da mesma classe CElChart. Eles usam duas primitivas gráficas diferentes:  OBJ_RECTANGLE_LABEL e BJ_BUTTON. Como resultado, surge o seguinte código:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);
CElChart Label(OBJ_BUTTON);
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   Fon.Show();
   
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.Show();
   Label.YCoord(240);
   Label.XCoord(250);
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Uma vez que o item é criado, resta determinar suas propriedades na função OnInit. Agora, pode-se exibir os itens no gráfico e chamar seus métodos Show.

Graças à combinação da primitiva base com a classe CElChart, podem-se criar interfaces gráficas do usuário poderosas, flexíveis, e mais importante, simples. Além disso, é fornecida a exibição gráfica do livro de ofertas, livro esse que apresenta uma tabela que consiste em muitos elementos CBookCell baseados, por sua vez, na CElChart.

O motor gráfico da CPanel suporta nidificação. Isto significa que dentro de um único elemento podem ser colocados mais outros. Graças à versatilidade, é alcançada a universalidade do controle. Por exemplo, o comando fornecido à forma global pode ser enviado a todos os seus sub-elementos. Alteramos o exemplo acima:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);
CElChart *Label;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   
   Label = new CElChart(OBJ_BUTTON);
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.YCoord(240);
   Label.XCoord(250);
   //Label.Show();
   Fon.AddElement(Label);
   
   Fon.Show();
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }

Agora no processo de execução do programa, dinamicamente é criado CLabel, ele é o ponteiro para o item CElCahrt. Depois de criar e colocar as correspondentes propriedades, ele é adicionado no formulário Form. Agora, é necessário exibi-lo por meio de um comando separado Show. Em vez disso, basta executar o comando de Show somente para o elemento Fon, que é a forma principal de nosso aplicativo. As especificidades deste comando é que ele é válido para todos os sub-elementos aninhados, incluindo para o Label. 

CPanel não só define as propriedades do elemento, mas também suporta o desenvolvimento de eventos do modelo. Um evento na CPanel pode ser qualquer coisa, e não apenas um evento recebido a partir do gráfico. A classe CEvent e o método Event são responsáveis por isso. A classe CEvent é abstrata. Muitas classes concretas são baseadas nela, por exemplo, CEventChartObjClick.

Suponhamos que nosso formulário personalizado contém vários sub-elementos. O usuário pode criar um evento, por exemplo, clicar em qualquer um desses elementos. Como saber qual instância da classe deve manipular o evento? Para fazer isso, usamos o evento CEventChartObjClick: criamos a instância da classe e enviamos para nosso formulário central Form:

CElChart Fon(OBJ_RECTANGLE_LABEL);
...
...
void OnChartEvent(const int id,         // ID do evento   
                  const long& lparam,   // parâmetro do tipo de evento long 
                  const double& dparam, // parâmetro do tipo de evento double 
                  const string& sparam  // parâmetro do tipo de evento string 
  )
{ 
   CEvent* event = NULL;
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
         event = new CEventChartObjClick(sparam);
         break;
   }
   if(event != NULL)
   {
      Fon.Event(event);
      delete event;
   }
}

Com este método, enviamos um evento difundido CEventChartObjClick, que terá todos os elementos dentro de uma instância Fon. O fato de este evento ser processado depende da lógica interna do próprio formulário. 

Suponhamos que nosso rótulo Meta Quotes Language processa esse clique e muda seu texto para "Enjoy". Para fazer isso, criamos a classe CEnjoy e adicionamos a ela a lógica necessária: redefinimos o método OnClick, manipulador de eventos com o mesmo nome:

//+------------------------------------------------------------------+
//|                                       MarketBookArticlePanel.mq5 |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Panel\ElChart.mqh>
#include <Panel\Node.mqh>

CElChart Fon(OBJ_RECTANGLE_LABEL);

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
class CEnjoy : public CElChart
{
protected:
   virtual void OnClick(void);
public:
                CEnjoy(void);
   
};

CEnjoy::CEnjoy(void) : CElChart(OBJ_BUTTON)
{
}
void CEnjoy::OnClick(void)
{
   if(Text() != "Enjoy!")
      Text("Enjoy!");
   else
      Text("Meta Quotes Language");
}
CEnjoy Label;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   Fon.Height(100);
   Fon.Width(250);
   Fon.YCoord(200);
   Fon.XCoord(200);
   Fon.BackgroundColor(clrWhite);
   Fon.BorderType(BORDER_FLAT);
   Fon.BorderColor(clrBlack);
   
   Label.Text("Meta Quotes Language");
   Label.BorderType(BORDER_FLAT);
   Label.BorderColor(clrBlack);
   Label.Width(150);
   Label.YCoord(240);
   Label.XCoord(250);
   //Label.Show();
   Fon.AddElement(&Label);
   
   Fon.Show();
   ChartRedraw();
//---
   return(INIT_SUCCEEDED);
}
void OnChartEvent(const int id,         // ID do evento   
                  const long& lparam,   // parâmetro do tipo de evento long 
                  const double& dparam, // parâmetro do tipo de evento double 
                  const string& sparam  // parâmetro do tipo de evento string 
  )
{ 
   CEvent* event = NULL;
   switch(id)
   {
      case CHARTEVENT_OBJECT_CLICK:
         event = new CEventChartObjClick(sparam);
         break;
   }
   if(event != NULL)
   {
      Fon.Event(event);
      delete event;
   }
   ChartRedraw();
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Pode parecer estranho que enviemos o evento CEventObjClick para o formulário Form através do método Event, e processamos no método OnClick. De fato, muitos dos eventos padrão (por exemplo, cliques do mouse) têm seus próprios eventos-eventos especiais. Quando são redefinidos, os eventos correspondentes chegam a eles. Quando não se faz isso, todos os eventos são processados no próximo nível superior, no método Event. Este é também um método virtual, que pode ser redefinindo da mesma forma como o método OnClick. A este nível, o trabalho com o evento ocorre através da análise da instância que entra CEvent. 

Por enquanto, deixamos esses detalhes e especificamos as propriedades básicas da CPanel.

  • Todas as classes СPanel, que implementam elementos da interface gráfica, podem ser baseadas em qualquer primitiva escolhida. Ela é selecionada e indicada ao criar a instância da classe.
  • Cada elemento arbitrário da CPanel pode conter um conjunto ilimitado de outros elementos da CPanel. Assim é implementada a nidificação, e, portanto, a universalidade do controle. Todos os eventos estão espalhados por toda a árvore do nidificação, e, assim, cada elemento tem acesso a cada evento.
  • O modelo CPanel tem dois níveis. Na base no modelo de baixo nível estão o método Event e as classes de tipo CEvent. Desse modo, é possível processar absolutamente qualquer evento, incluindo os que não são suportados em MQL. Além disso, os eventos, enviados através da CEvent, são sempre difundidos. Num nível superior, os eventos padrão são convertidos em chamadas para os métodos apropriados. Por exemplo, o evento CEventChartObjClick é transformada numa chamada OnClick, enquanto a chamada do método Show gera a chamada recursiva de métodos OnShow de todos os elementos filho. Neste nível, o evento pode ser chamado diretamente. Ao chamar o método Show(), ele exibe um painel, enquanto a chamada do método Refresh atualiza a exibição desse painel.

Visão geral saiu muito fluente e concisa, mas a apresentação resultante deve ser suficiente para entender os nossos próximos passos na criação do livro de ofertas de scalping.

Sincronização do fluxo de ticks e tabelas de ordens

A tabela de ordens é uma estrutura dinâmica, que muda os valores nos mercados líquidos dezenas de vezes por segundo. Para acessar a fatia atual da tabela de ordens, deve ser processado o evento especial BookEvent no mesmo manipulador de eventos, ou seja, na função OnBookEvent. Quando ocorre uma alteração na tabela de ordens, o terminal chama o evento OnBookEvent, indicando o símbolo em que ocorreu tal mudança. Lembremos que na versão anterior deste artigo foi desenvolvida a classe CMarketBook, que oferecia fácil acesso à fatia de preços do livro de ofertas. Para obter a fatia atual do livro, nesta classe, bastava chamar o método Regresh() na função OnBookEvent. Tinha mais ou menos a seguinte forma:

//+------------------------------------------------------------------+
//|                                                   MarketBook.mq5 |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window
#property indicator_buffers 0
#property indicator_plots 0
#include <MarketBook.mqh>

CMarketBook MarketBook.mqh
double fake_buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
   MarketBook.SetMarketBookSymbol(Symbol());
//--- indicator buffers mapping
   SetIndexBuffer(0,fake_buffer,INDICATOR_CALCULATIONS);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   ChartRedraw();
}

Na nova versão, nosso livro de ofertas, exceto para a tabela de transações, deve exibir o gráfico de ticks em tempo real. Portanto, é necessário fornecer funções adicionais para trabalhar com ticks recém-chegados. Analisar os ticks em MQL5 é possível através de três mecanismos fundamentais. Isto é:

  • Obtenção do último tick conhecido através da função SymbolInfoTick;
  • Processamento do evento de chegada de novo tick na função OnTick para experts e na função OnCalculate, para indicadores;
  • Obtenção do histórico de ticks usando as funções CopyTicks e CopyTicksRange.

Os dois primeiros métodos podem ser combinados entre si. Por exemplo, no evento OnTick ou OnCalculate, pode-se chamar a função SymbolInfoTick e obter acesso aos parâmetros do último tick. No entanto, estes dois métodos não satisfazem nossas necessidades, devido à natureza da ocorrência do fluxo de ticks.

Para entender como são formados os ticks, acessamos o artigo "Princípios da precificação da bolsa tomando como exemplo o mercado de derivativos da bolsa de moscou (MOEX)" e examinamos o livro de ofertas condicional para o ouro: 

Preço, US$ por onça troy de ouro Número de onças (contratos)
1280.8 17
1280.3 3
1280.1 5
1280.0 1
1279.8 2
1279.7 15
1279.3 3
1278.8 13

Figura 4. Exemplo de livro de ofertas.

Imaginemos que, durante a atualização do livro de ofertas, nós pedimos o histórico de ticks e, com ajuda de um algoritmo especial, definimos quantos ticks chegaram desde a última atualização. Teoricamente, cada tick deve coincidir pelo menos com uma mudança no livro de ofertas, o que significa que, a cada variação no livro de ofertas, pode chegar um único tick como máximo. No entanto, na prática, esse padrão não é observado, e, para uma sincronização correta, precisa-se de trabalhar com o histórico de ticks.

Suponhamos que apareceu um comprador prestes a adquirir 9 contratos de ouro. Ao comprar, ele vai fazer pelo menos três transações. Se, no nível de 1280.1 ou 1280.3, houver poucos vendedores, o número de transações será maior. Ao concluir uma ação (compra), ele imediatamente cria algumas transações, que ocorrem ao mesmo tempo. Assim os, ticks no terminal MetaTrader 5 chegam em "maços". Portanto, se, na OnCalculate, for usada a função SymbolInfoTick, ela retornará apenas o último tick desta série, enquanto os anteriores serão perdidos.

Por isso precisamos de um outro mecanismo mais confiável para obter ticks por meio da função CopyTicks. Ela, como a CopyTicksRange, ao contrário da SymbolInfoTick, permite obter uma série de ticks. Graças a isso, o histórico de ticks será exibido corretamente, e nada será ignorado.

Mas a função CopyTiks não permite consultar os últimos N ticks. Em vez disso, ela fornece todos os ticks que chegaram desde o tempo especificado. Isso complica a tarefa. Precisamos executar uma consulta para obter uma matriz de ticks e compará-la com a matriz de ticks obtida na anterior atualização. Ao fazer isso, devemos deixar esclarecido quais não faziam parte da "última entrega", ou seja, os ticks novos. Mas comparar os ticks entre si diretamente é impossível, simplesmente porque podem não haver diferenças visíveis entre eles. Por exemplo, vejamos a tabela de transações abaixo:

Fig. 5. Tabela de transações com exemplo de transações idênticas.

Nós imediatamente vemos dois grupos de ticks idênticos. Eles são marcados com quadros vermelhos, eles têm o mesmo tempo, o volume. direção e preço. Assim, temos a certeza que não se deve comparar os ticks individuais entre si.

Mas se pode comparar o grupo de ticks. Se dois grupos de ticks são iguais entre si, pode-se concluir que estes e os subsequentes ticks foram analisados ​​durante a última atualização de preços.

Sincronizamos a classe CMarketBook com o fluxo de ticks: adicionamos a ela a matriz MqlTiks, que contém novos ticks, que chegaram desde a anterior atualização. Os ticks mais recentes serão calculados pelo método interno CompareTiks:

//+------------------------------------------------------------------+
//| Compare two tiks collections and find new tiks                   |
//+------------------------------------------------------------------+
void CMarketBook::CompareTiks(void)
{
   MqlTick n_tiks[];
   ulong t_begin = (TimeCurrent()-(1*20))*1000; // from 20 sec ago
   int total = CopyTicks(m_symbol, n_tiks, COPY_TICKS_ALL, t_begin, 1000);
   if(total<1)
   {
      printf("Não foi possível obter os ticks");
      return;
   }
   if(ArraySize(m_ticks) == 0)
   {
      ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY);
      return;
   }
   int k = ArraySize(m_ticks)-1;
   int n_t = 0;
   int limit_comp = 20;
   int comp_sucess = 0;
   //Examinamos as novas transações obtidas começando com a mais recente
   for(int i = ArraySize(n_tiks)-1; i >= 0 && k >= 0; i--)
   {
      if(!CompareTiks(n_tiks[i], m_ticks[k]))
      {
         n_t = ArraySize(n_tiks) - i;
         k = ArraySize(m_ticks)-1;
         comp_sucess = 0;
      }
      else
      {
         comp_sucess += 1;
         if(comp_sucess >= limit_comp)
            break;
         k--;
      }
   }
   //Lembramos o tick obtido
   ArrayResize(m_ticks, total);
   ArrayCopy(m_ticks, n_tiks, 0, 0, WHOLE_ARRAY);
   //Calculamos o índice do início dos ticks novos e os compiamos para o buffer para acesso
   ArrayResize(LastTicks, n_t);
   if(n_t > 0)
   {
      int index = ArraySize(n_tiks)-n_t;
      ArrayCopy(LastTicks, m_ticks, 0, index, n_t);
   }
}

O algoritmo apresentado não é simples. CompareTicks consulta todos os ticks nos últimos 20 segundos e os compara com a matriz de ticks previamente armazenada, começando com o final. Se 20 ticks consecutivos da matriz atual são iguais a 20 ticks da matriz anterior, todos os ticks após esses 20 são novos.

Explicaremos este algoritmo usando um esquema simples. Imaginemos que chamamos a função CopyTiks duas vezes, após um curto período. Mas, em vez de ticks, ela retorna uma matriz de zeros e uns. Após obter estas duas matrizes, esclarecemos quantos elementos únicos, recentes, da segunda matriz não coincidem com os elementos da primeira matriz. Este será o número de novos ticks que entraram desde a última atualização. Aqui está num esquema:

Fig. 6 Esquema de sincronização das séries recorrentes.

Ao comparar, vemos que os números de 6 a 14 da primeira matriz são iguais aos números de 1 a 8 da segunda matriz. Portanto, na Array2 há cinco novos valores, os elementos de 9 a 14. O algoritmo funciona em várias combinações: matrizes podem ser de comprimentos diferentes, não ter elementos comuns ou ser completamente idênticas entre si. Em todos esses casos, o número de valores novos é definido corretamente.

Uma vez que o número de ticks está definido, devemos copiá-los para a matriz LastTiks. Esta matriz é definida como um campo público dentro da classe CMarketBook.

Obtemos uma nova versão da classe CMarketBook, que, além da tabela de ordens, agora contém uma matriz de ticks, que entraram entre as atualizações anterior e atual. Por exemplo, para saber sobre ticks novos, pode-se escrever um código como este:

//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   string new_tiks = (string)ArraySize(MarketBook.LastTicks);
   printf("Entraram " + new_tiks + "  novos ticks");
}

O posicionamento dos ticks na classe do livro de ofertas permite sincronizar corretamente as ordens atuais com o fluxo de ticks. A cada atualização do livro de ofertas, temos uma lista de ticks anterior a esta atualização. Assim, o fluxo de ticks e o livro de ofertas são totalmente sincronizados entre si. Mais tarde, usaremos esta propriedade importante, mas, por agora, avancemos para a biblioteca gráfica CGraphic.

Fundamentos da CGraphic

A biblioteca CGraphic possui linhas, histogramas, pontos, formas geométricas complexas. Para nossos propósitos, só precisamos de uma pequena fração de sua capacidade. Precisamos de duas linas para exibição de níveis Ask e Bid e pontos especiais para exibição de transações Last. Na sua essência, a CGraphic é um contêiner de objetos CCurve. Cada objeto desse genro é um tipo de curva que consiste em pontos com coordenadas X e Y. Dependendo do tipo de exibição, eles podem se juntar através de linhas, podem ser os vértices das colunas do histograma ou ser exibidos como pontos. 

Devido a algumas peculiaridades de visualização de transações Last, trabalharemos com gráficos bidimensionais. Tentemos criar um gráfico bidimensional simples na forma de uma linha:

//+------------------------------------------------------------------+
//|                                                   TestCanvas.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Graphics\Graphic.mqh>

CGraphic Graph;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   double x[] = {1,2,3,4,5,6,7,8,9,10};
   double y[] = {1,2,3,2,4,3,5,6,4,3};
   CCurve* cur = Graph.CurveAdd(x, y, CURVE_LINES, "Line");   
   Graph.CurvePlotAll();
   Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); 
   Graph.Redraw(true);
   Graph.Update();
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

Quando ele é executado como expert, esta imagem aparece no gráfico:

Fig. 7. Exemplo de gráfico de linhas bidimensional criado usando a CGraphic

Agora tentamos mudar a visualização de nossa curva bidimensional CCurve para pontos:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   double x[] = {1,2,3,4,5,6,7,8,9,10};
   double y[] = {1,2,3,2,4,3,5,6,4,3};
   CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points");   
   cur.PointsType(POINT_CIRCLE);
   Graph.CurvePlotAll();
   Graph.Create(ChartID(), "Ticks", 0, (int)50, (int)60, 510, 300); 
   Graph.Redraw(true);
   Graph.Update();
   return(INIT_SUCCEEDED);
}

O mesmo gráfico, mas na forma de pontos:

Fig. 8. Exemplo de gráfico de pontos bidimensional criado com ajuda da CGraphic

Como pode ser observado, as etapas básicas consistem em criar o objeto da curva, cujos valores devem previamente estar nas matrizes de x e y: 

double x[] = {1,2,3,4,5,6,7,8,9,10};
double y[] = {1,2,3,2,4,3,5,6,4,3};
CCurve* cur = Graph.CurveAdd(x, y, CURVE_POINTS, "Points"); 

O objeto criado é colocado dentro da CGraphic, enquanto o método CurveAdd retorna o referência para ele. Isso é feito para que seja possível definir as propriedades adequadas desta curva, o que nós fizemos no segundo exemplo definindo o tipo de curva como CURVE_POINTS e indicando o tipo de ícone na forma de um círculo:

cur.PointsType(POINT_CIRCLE);

Uma vez que todas as linhas são adicionadas, é necessário exibir o gráfico seguindo o comando Create e Redraw.

Realizamos a mesma sequência de ações para nosso projeto do livro de ofertas, mas preparamos os dados para as curvas de um modo especial, enquanto que colocamos todos os comandos dentro da classe especial CElTickGraph, ela é um elemento filho da CElChart.

Integração da CGraphic com a biblioteca CPanel

Nós esclarecemos os pontos principais ao trabalhar com a CGraphic. Agora é hora de inserir esta classe na biblioteca CPanel. Como já mencionado, a CPanel fornece acesso aos eventos necessários, coloca os elementos gráficos corretamente, gerencia suas propriedades. Tudo isso é necessário para fazer o gráfico de ticks uma parte integrante de um único painel de livro de ofertas. Portanto, primeiro, escrevemos o elemento especial CElTickGraph, ele é parte da CPanel, que integra a CGraphic no painel. Além disso, CElTickGraph obterá um fluxo de ticks atualizado de preços e redesenhará o gráfico de ticks. A tarefa final é a mais difícil. Resumidamente, listamos o que tem que ser capaz de fazer o CElTickGraph.

  • CElTickGraph marca a área retangular dentro do painel flexível do livro de ofertas. A área é isolada por bordas pretas. 
  • Dentro da área  CElTickGraph é colocado o gráfico da CGraphic, que exibe o fluxo de ticks dos preços.
  • O fluxo de ticks mostra os últimos N ticks. O número N pode ser alterado nas configurações.
  • CElTickGraph atualiza os valores das curvas CCurve, que fazem parte da CGraphic, de modo que os ticks anteriores são removidos do gráfico, enquanto os novos são adicionados a ele. Graças a isto, CElTickGraph cria o efeito de gráfico de ticks mudando gradualmente.

Para simplificar a tarefa da CElTickGraph, usamos um buffer circular, cujo princípio já foi descrito num artigo separado.

Criamos quatro bufferes circulares para exibir os seguintes valores:

  • nível Ask (exibido como uma linha vermelha);
  • nível Bid (exibido como uma linha azul);
  • última transação do lado da compra (exibida como um triângulo azul virado para baixo);
  • última transação do lado da venda (exibida como um triângulo vermelho virado para cima);

Como pode ser visto, distinguiremos as transações Last entre si, por isso serão necessários dois bufferes circulares, e não um.

A segunda dificuldade é que o número de pontos entre Ask/Bid e os preços Last não corresponde. Quando se desenha uma linha continua, cada ponto no eixo X deve ter um valor no eixo Y. Mas, se, em lugar de uma linha, são utilizados pontos, no momento X o ponto pode se encontrar no gráfico, ou pode estar ausente. É necessário ter em conta esta propriedade, e usar o gráfico bidimensional. Suponhamos que temos um ponto com as seguintes coordenadas X-Y: 1000-57034. Então, no momento da chegada do novo tick, este ponto terá as coordenadas 999-57034. Além disso, ele se deslocará para a posição 994-57034. Sua última posição será 0-57034. Em seguida, ela desaparecerá do gráfico. O ponto após ela pode se encontrar a certa quantidade de passos dela Quando o ponto 1 tiver as coordenadas 994-57034, o ponto 2 estará em 995:57035 ou em 998:57035. Combinando as linhas no gráfico bidimensional, podemos mostrar esses espaços corretamente, sem legar o fluxo de ticks numa sequência contínua.

Imaginemos uma tabela hipotética que exibe o fluxo de transações, tendo em conta os índices:

Index Ask Bid Buy Sell
999 57034 57032 57034
998 57034 57032
57032
997 57034 57031 57034
996 57035 57033 57035
995 57036 57035

994 57036 57035

993 57036 57035
57035
992 57036 57034
57034
991 57036 57035 57035
...



Tabela 1. Esquema de sincronização do fluxo de ticks numa tabela (gráfico) bidimensional.

Nela Ask e Bid são preenchidos completamente, enquanto as transações de compra (Buy) e venda (Sell) às vezes estão ausentes. O posicionamento das leituras dos índices sincroniza corretamente séries de diferentes comprimentos. Não impota quantas transações Last houver, elas sempre corresponderão aos níveis Ask e Bid necessários.

Descrevemos o princípios de trabalho da CElTickGraph. Agora vamos dar o código-fonte completo e depois analisar os momentos mais difíceis nele.

//+------------------------------------------------------------------+
//|                                                         Graf.mqh |
//|                        Copyright 2017, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#include <Panel\ElChart.mqh>
#include <RingBuffer\RiBuffDbl.mqh>
#include <RingBuffer\RiBuffInt.mqh>
#include <RingBuffer\RiMaxMin.mqh>
#include "GlobalMarketBook.mqh"
#include "GraphicMain.mqh"
#include "EventNewTick.mqh"

input int TicksHistoryTotal = 200;
//+------------------------------------------------------------------+
//| Define o número da curva no objeto gráfico da CGraphic           |
//+------------------------------------------------------------------+
enum ENUM_TICK_LINES
{
   ASK_LINE,
   BID_LINE,
   LAST_BUY,
   LAST_SELL,
   LAST_LINE,
   VOL_LINE
};
//+------------------------------------------------------------------+
//| Elemento gráfico que exibe o gráfico de ticks                 |
//+------------------------------------------------------------------+
class CElTickGraph : public CElChart
{
private:
   
   CGraphicMain m_graf;
   /* Buffers circulares para trabalho rápido com o fluxo de ticks*/
   CRiMaxMin    m_ask;
   CRiMaxMin    m_bid;
   CRiMaxMin    m_last;
   CRiBuffDbl   m_last_buy;
   CRiMaxMin    m_last_sell;
   CRiBuffInt   m_vol;
   CRiBuffInt   m_flags;
   
   double       m_xpoints[];  // Matriz de índices
   void         RefreshCurves();
   void         SetMaxMin(void);
public:
                CElTickGraph(void);
   virtual void Event(CEvent* event);
   void         SetTiksTotal(int tiks);
   int          GetTiksTotal(void);
   void         Redraw(void);
   virtual void Show(void);
   virtual void OnHide(void);
   virtual void OnRefresh(CEventRefresh* refresh);
   void         AddLastTick();
};
//+------------------------------------------------------------------+
//| Inicialização do gráfico                                            |
//+------------------------------------------------------------------+
CElTickGraph::CElTickGraph(void) : CElChart(OBJ_RECTANGLE_LABEL)
{
   double y[] = {0};
   y[0] = MarketBook.InfoGetDouble(MBOOK_BEST_ASK_PRICE);
   double x[] = {0};
   
   CCurve* cur = m_graf.CurveAdd(x, y, CURVE_LINES, "Ask");   
   cur.Color(ColorToARGB(clrLightCoral, 255));
   cur.LinesEndStyle(LINE_END_ROUND);
   
   cur = m_graf.CurveAdd(x, y, CURVE_LINES, "Bid");
   cur.Color(ColorToARGB(clrCornflowerBlue, 255));
   
   cur = m_graf.CurveAdd(x, y, CURVE_POINTS, "Buy");
   cur.PointsType(POINT_TRIANGLE_DOWN);
   cur.PointsColor(ColorToARGB(clrCornflowerBlue, 255));
   cur.Color(ColorToARGB(clrCornflowerBlue, 255));
   cur.PointsFill(true);
   cur.PointsSize(5);
   
   
   cur = m_graf.CurveAdd(x, y, CURVE_POINTS, "Sell");
   cur.PointsType(POINT_TRIANGLE);
   cur.PointsColor(ColorToARGB(clrLightCoral, 255));
   cur.Color(ColorToARGB(clrLightCoral, 255));
   cur.PointsFill(true);
   cur.PointsSize(5);
   
   m_graf.CurvePlotAll();
   m_graf.IndentRight(1);
   m_graf.GapSize(1);
   SetTiksTotal(TicksHistoryTotal);
}
//+------------------------------------------------------------------+
//| Define o número de ticks na janela do gráfico                    |
//+------------------------------------------------------------------+
void CElTickGraph::SetTiksTotal(int tiks)
{
   m_last.SetMaxTotal(tiks);
   m_last_buy.SetMaxTotal(tiks);
   m_last_sell.SetMaxTotal(tiks);
   m_ask.SetMaxTotal(tiks);
   m_bid.SetMaxTotal(tiks);
   m_vol.SetMaxTotal(tiks);
   ArrayResize(m_xpoints, tiks);
   for(int i = 0; i < ArraySize(m_xpoints); i++)
      m_xpoints[i] = i;
}

//+------------------------------------------------------------------+
//| Atualiza as linhas dos ticks                                            |
//+------------------------------------------------------------------+
void CElTickGraph::RefreshCurves(void) 
{
   int total_last = m_last.GetTotal();
   int total_ask = m_ask.GetTotal();
   int total_bid = m_bid.GetTotal();
   int total = 10;
   for(int i = 0; i < m_graf.CurvesTotal(); i++)
   {
      CCurve* curve = m_graf.CurveGetByIndex(i);
      double y_points[];
      double x_points[];
      switch(i)
      {
         case LAST_LINE:
         {
            m_last.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         }
         case ASK_LINE:
            m_ask.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         case BID_LINE:
            m_bid.ToArray(y_points);
            if(ArraySize(x_points) < ArraySize(y_points))
               ArrayCopy(x_points, m_xpoints, 0, 0, ArraySize(y_points));
            curve.Update(x_points, y_points);
            break;
         case LAST_BUY:
         {
            m_last_buy.ToArray(y_points);
            CPoint2D points[];
            ArrayResize(points, ArraySize(y_points));
            int k = 0;
            for(int p = 0; p < ArraySize(y_points);p++)
            {
               if(y_points[p] == -1)
                  continue;
               points[k].x = p;
               points[k].y = y_points[p];
               k++;
            }
            if(k > 0)
            {
               ArrayResize(points, k);
               curve.Update(points);
            }
            break;
         }
         case LAST_SELL:
         {
            m_last_sell.ToArray(y_points);
            CPoint2D points[];
            ArrayResize(points, ArraySize(y_points));
            int k = 0;
            for(int p = 0; p < ArraySize(y_points);p++)
            {
               if(y_points[p] == -1)
                  continue;
               points[k].x = p;
               points[k].y = y_points[p];
               k++;
            }
            if(k > 0)
            {
               ArrayResize(points, k);
               curve.Update(points);
            }
            break;
         }
      }
   }
   
}
//+------------------------------------------------------------------+
//| Retorna o número de ticks na janela do gráfico                       |
//+------------------------------------------------------------------+
int CElTickGraph::GetTiksTotal(void)
{
   return m_ask.GetMaxTotal();
}
//+------------------------------------------------------------------+
//| Atualiza o gráfico no momento da atualização do livro                     |
//+------------------------------------------------------------------+
void CElTickGraph::OnRefresh(CEventRefresh* refresh)
{
   // Desenhamos no gráfico os últimos ticks de entrada
   int dbg = 5;
   int total = ArraySize(MarketBook.LastTicks);
   for(int i = 0; i < ArraySize(MarketBook.LastTicks); i++)
   {
      MqlTick tick = MarketBook.LastTicks[i];
      if((tick.flags & TICK_FLAG_BUY)==TICK_FLAG_BUY)
      {
         m_last_buy.AddValue(tick.last);
         m_last_sell.AddValue(-1);
         m_ask.AddValue(tick.last);
         m_bid.AddValue(tick.bid);
      }
      if((tick.flags & TICK_FLAG_SELL)==TICK_FLAG_SELL)
      {
         m_last_sell.AddValue(tick.last);
         m_last_buy.AddValue(-1);
         m_bid.AddValue(tick.last);
         m_ask.AddValue(tick.ask);
      }
      if((tick.flags & TICK_FLAG_ASK)==TICK_FLAG_ASK ||
         (tick.flags & TICK_FLAG_BID)==TICK_FLAG_BID)
      {
         m_last_sell.AddValue(-1);
         m_last_buy.AddValue(-1);
         m_bid.AddValue(tick.bid);
         m_ask.AddValue(tick.ask);
      }
   }
   MqlTick tick;
   if(!SymbolInfoTick(Symbol(), tick))
       return;
   if(ArraySize(MarketBook.LastTicks)>0)
   {
      RefreshCurves();
      m_graf.Redraw(true);
      m_graf.Update();
   }
}
void CElTickGraph::Event(CEvent *event)
{
   CElChart::Event(event);
   if(event.EventType() != EVENT_CHART_CUSTOM)
      return;
   CEventNewTick* ent = dynamic_cast<CEventNewTick*>(event);
   if(ent == NULL)
      return;
   MqlTick tick;
   ent.GetNewTick(tick);
   if((tick.flags & TICK_FLAG_BUY) == TICK_FLAG_BUY)
   {
      int last = m_last_buy.GetTotal()-1;
      if(last >= 0)
         m_last_buy.ChangeValue(last, tick.last);
   }
}
//+------------------------------------------------------------------+
//| Calcula a escala dos eixos de modo que o preço atual sempre|
//| apareça no ponto médio do gráfico de preços                                     |
//+------------------------------------------------------------------+
void CElTickGraph::SetMaxMin(void)
{
   double max = m_last.MaxValue();
   double min = m_last.MinValue();
   double curr = m_last.GetValue(m_last.GetTotal()-1);
   double max_delta = max - curr;
   double min_delta = curr - min;
   if(max_delta > min_delta)
      m_graf.SetMaxMinValues(0, m_last.GetTotal(), (max-max_delta*2.0), max);
   else
      m_graf.SetMaxMinValues(0, m_last.GetTotal(), min, (min+min_delta*2.0));
}
//+------------------------------------------------------------------+
//| Atualiza o gráfico                                                 |
//+------------------------------------------------------------------+
void CElTickGraph::Redraw(void)
{
   m_graf.Redraw(true);
   m_graf.Update();
}
//+------------------------------------------------------------------+
//| Intercepta a exibição do gráfico, alterando a precedência da exibição   |
//+------------------------------------------------------------------+
void CElTickGraph::Show(void)
{
   BackgroundColor(clrNONE);
   BorderColor(clrBlack);
   Text("Ticks:");
   //m_graf.BackgroundColor(clrWhiteSmoke);
   m_graf.Create(ChartID(), "Ticks", 0, (int)XCoord()+20, (int)YCoord()+30, 610, 600); 
   m_graf.Redraw(true);
   m_graf.Update();
   CElChart::Show();
}

//+------------------------------------------------------------------+
//| No momento de exibição, mostramos o gráfico                           |
//+------------------------------------------------------------------+
void CElTickGraph::OnHide(void)
{
   m_graf.Destroy();
   CNode::OnHide();
}

Analisaremos este código em mais detalhes. Começamos com o construtor da classe CElTickGraph::CElTickGraph. A partir de sua declaração, é claro que a classe é baseada na primitiva gráfica OBJ_RECTANGLE_LABEL, ou seja. no rótulo retangular convencional. O construtor cria várias curvas do tipo CCurve, cada uma delas responde pelo seu próprio tipo de dados. Para cada um deles, são definidas propriedades: nome de lina, seu tipo e cor. Quando se cria a curva, os valores que ela apresentará são ainda desconhecidos, por isso, usamos matrizes falsas double x e y, que contêm as coordenadas do primeiro ponto. Uma vez que as curvas são criadas e colocadas no objeto da CGraphic, são configurados os buffers circulares no método SetTiksTotal. A configuração se reduz à instalação de um número limite de ticks memorizados, que é definido pelos parâmetros externos TicksHistoryTotal.

O livro de ofertas está prestes a trabalhar, quando todas as curvas necessárias são adicionadas na CGraphic, e os buffers circulares estão configurados apropriadamente. No processo, são chamados dois métodos fundamentais: CElTickGraph::OnRefresh e CElTickGraph::RefreshCurves. Vamos examiná-los.

O método OnRefresh é chamado depois de uma mudança no livro de ofertas. Esse tipo de alterações são acompanhados usando a função OnBookEvent:

//+------------------------------------------------------------------+
//| MarketBook change event                                          |
//+------------------------------------------------------------------+
void OnBookEvent(const string &symbol)
{
   
   if(symbol != MarketBook.GetMarketBookSymbol())
      return;
   MarketBook.Refresh();
   MButton.Refresh();
   ChartRedraw();
}

Primeiro, é atualizado o livro de ofertas (MarketBook.Refresh()), logo, seu painel: MButton.Refresh(). Como o painel é exibido como um botão pode se maximizar/minimizar, em si esse botão é o elemento pai de todo o painel. Portanto, todos os eventos, incluindo a ordem de atualização, são invocados através deste botão (MButton). A ordem de atualização passa através de todos os elementos anexados no botão e, finalmente, alcança o CElTickGraph, em que se encontra o algoritmo que atualiza o gráfico. O algoritmo é implementado no método OnRefresh.

Inicialmente, o algoritmo obtém o número de ticks que conseguiram surgir desde a anterior atualização. Em seguida, os valores de cada um dos ticks são adicionados ao buffer circular respectivo. O preço Ask do tick é adicionado ao buffer circular m_ask, enquanto o preço Bid, ao buffer m_bid e assim por diante. Se o tipo do último tick for a transação Last, os preços Ask e Bid serão forçosamente sincronizados com o preço Last. Isso é feito, porque o terminal não realiza tal sincronização, mas sim gera os valores Ask e Bid dos ticks anteriores. Desse modo, as transações Last sempre se encontram quer no nível Ask quer no nível Bid. Reparemos que o livro de ofertas padrão não faz uma sincronização desse tipo, e o Last nele pode se encontrar entre estas duas linhas.

Após a última fila de ticks ser colocada no buffer circular, é chamado o método OnRefreshCurves, responsável pela plotagem de ticks no gráfico. No método, é colocado o ciclo, em que buscadas exaustivamente todas as curvas CCurve disponíveis. Para cada curva, é realizada uma atualização completa dos pontos por meio do método curve.Update. Obtemos os pontos para o eixo Y copiando todos os valores do buffer circular para a matriz double convencional. O pontos para os pata o eixo X são obtidos de uma maneira mais sofisticada. Com a busca exaustiva, para cada ponto Y, é substituída a coordenada X por x-1. Ou seja, se o elemento x tiver o valor de 1000, após esta busca, ele terá o valor de 999. Assim, é alcançado o efeito de movimento, quando o gráfico plota novos valores, enquanto os anteriores desaparecem.

Uma vez que todos os valores são colocados nos índices necessários e as curvas CCurve são atualizadas, resta atualizar o livro de ofertas, para o qual o método OnRefresh chama os métodos de atualização do gráfico:  m_graf.Redraw e m_graf.Update.

O algoritmo de exibição do gráfico de ticks permite escolher dois modos:

  • O gráfico de ticks é exibido sem anexação dos últimos preços à metade do livro de ofertas. Os máximos e mínimos do gráfico são calculados automaticamente, dentro da CGraphic.
  • O gráfico de ticks é mostrado com base na anexação dos últimos preços à metade do livro de ofertas. Onde quer que estejam os preços máximo e mínimo, a preço atual - último - sempre aparecerá na metade do gráfico.

No primeiro caso, é chamado o dimensionamento automático, realizado pela CGraphic. No segundo caso, o dimensionamento é realizado pelo método SetMaxMin.

Instalação, dinâmica de trabalho, características comparativas do livro de ofertas

Todos os arquivos necessários, para o desenvolvimento de nosso aplicativo, podem ser divididos em quatro grupos:

  • arquivos da biblioteca gráfica CPanel, localizados em MQL5\Include\Panel;
  • arquivo da classe de livro de ofertas MarketBook, localizado em MQL5\Include\Trade;
  • arquivos das classes dos buffers circulares, localizados em MQL5\Include\RingBuffer;
  • Arquivos do livro de ofertas de scalping, localizados em MQL5\Indicators\MarketBookArticle.

O arquivo anexo contém todos os arquivos nos seus diretórios respectivos. Para instalar o programa, simplesmente descompacte o arquivo na pasta MQL5. Não é necessário criar subpastas. Depois da descompactação, compile o arquivo MQL5\Indicators\MarketBookArticle\MarketBook.mq5. Após a compilação, aparecerá o indicador correspondente, que será exibido na janela do Navegador do MetaTrader 5. 

A melhor maneira de avaliar um algoritmo consiste em exibir as mudanças do gráfico de ticks de uma forma dinâmica. O vídeo a seguir mostra como o gráfico de ticks se altera ao longo do tempo, deslocando gradualmente a janela do gráfico para a direita:


Observemos que o gráfico de ticks resultante de nosso livro de ofertas difere do mesmo gráfico do livro de ofertas no MetaTrader 5. A tabela comparativa abaixo mostra as diferenças:

Livro de ofertas padrão no MetaTrader 5 Livro de ofertas desenvolvido
Preços Last, Ask e Bid não estão interligados. O preço Last pode estar num nível diferente do Ask e Bid. Preços Last, Ask e Bid sincronizados. O preço Last sempre se encontra quer no nível Ask quer no nível Bid.
Os preços Last são exibidos como círculos de diâmetros diferentes, diretamente proporcionais ao volume de transações. O círculo com o diâmetro máximo corresponde à transação com o volume máximo, concluída nos últimos N ticks, onde N é o período da janela deslizante do gráfico de ticks. As transações de compra são exibidas como um triângulo azul apontando para baixo, enquanto as transações de venda, como um triângulo vermelho, para cima. Não se exibem transações dependendo do seu volume.
A escala do gráfico de ticks é sincronizada com a altura da tabela de pedidos pendentes. Assim, qualquer nível de transações na tabela corresponde ao mesmo nível no gráfico da ticks. A desvantagem desta solução é que não se pode exibir o gráfico de ticks numa escala maior. A vantagem é a visibilidade dos preços e a total conformidade dos níveis do livro de ofertas com o gráfico de ticks. As escalas do gráfico de ticks e da tabela de ordens pendentes não coincidem entre si. O preço atual do gráfico de ticks só pode corresponder mais o menos à metade da tabela de ordens. A desvantagem desta solução é que os níveis da tabela de ordens não se ajustam visualmente ao gráfico de ticks. A vantagem é que se pode definir quase qualquer escala para o gráfico de ticks.
O gráfico de ticks é fornecido com um histograma de volumes adicional localizado abaixo dele.  O ​​gráfico de ticks não contém quaisquer complementos.

Tabela 2. Características comparativas do livro de ofertas padrão o desenvolvido.

Fim do artigo

Consideramos todos os principais pontos no desenvolvimento de um livro de ofertas de scalping.

  • Aprimoramos a aparência da tabela de ordens.
  • Adicionamos um gráfico de ticks com base na CGraphic ao painel, modernizando significativamente o motor gráfico.
  • Terminamos o livro de ofertas adicionando o algoritmo de sincronização de ticks com a tabela de ordens atual.

No entanto, mesmo agora, nosso livro de ofertas ainda está longe da versão completa de scalping. Claro, muitos usuários podem se decepcionar, depois de ter lido até aqui, sem ter visto aplicativos que funcionam de maneira bem parecida com o livro de ofertas padrão ou mesmo programas especializados como a unidade de Bondar ou QScalp. Porém, deve-se entender que qualquer software complexo tem de passar por uma série de fases evolutivas, durante seu desenvolvimento. Aqui está o que eu acho que é apropriado adicionar às seguintes versões do livro de ofertas:

  • Funcionalidade para colocar ordens limite diretamente no painel do livro.
  • Recurso para acompanhar ordens grandes no gráfico de ticks.
  • Diferenciar as transações Last pelo volume, exibindo-as de diferentes maneiras no gráfico.
  • Exibir indicadores adicionais paralelamente ao gráfico de ticks. Por exemplo, sob o gráfico de ticks, mostrar o histograma de relação entre todas as ordens Buy Limit e as Sell Limit.
  • E, finalmente, a coisa mais importante: carregar e salvar o histórico do livro de ofertas, de modo que seja possível construir estratégias de negociação em modo teste offline.

Tudo isto pode ser realizado, e é possível essas opções serem lançadas. Se os usuários manifestarem interesse no assunto, continuarão sendo liberados artigos sobre esta série. 

Traduzido do russo por MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/ru/articles/3336

Arquivos anexados |
Redes Neurais Profundas (Parte IV). Criação, treinamento e teste de um modelo de rede neural Redes Neurais Profundas (Parte IV). Criação, treinamento e teste de um modelo de rede neural

Este artigo considera novas capacidades do pacote darch (v.0.12.0). Contém uma descrição do treinamento de redes neurais profundas com diferentes tipos de dados, diferentes estruturas e sequências de treinamento. Os resultados do treino estão incluídos.

Expert Advisor universal: indicador CUnIndicator e trabalho com ordens pendentes (parte 9) Expert Advisor universal: indicador CUnIndicator e trabalho com ordens pendentes (parte 9)

O artigo descreve o trabalho com indicadores através da classe universal do CUnIndicator. Além disso, consideram-se novas formas de trabalhar com ordens pendentes. Observe que, a partir deste ponto, a estrutura do projeto do CStrategy muda significativamente. Agora todos os arquivos são colocados num único diretório para a conveniência dos usuários.

TradeObjects: Automação de negociação com base em objetos gráficos na MetaTrader TradeObjects: Automação de negociação com base em objetos gráficos na MetaTrader

Este artigo lida com uma abordagem simples para a criação de um sistema de negociação automatizado com base no desenho de uma linha ao gráfico e oferece um Expert Advisor pronto, usando as propriedades padrão dos objetos da MetaTrader 4 e 5, suportando as principais operações de negociação.

Interfaces gráficas XI: Integração da Biblioteca Gráfica Padrão (build 16) Interfaces gráficas XI: Integração da Biblioteca Gráfica Padrão (build 16)

Uma nova versão da biblioteca gráfica para a criação de gráficos científicos (a classe CGraphic) foi apresentada recentemente. Esta atualização da biblioteca desenvolvida para criar interfaces gráficas irá introduzir uma versão com um novo controle para a criação de gráficos. Agora está ainda mais fácil de visualizar os dados de diferentes tipos.