preview
Desenvolvendo um Trading System com base no Livro de Ofertas (Parte I): o indicador

Desenvolvendo um Trading System com base no Livro de Ofertas (Parte I): o indicador

MetaTrader 5Exemplos | 5 setembro 2024, 11:14
150 0
Daniel Santos
Daniel Santos

Introdução

Recapitulando sobre o que venha a ser o Depth of Market, sabemos que se trata de uma fila de ordens pendentes do tipo limite. Essas ordens representam as intenções de negociações dos agentes de mercado, e frequentemente não resultam em uma negociação propriamente dita. Isso porque os players tem a possibilidade de cancelar suas ordens previamente apregoadas por diversos motivos. Dentre eles estão mudança nas condições do mercado, e o consequente desinteresse na execução daquela ordem na quantidade e no preço especificado anteriormente.

O valor retornado pela função SymbolInfoInteger(_Symbol, SYMBOL_TICKS_BOOKDEPTH) é justamente a profundidade do livro de ofertas, e representa metade do array a ser preenchido com os níveis de preços a serem trabalhados. Metade desse array se destina à quantidade de ordens limite de venda, e a outra metade para ordens limite de compra que foram apregoadas. Conforme a documentação, para ativos que não tem fila de solicitações, o valor dessa propriedade é igual a zero. Temos um exemplo dessa ideia na próxima figura, que é de um livro de ofertas cuja profundidade tem valor 10. Além disso, todos os níveis de preços disponibilizados estão visíveis:

Exemplo: Livro de Ofertas com profundidade 10

Note que a profundidade pode ser obtida do símbolo, e não necessariamente do livro de ofertas. A função o uso da SymbolInfoInteger seria suficiente para a obtenção do valor da propriedade, e ainda não precisaríamos do manipulador OnBookEvent ou das funções relacionadas tais como  MarketBookAdd. Claro que podemos chegar ao mesmo resultado através contagem do número de elementos do array do tipo MqlBookInfo que o manipulador OnBookEvent preenche, conforme veremos mais detalhadamente adiante.

Você pode estar se perguntando por que usar esse indicador ao invés de simplesmente utilizar o livro de ofertas padrão do MetaTrader 5. Apresento algumas das razões:

  • otimização do espaço disponível no gráfico, escolhendo o tamanho do histograma e sua posição na tela
  • apresentação dos eventos do livro de forma mais limpa
  • uso no testador de estratégia, mediante futura implementação de mecanismo de armazenamento em disco dos eventos BookEvent, levando-se em conta a impossibilidade de realizar esse teste de forma nativa até o momento


Geração do símbolo customizado

É o processo que irá nos permitir a realização de testes do indicador mesmo com o mercado fechado, ou ainda quando o broker não transmitir os eventos para o símbolo em questão. Ou seja, não haverá fila de solicitações online nesses casos, tampouco vamos encontrar esses eventos armazenados em um cache no computador local. Ainda não trabalharemos com eventos que ocorreram no passado em um símbolo real, mas nos concentraremos nesse momento na geração de BookEvent simulados para ativos fictícios. Isso porque a criação desse ativo e a simulação dos eventos se justificam na obrigatoriedade de se trabalhar com a função CustomBookAdd. Ela é uma função que foi desenhada para funcionar especificamente com símbolos customizados.

Temos o script CloneSymbolTicksAndRates a seguir, que fará a geração do símbolo customizado. Foi adaptado da documentação para atender nossa necessidade, e inicia com a definição de algumas constantes e a inclusão de uma biblioteca padrão DateTime.mqh para trabalhar com datas. Observe que o nome do símbolo customizado será derivado da nomenclatura do símbolo real, que será passado ao script pela função Symbol(). Portanto, é necessário rodar esse script sobre o ativo real que se deseja clonar. Embora também seja possível clonar símbolos personalizados, não vejo muito sentido em fazer isso.

#define   CUSTOM_SYMBOL_NAME     Symbol()+".C"     
#define   CUSTOM_SYMBOL_PATH     "Forex"           
#define   CUSTOM_SYMBOL_ORIGIN   Symbol()          

#define   DATATICKS_TO_COPY      UINT_MAX 
#define   DAYS_TO_COPY           5
#include <Tools\DateTime.mqh>

No trecho a seguir, que está inserido na função OnStart() do mesmo script, vemos a criação do objeto de data "timemaster" usado para o cálculo do período de tempo que serão coletados os ticks e as barras para a clonagem. Conforme a constante DAYS_TO_COPY que definimos, a função Bars irá copiar os últimos 5 dias do símblo de origem. O mesmo tempo inicial do range é convertido para milissegundos e empregado pela função CopyTicks, completando assim a clonagem do ativo.

   CDateTime timemaster;
   datetime now = TimeTradeServer();
   timemaster.Date(now);
   timemaster.DayDec(DAYS_TO_COPY);
   long DaysAgoMsc = 1000 * timemaster.DateTime();
   int bars_origin = Bars(CUSTOM_SYMBOL_ORIGIN, PERIOD_M1, timemaster.DateTime(), now);
   int create = CreateCustomSymbol(CUSTOM_SYMBOL_NAME, CUSTOM_SYMBOL_PATH, CUSTOM_SYMBOL_ORIGIN);
   if(create != 0 && create != 5304)
      return;
   MqlTick array[] = {};
   MqlRates rates[] = {};
   int attempts = 0;
   while(attempts < 3)
     {
      int received = CopyTicks(CUSTOM_SYMBOL_ORIGIN, array, COPY_TICKS_ALL, DaysAgoMsc, DATATICKS_TO_COPY);
      if(received != -1)
        {
         if(GetLastError() == 0)
            break;
        }
      attempts++;
      Sleep(1000);
     }

Assim que o processo se completar, o novo símbolo já deverá aparecer na lista de ativos da observação de mercado, com o nome <AtivodeOrigem>.C. Devemos então abrir novo gráfico com esse símbolo sintético e prosseguir para a próxima etapa.

Caso já exista um outro símbolo sintético poderá aproveitá-lo, então será desnecessário criar um outro da maneira que foi explicado nessa sessão. Ao final, bastará abrir um novo gráfico nesse símbolo customizado, e executar nele dois outras aplicações MQL5 que desenvolveremos aqui: o indicador e o script gerador de eventos. Detalharemos tudo na sequencia.


Script gerador de eventos do tipo BookEvent para testes

Ter um símbolo customizado apenas não supre a ausência de uma sequencia de ticks do livro de ofertas online no momento em que se deseja fazer backtest do indicador que se baseia nos eventos de livro. Portanto, será necessário um processo de geração de dados simulados. Por isso foi desenvolvido o script a seguir.

//+------------------------------------------------------------------+
//|                                            GenerateBookEvent.mq5 |
//|                                               Daniel Santos      |
//+------------------------------------------------------------------+
#property copyright "Daniel Santos"
#property version   "1.00"
#define SYNTH_SYMBOL_MARKET_DEPTH      32
#define SYNTH_SYMBOL_BOOK_ITERATIONS   20
#include <Random.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
double BidValue, tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
MqlBookInfo    books[];
int marketDepth = SYNTH_SYMBOL_MARKET_DEPTH;
CRandom rdn;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   if(!SymbolInfoInteger(_Symbol, SYMBOL_CUSTOM)) // if the symbol exists
     {
      Print("Custom symbol ", _Symbol, " does not exist");
      return;
     }
   else
      BookGenerationLoop();
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void BookGenerationLoop()
  {
   MqlRates BarRates_D1[];
   CopyRates(_Symbol, PERIOD_D1, 0, 1, BarRates_D1);
   if(ArraySize(BarRates_D1) == 0)
      return;
   BidValue = BarRates_D1[0].close;
   ArrayResize(books, 2 * marketDepth);
   for(int j = 0; j < SYNTH_SYMBOL_BOOK_ITERATIONS; j++)
     {
      for(int i = 0, j = 0; i < marketDepth; i++)
        {
         books[i].type = BOOK_TYPE_SELL;
         books[i].price = BidValue + ((marketDepth - i) * tickSize);
         books[i].volume_real = rdn.RandomInteger(10, 500);
         books[i].volume_real = round((books[i].volume_real + books[j].volume_real) / 2);
         books[i].volume = (int)books[i].volume_real;
         //----
         books[marketDepth + i].type = BOOK_TYPE_BUY;
         books[marketDepth + i].price = BidValue - (i * tickSize);
         books[marketDepth + i].volume_real = rdn.RandomInteger(10, 500);
         books[marketDepth + i].volume_real = round((books[marketDepth + i].volume_real
                                              + books[marketDepth + j].volume_real) / 2);
         books[marketDepth + i].volume = (int)books[marketDepth + i].volume_real;
         if(j != i)
            j++;
        }
      CustomBookAdd(_Symbol, books);
      Sleep(rdn.RandomInteger(400, 1000));
     }
  }
//+------------------------------------------------------------------+

Foi utilizada uma outra implementação de números aleatórios de 32 bits, ao invés da função padrão MathRand(). Isso se deve a uma série de motivos. Dentre eles está a praticidade de gerar inteiros dentro de uma faixa especificada, conforme aproveitamos nesse script através da função RandomInteger(min, max).

Escolheu-se uma constante relativamente alta para a profundidade do livro de ofertas, 32. Ou seja, serão no total 64 níveis de preço sendo gerados a cada iteração. Defina um valor menor, se desejar.

O algoritmo testa se o símbolo é de fato personalizado. Em caso positivo, segue o loop de geração de cada um dos itens do livro de ofertas, repetindo isso em outro loop na quantidade de iterações definida. Nessa implementação temos 20 repetições, com pausas entre elas com durações aleatoriamente escolhidas numa faixa que vai de 400 milissegundos até 1000 milissegundos (que é o mesmo que 1 segundo). Essa dinâmica torna a visualização dos ticks mais agradável e realista.

Os preços têm ancoragem vertical no último preço de fechamento do timeframe diário, conforme extraído do ativo de origem. Dessa referência para cima, ficarão os 32 níveis de ofertas de venda; abaixo desse preço de referência, os 32 níveis de compra. Pela paleta padrão do indicador, as barras do histograma referentes a ofertas de vendas têm tom avermelhado, e as de compra são em tons de azul claro.

A diferença de preço entre um nível e o seguinte é estabelecida pelo tamanho do tick do ativo, obtida através da propriedade SYMBOL_TRADE_TICK_SIZE.


Indicador para exibição das mudanças de Market Depth

Código-fonte da biblioteca

O indicador foi desenvolvido empregando programação orientada a objeto. Foi desenvolvida a classe BookEventHistogram para gerenciamento do histograma do livro de ofertas, desde a criação, atualização e remoção das barras quando o objeto dessa classe é destruído.

A seguir, as declarações das variáveis e das funções da classe BookEventHistogram:

class BookEventHistogram
  {
protected:
   color                histogramColors[]; //Extreme / Mid-high / Mid-low
   int                  bookSize;
   int                  currElements;
   int                  elementMaxPixelsWidth;
   bool                 showMessages;
   ENUM_ALIGN_MODE      corner;
   string               bookEventElementPrefix;
public:
   MqlBookInfo          lastBook[];
   datetime             lastDate;
   void                 SetAlignLeft(void);
   void                 SetCustomHistogramColors(color &colors[]);
   void                 SetBookSize(int value) {bookSize = value;}
   void                 SetElementMaxPixelsWidth(int m);
   int                  GetBookSize(void) {return bookSize;}
   void                 DrawBookElements(MqlBookInfo& book[], datetime now);
   void                 CleanBookElements(void);
   void                 CreateBookElements(MqlBookInfo& book[], datetime now);
   void                 CreateOrRefreshElement(int buttonHeigh, int buttonWidth, int i, color clr, int ydistance);
   //--- Default constructor
                     BookEventHistogram(void);
                    ~BookEventHistogram(void);
  };

Veja que nem todas as funções estão definidas nesse segmento, porém são complementadas nas demais linhas do arquivo BookEventHistogram.mqh.

Dentre as mais importantes, as funções CreateBookElements e CreateOrRefreshElement trabalham em conjunto para garantir a atualização dos elementos existentes, criando-os quando ainda não existentes. As demais funções servem para manter as propriedades atualizadas, ou para retornar o valor de algumas dessas variáveis do objeto.


Código-fonte do indicador

O início do código contem a definição do número de plots e de buffers para 3. Uma análise mais aprofundada vai constatar que na verdade não são empregados buffers da estrutura raiz de um indicador em MQL5. Entretanto essa declaração possibilita a geração de um código que vai garantir a interação do usuário com algumas propriedades durante a inicialização do indicador. Nesse caso, estamos interessado nas propriedades de cores, cujo esquema de inputs favorece uma experiência intuitiva e amigável ao usuário.

Cada um dos plots passa duas cores, uma de compra outra de venda. Esse conjunto de seis cores vai ser empregado para a escolha da cor que cada segmento terá, conforme os critérios. De uma forma superficial, os segmentos de maior tamanho no histograma são classificados como "extremos", os de tamanho acima da média são "mid-high", e os outros "mid-low".

As cores são recuperadas com a função PlotIndexGetInteger, onde especifica-se o plot e a posição dentro do plot que se deseja extrair a informação.

#define NUMBER_OF_PLOTS 3
#property indicator_chart_window
#property indicator_buffers NUMBER_OF_PLOTS
#property indicator_plots   NUMBER_OF_PLOTS
//--- Invisible plots
#property indicator_label1  "Extreme volume elements colors"
#property indicator_type1   DRAW_NONE
#property indicator_color1  C'212,135,114', C'155,208,226'
//---
#property indicator_label2  "Mid-high volume elements colors"
#property indicator_type2   DRAW_NONE
#property indicator_color2  C'217,111,86', C'124,195,216'
//---
#property indicator_label3  "Mid-low volume elements color"
#property indicator_type3   DRAW_NONE
#property indicator_color3  C'208,101,74', C'114,190,214'
#include "BookEventHistogram.mqh"
enum HistogramPosition
  {
   LEFT,      //<<<< Histogram on the left
   RIGHT,     //Histogram on the right >>>>
  };
enum HistogramProportion
  {
   A_QUARTER,   // A quarter of the chart
   A_THIRD,     // A third of the chart
   HALF,        // Half of the chart
  };
input  HistogramPosition position = RIGHT; // Indicator position
input  HistogramProportion proportion = A_QUARTER; // Histogram ratio (compared to chart width)
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
double volumes[];
color histogramColors[];
BookEventHistogram bookEvent;

Prosseguindo, temos dois enumeradores visando oferecer opções bem específicas ao usuário durante o carregamento do indicador.  Queremos saber onde o histograma deve ser desenhado, se à direita ou à esquerda do gráfico. Também é requerido que o usuário determine a proporção que o histograma irá ocupar em relação à largura do gráfico: um quarto, um terço ou metade do gráfico. Por exemplo, se eu estiver diante de um gráfico de largura 500 pixels e definir a proporção para metade, as barras do histograma poderão ter tamanho de 0 até 250 pixels.

Finalmente, vamos observar no código-fonte BookEvents.mq5 que as funções OnBookEvent e OnChartEvent é que irão desencadear quase todas as requisições de atualização do histograma. OnCalculate acaba por não ter participação no algoritmo, sendo mantida por uma questão de sintaxe do mql.


Utilização dos scripts e indicador

A sequência correta para acionamento dos scripts e do indicador é a seguinte (para que seja coerente com os recursos desenvolvidos até aqui):

  • script CloneSymbolTicksAndRates (no gráfico do símbolo real que desejo clonar)
  • -> indicador BookEvents (gráfico do símbolo customizado gerado)
  • -> script GenerateBookEvent (gráfico do símbolo customizado gerado)

O evento BookEvent é disseminado para todas as instâncias gráficas do ativo customizado para o qual é apontado. Por isso, tanto faz executar o indicador e o gerador de eventos em gráficos separados ou no mesmo gráfico, desde que sejam ambos no mesmo nome de símbolo personalizado.

A animação abaixo ilustra essa sequência e também o funcionamento do indicador. Espero que gostem!

Demonstração - Indicador BookEvents


Conclusão

O livro de ofertas - Depth of Market - é sem dúvidas algo de bastante relevância para a execução de trades rápidos, sobretudo em algoritmos de alta frequência - os HFT. Esse é um tipo de evento que podemos obter do mercado através do broker em muitos dos ativos negociados. Com o passar do tempo, há chances de que os brokers tenham interesse na ampliação na cobertura e na disponibilidade dessas informações também para os outros ativos.

Adicionalmente acredito que não seja adequado construir um trade system que se baseie apenas no livro de ofertas, mas o livro pode ajudar a encontrar zonas de liquidez e ter alguma correlação com o movimento do preço. Por isso julgo prudente aliar a análise do livro de ofertas com outras ferramentas e indicadores que no conjunto possam trazer a um resultado consistente.

Fica em aberto a possibilidade de aperfeiçoamentos futuros do indicador, tais como a implementação de formas de se armazenar eventos do tipo BookEvent, e de posteriormente empregar esses dados em backtest. Seja em negociações manuais, ou seja em automações.


Arquivos anexados |
Algoritmo de otimização baseado em brainstorming — Brain Storm Optimization (Parte I): Clusterização Algoritmo de otimização baseado em brainstorming — Brain Storm Optimization (Parte I): Clusterização
Neste artigo, discutimos um método inovador de otimização chamado BSO (Brain Storm Optimization), inspirado na tempestade de ideias (brainstorming). Também abordamos um novo enfoque para resolver problemas de otimização multimodal que utiliza o BSO, permitindo encontrar várias soluções ótimas sem a necessidade de definir previamente o número de subpopulações. Além disso, analisamos os métodos de clusterização K-Means e K-Means++.
Do básico ao intermediário: Diretiva Include Do básico ao intermediário: Diretiva Include
Neste artigo, vamos falar de uma diretiva de compilação, muito utilizada nos mais diversos códigos que você poderá ver em MQL5. Apesar desta diretiva de compilação ser explicada aqui de maneira bem básica e superficial. É importante que comecemos a entender como usar ela. Já que em breve ela será indispensável para continuarmos em direção a um nível de programação maior. O conteúdo exposto aqui, visa e tem como objetivo, pura e simplesmente a didática. De modo algum deve ser encarado como sendo, uma aplicação cuja finalidade não venha a ser o aprendizado e estudo dos conceitos mostrados.
Redes neurais de maneira fácil (Parte 84): normalização reversível (RevIN) Redes neurais de maneira fácil (Parte 84): normalização reversível (RevIN)
Há muito já aprendemos que o pré-processamento dos dados brutos desempenha um grande papel na estabilidade do treinamento do modelo. E, para o processamento online de dados "brutos", frequentemente usamos a camada de normalização em lote. No entanto, às vezes surge a necessidade de um procedimento inverso. Um dos possíveis métodos para resolver tais tarefas é discutido neste artigo.
Algoritmos de otimização populacional: Algoritmo Boids, ou algoritmo de comportamento de enxame (Boids Algorithm, Boids) Algoritmos de otimização populacional: Algoritmo Boids, ou algoritmo de comportamento de enxame (Boids Algorithm, Boids)
Neste artigo, estudaremos algoritmo Boids, baseado em exemplos únicos de comportamento de enxame de animais. O algoritmo Boids, por sua vez, serviu como base para a criação de uma classe inteira de algoritmos, agrupados sob o nome de "Inteligência de Enxame".