Desenvolvendo um Trading System com base no Livro de Ofertas (Parte I): o indicador
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:
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!
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.
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso