Desenvolvimento de um utilitário de navegação e seleção de símbolos em MQL5 e MQL4

12 fevereiro 2019, 07:17
Roman Klymenko
0
170

Introdução

Traders experientes estão bem cientes do fato de que a maioria das coisas demoradas na negociação não são abrir e monitorar posições, mas sim selecionar símbolos e procurar pontos de entrada.

Claro, estes não são problemas enormes se você trabalha apenas com 1-2 símbolos. Mas se o seu kit de ferramentas de negociação consiste em centenas de ações e dezenas de símbolos Forex, isto pode levar várias horas apenas para encontrar pontos de entrada adequados.

Neste artigo, nós vamos desenvolver um EA simplificando a busca por ações. O EA deve ser útil de três maneiras:

  • ele deve pré-filtrar ações fornecendo-nos uma lista das que atendem às nossas condições;
  • ele deve simplificar a navegação através da lista de ações gerada;
  • ele deve exibir dados adicionais necessários para a tomada de uma decisão.

Modelo inicial do EA

Inicialmente, nós vamos desenvolver o EA em MQL5. No entanto, uma vez que muitas corretoras ainda não oferecem contas em MetaTrader 5, nós iremos redesenvolver o EA para que ele também funcione na MetaTrader 4 no final do artigo.

Então, vamos preparar o modelo que não é muito diferente daquele que pode ser gerado pelo assistente da MQL5:

//+------------------------------------------------------------------+
//|                                                     _finder.mq5  |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Klymenko Roman (needtome@icloud.com)"
#property link      "https://logmy.net"
#property version   "1.00"
#property strict

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- cria um timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   EventKillTimer();
  }
//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer()
  {
  }
//+------------------------------------------------------------------+

void OnChartEvent(const int id,         // ID do evento   
                  const long& lparam,   // parâmetro do evento do tipo long 
                  const double& dparam, // parâmetro do evento do tipo double 
                  const string& sparam) // parâmetro do evento do tipo string 
  { 

}

Neste modelo, nós registramos o timer ao criar um EA. Nosso timer é ativado a cada segundo. Isso significa que a função padrão OnTimer deve ser chamada uma vez por segundo.

A única linha diferente de um modelo padrão gerado pelo assistente MQL5 é a #property strict. Esta linha é necessária para o EA funcionar corretamente na MetaTrader 4. Como isso não tem um efeito significativo na MetaTrader 5, nós o adicionamos ao nosso modelo com antecedência.

Nós vamos aplicar as seguintes funções padrão:

  • OnInit: exibe os botões dos instrumentos de negociação que atendem às nossas condições no gráfico;
  • OnDeinit: remove o timer e todos os objetos gráficos criados pelo EA;
  • OnTimer: o timer deve ser usado para determinar os cliques nos objetos gráficos no gráfico criado pelo EA;
  • OnChartEvent: resposta ao clique em objetos gráficos criados no gráfico em que o EA é anexado.

A lista de símbolos que satisfazem nossas condições será armazenada no objeto do tipo CArrayString. Portanto, inclua o arquivo MQH com a descrição do objeto ao nosso EA:

#include <Arrays\ArrayString.mqh>

Nós também precisaremos do objeto do tipo CChart para trabalhar com gráficos. Vamos incluir também sua definição:

#include <Charts\Chart.mqh>

Tudo isso é feito no início do nosso modelo, fora de qualquer função, após o bloco de linhas #property.

Em seguida, nós precisamos decidir sobre a largura e a altura de todos os botões criados pelo EA. Defina esses valores em constantes especificando-os após o bloco de linhas #include:

#define BTN_HEIGHT                        (20)
#define BTN_WIDTH                         (100)

Entradas

O EA deve ser gerenciado por meio das entradas. Vamos dar uma olhada nelas para que nós possamos definir imediatamente qual funcionalidade implementaremos posteriormente no artigo:

sinput string        delimeter_01="";        // --- Configurações de filtragem ---
input bool           noSYMBmarketWath=true;  // Ocultar se não estiver no Market Watch
input bool           noSYMBwithPOS=true;     // Ocultar se houver posições
input ValueOfSpread  hide_SPREAD=spread_b1;  // Ocultar no caso de um spread
input uint           hide_PRICE_HIGH=0;      // Ocultar se o preço for maior
input uint           hide_PRICE_LOW=0;       // Ocultar se o preço for menor
input bool           hideProhibites=true;    // Ocultar se a negociação está desativada
input bool           hideClosed=true;        // Ocultar se o mercado está fechado
input StartHour      hide_HOURS=hour_any;    // Exibe se há um horário em aberto
input double         hideATRcents=0.00;      // Oculta se o ATR for menor que o valor definido em dólares
sinput string        delimeter_02="";        // --- Configurações do gráfico ---
input bool           addInfoWatch=false;     // Adiciona o gráfico ao Market Watch
input bool           viewCandle=true;        // Abre o gráfico de velas
input bool           viewVolumes=true;       // Exibe o volume de tick
input bool           showInfoSymbol=true;    // Exibe a direção do movimento
input bool           showNameSymbol=true;    // Exibe o nome do símbolo

Nós podemos notar imediatamente que duas entradas possuem um tipo personalizado. Portanto, vamos adicionar a definição desses tipos antes das entradas. Ambos os tipos personalizados são enumerações.

A enumeração ValueOfSpread define possíveis condições relativas a um valor do spread para os símbolos a serem exibidos pelo EA:

enum ValueOfSpread
  {
   spread_no,//No
   spread_b05,// > 0.05%
   spread_b1,// > 0.1% 
   spread_b15,// > 0.15% 
   spread_l15,// < 0.15% 
   spread_l1,// < 0.1% 
  }; 

Um spread superior a 0,1% do preço é considerado aumentado. Portanto, nós exibiremos apenas símbolos com um spread inferior a 0,1% por padrão. Assim, o valor do parâmetro Ocultar em caso de spread é > 0.1%. Mas se a lista de tais símbolos fornecidos pela sua corretora for muito pequena, você poderá escolher outro valor para esse parâmetro.

A enumeração StartHour contém a lista dos principais períodos, durante os quais alguns dos mercados abrem:

enum StartHour
  {
   hour_any, //Qualquer horário
   hour_9am, // 9 am
   hour_10am,// 10 am 
   hour_4pm, // 4 pm 
   hour_0am, // Meia noite
  }; 

9 am (ou qualquer outro valor) não significa que apenas símbolos abrindo exatamente no tempo especificado devem ser exibidos. Em vez disso, ele significa que os símbolos que abrem a essa hora (por exemplo, às 9:05) devem ser exibidos.

Desse modo, 4 pm significa que apenas as ações do mercado de ações dos EUA abertas às 16:30 serão exibidas.

10 am refere-se principalmente às ações do mercado russo e europeu.

9 am é o horário de abertura de alguns índices.

Finalmente, meia noite é o tempo de abertura do mercado Forex, pois funciona 24 horas por dia.

Variáveis globais

Antes de nós começarmos a trabalhar com o conteúdo das funções padrão, nós precisamos apenas declarar uma série de variáveis cujo escopo de visibilidade é o nosso EA. Vamos adicioná-los após as entradas:

// prefixo a ser adicionado aos nomes de todos os objetos gráficos criados pelo EA:
string exprefix="finder";
// array de símbolos adequados às nossas condições:
CArrayString arrPanel1;
// índice do símbolo atual no array arrPanel1:
int panel1val;
// array que é para armazenar gráficos criados pelo EA (há apenas um gráfico aqui no momento):
CChart charts[];
// array que é para armazenar ponteiros para os gráficos criados pelo EA (há apenas um ponteiro aqui no momento):
long curChartID[];

Os comentários devem esclarecer por que nós precisamos dessas variáveis.

Todas as preparações estão completas. Agora nós estamos prontos para começar a desenvolver o EA. Mas primeiro, vamos dar uma olhada no resultado:

//+------------------------------------------------------------------+
//|                                                     _finder.mq5  |
//|                        Copyright 2018, MetaQuotes Software Corp. | 
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Klymenko Roman (needtome@icloud.com)"
#property link      "https://logmy.net"
#property version   "1.00"
#property strict

#include <Arrays\ArrayString.mqh>
#include <Charts\Chart.mqh>

#define BTN_HEIGHT                        (20)
#define BTN_WIDTH                         (100)

enum ValueOfSpread
  {
   spread_no,//No
   spread_b05,// > 0.05 %
   spread_b1,// > 0.1 %
   spread_b15,// > 0.15 %
   spread_l15,// < 0.15 %
   spread_l1,// < 0.1 %
  }; 
enum StartHour
  {
   hour_any,//Qualquer horário
   hour_9am,// 9 am
   hour_10am,// 10 am 
   hour_4pm,// 4 pm 
   hour_0am,// Meia noite
  }; 

input bool           noSYMBmarketWath=true; // Ocultar se não estiver no Market Watch
input bool           noSYMBwithPOS=true;    // Ocultar se houver posições
input ValueOfSpread  hide_SPREAD=spread_b1; // Ocultar no caso de um spread
input uint           hide_PRICE_HIGH=0;     // Ocultar se o preço for maior (0 - não ocultar)
input uint           hide_PRICE_LOW=0;      // Ocultar se o preço for menor (0 - não ocultar)
input bool           hideProhibites=true;   // Ocultar se a negociação está desativada
input StartHour      hide_HOURS=hour_any;   // Exibe símbolos que abrem em
input bool           viewCandle=true;       // Abre o gráfico de velas

// prefixo a ser adicionado aos nomes de todos os objetos gráficos criados pelo EA:
string exprefix="finder";
// array de símbolos adequados às nossas condições:
CArrayString arrPanel1;
// índice do símbolo atual no array arrPanel1:
int panel1val;
// array que é para armazenar gráficos criados pelo EA (há apenas um gráfico aqui no momento):
CChart charts[];
// array que é para armazenar ponteiros para os gráficos criados pelo EA (há apenas um ponteiro aqui no momento):
long curChartID[];

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- cria um timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }
  
//+------------------------------------------------------------------+
//| Função de desinicialização do Expert                             |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(reason!=REASON_CHARTCHANGE){
      ObjectsDeleteAll(0, exprefix);
   }
   EventKillTimer();
  }

//+------------------------------------------------------------------+
//| Funçao timer                                                     |
//+------------------------------------------------------------------+
void OnTimer()
  {
  }

void OnChartEvent(const int id,         // ID do evento   
                  const long& lparam,   // parâmetro do evento do tipo long 
                  const double& dparam, // parâmetro do evento do tipo double 
                  const string& sparam) // parâmetro do evento do tipo string 
  { 

}

Função de filtragem dos símbolos

Nós vamos começar a partir da função que exibe os botões de símbolos que atendem às nossas condições no gráfico. Vamos nomear esta função start_symbols. A função é chamada dentro da função OnInit. Como resultado, a função OnInit assume sua forma final:

//+------------------------------------------------------------------+
//| Função de inicialização do Expert                                |
//+------------------------------------------------------------------+
int OnInit()
  {
   start_symbols();

//--- cria um timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

Por que nós precisamos de uma função separada se tudo puder ser implementado dentro da OnInit? Tudo é simples. Nós chamaremos esta função não somente ao iniciar o EA, mas também ao pressionar o tecla R. Assim, nós poderemos atualizar facilmente a lista de caracteres sem ter que remover o EA do gráfico e reabri-lo.

Como o spread está mudando constantemente, nós teremos que atualizar a lista de símbolos com bastante frequência. Além disso, a presença de posições em aberto em certos símbolos também está se alterando. Portanto, antes de usar o EA iniciado anteriormente, não se esqueça de atualizar a lista de símbolos (pressionando R) para ver os dados atuais.

Vamos dar uma olhada na função start_symbols. Ela também serve como um wrapper para o lançamento de outras funções:

void start_symbols(){
   // define o índice do símbolo atual na lista para zero (o primeiro símbolo no array):
   panel1val=0;
   // prepara a lista de símbolos:
   prepare_symbols();
   // remove os botões de símbolos criados anteriormente do gráfico:
   ObjectsDeleteAll(0, exprefix);
   // exibe a lista de símbolos:
   show_symbols();
   // atualiza o gráfico para ver as alterações:
   ChartRedraw(0);
}

Nós encontramos mais duas funções personalizadas: prepare_symbols e show_symbols. O primeiro forma o array de símbolos que se ajustam às nossas condições. O segundo exibe os botões desses símbolos no gráfico onde o EA está sendo executado.

Exibir os botões no gráfico é simples. Primeiro, nós encontramos as coordenadas X e Y usadas para exibir um botão para que ele não se sobreponha a outros botões. Então nós devemos exibi-lo:

void show_symbols(){
   
   // inicializa as variáveis para definir as coordenadas X e Y
   int btn_left=0;
   int btn_line=1;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   
   // exibe o botão no gráfico para cada símbolo no array
   // escreve o nome do símbolo no botão
   for( int i=0; i<arrPanel1.Total(); i++ ){
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      
      ObjectCreate(0, exprefix+"btn"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TEXT,arrPanel1.At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
      
      
      btn_left+=BTN_WIDTH;
   }
   
}

Como resultado, os símbolos que atendem às nossas condições aparecerão no gráfico:

Exibindo os botões de símbolo no gráfico

Agora, vamos nos concentrar em formar as condições usadas para selecionar os símbolos (a função prepare_symbols). Primeiro, vamos adicionar todos os símbolos à lista:

void prepare_symbols(){
   // variável para armazenamento temporário do nome do símbolo
   string name;
   // variável para armazenar as últimas cotações do símbolo
   MqlTick lastme;
   
   // redefine o array de símbolos se ela contiver algum valor
   arrPanel1.Resize(0);
   
   // forma o array temporário tmpSymbols
   // ele conterá todos os símbolos disponíveis
   CArrayString tmpSymbols;
   for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
      tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
   }
   
   // as condições devem ser verificadas aqui
   // e o símbolo deve ser incluído na lista de símbolos
   // se couber nas condições
   for( int i=0; i<tmpSymbols.Total(); i++ ){
      name=tmpSymbols[i];
      
      // remove espaços excessivos do nome do símbolo,
      // já que nós não sabemos com certeza de onde veio
      StringTrimLeft(name);
      StringTrimRight(name);
      if( !StringLen(name) ){
         continue;
      }
      
      // a filtragem principal dos símbolos é executada mais adiante
      // ...

      
      // se um símbolo se encaixa em todas as nossas condições, ele é adicionado à lista
      arrPanel1.Add(name);
   }
}

Primeiro, coloque todos os símbolos em um array temporário. A filtragem inicial já ocorre neste ponto pela entrada Ocultar símbolos ausentes no painel do Market Watch.

Colocar símbolos nos arrays temporários não é necessário. Em vez disso, nós podemos colocar os símbolos necessários na lista principal. Mas, nesse caso, nós precisaríamos reescrever o código, por exemplo, caso precisemos adicionar uma entrada adicionando apenas os símbolos a serem exibidos na lista. Em outras palavras, nós precisaríamos usar símbolos personalizados na ordem necessária, em vez de usar todos os símbolos oferecidos pela corretora.

Pelas mesmas razões, o nome do símbolo é, primeiramente, limpo de espaços no loop que enumera todos os símbolos do array temporário. Se você quiser implementar a entrada descrita acima, não será possível filtrar a entrada personalizada.

Agora, vamos classificar os símbolos obtidos com base nas entradas que nós temos. Os blocos de código fornecidos abaixo são adicionados abaixo da linha de comentário a filtragem principal de símbolos é realizada em da função prepare_symbols (no loop de adicionar símbolos à lista).

Ocultar símbolos com posições:

      // Ocultar símbolos com posições
      bool isskip=false;
      if( noSYMBwithPOS ){
         // ver a lista de todas as posições abertas
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // pula se houver uma posição para o símbolo atual
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }
      }

Primeiro, ele verifica se existe uma posição em um símbolo. Se não houver posição, ele verifica se há uma ordem limitada. Se uma posição em aberto ou uma ordem limitada estiver presente, ele pula o símbolo.

Ocultar símbolos com um spread:

      // se as entradas usando o valor do preço atual do símbolo estiverem ativas,
      // tenta obter os valores atuais
      if(hide_PRICE_HIGH>0 || hide_PRICE_LOW>0 || hide_SPREAD>0 ){
         SymbolInfoTick(name, lastme);
         if( lastme.bid==0 ){
            Alert("Failed to get BID value. Some filtration functions may not work.");
         }
      }
      if(hide_SPREAD>0 && lastme.bid>0){
         switch(hide_SPREAD){
            // se o spread atual exceder 0.05% do preço, pula o símbolo
            case spread_b05:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.05 ){
                  isskip=true;
               }
               break;
            // se o spread atual exceder 0.1% do preço, pula o símbolo
            case spread_b1:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.1 ){
                  isskip=true;
               }
               break;
            // se o spread atual exceder 0.15% do preço, pula o símbolo
            case spread_b15:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 > 0.15 ){
                  isskip=true;
               }
               break;
            // se o spread atual for menor que 0.15% do preço, pula o símbolo
            case spread_l15:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.15 ){
                  isskip=true;
               }
               break;
            // se o spread atual for menor que 0.1% do preço, pula o símbolo
            case spread_l1:
               if( ((SymbolInfoInteger(name, SYMBOL_SPREAD)*SymbolInfoDouble(name, SYMBOL_POINT))/lastme.bid)*100 < 0.1 ){
                  isskip=true;
               }
               break;
         }
      }
      if(isskip){
         continue;
      }

Quanto menor o spread, melhor. Deste ponto de vista, é melhor trabalhar com símbolos com um spread inferior a 0.05% do preço. Nem todos as corretoras oferecem condições tão boas, especialmente quando estas negociam no mercado de ações.

Ocultar símbolos com o preço maior (0 - não oculta):

      // Ocultar símbolos com o preço maior (0 - não oculta)
      if(hide_PRICE_HIGH>0 && lastme.bid>0 && lastme.bid>hide_PRICE_HIGH){
         continue;
      }

Ocultar símbolos com o preço menor (0 - não oculta):

      if(hide_PRICE_LOW>0 && lastme.bid>0 && lastme.bid<hide_PRICE_LOW){
         continue;
      }

Ocultar símbolos indisponíveis para negociação:

      if(hideProhibites){
         // pula se o volume de posição mínima em um símbolo é 0
         if( SymbolInfoDouble(name, SYMBOL_VOLUME_MIN)==0 ) continue;
         // pula se as posições de abertura estão desativadas em um símbolo
         if(SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED || SymbolInfoInteger(name, SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_CLOSEONLY ){
            continue;
         }
      }

Naturalmente, é possível ocultar a negociação dos símbolos se estiver desativado para quaisquer condições nas entradas. Mas você ainda pode querer ter esses símbolos na lista. É por isso que nós adicionamos essa entrada.

Exibir símbolos abrindo somente em uma hora especificada:

      // obtém o dia atual na variável curDay
      MqlDateTime curDay;
      TimeCurrent(curDay);
      MqlDateTime curDayFrom;
      datetime dfrom;
      datetime dto;
      // se houver uma limitação no horário de abertura do mercado 
      // e nós conseguimos obter o horário de abertura da ação atual para o dia atual, então...
      if( hide_HOURS!=hour_any && SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto)){
         TimeToStruct(dfrom, curDayFrom);
         if(hide_HOURS==hour_9am && curDayFrom.hour != 9){
            continue;
         }
         if(hide_HOURS==hour_10am && curDayFrom.hour != 10){
            continue;
         }
         if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){
            continue;
         }
         if(hide_HOURS==hour_0am && curDayFrom.hour != 0){
            continue;
         }
      }

Ocultar se o mercado está fechado. Se você iniciar o EA no domingo, dificilmente você deseja analisar um mercado de ações. Provavelmente, você deseja selecionar símbolos disponíveis no domingo, como o índice TA25 ou criptomoedas. Esse parâmetro de entrada nos permite fazer isso.

Naturalmente, seria possível apenas exibir símbolos negociados hoje em vez de introduzir uma entrada separada. Mas e se for domingo e ainda quisermos nos preparar para o próximo dia de negociação, selecionando ações adequadas, etc? Vamos implementar essa habilidade como um parâmetro de entrada.

Para definir se o mercado estará aberto hoje, nós precisaremos da função SymbolInfoSessionTrade. Se retornar false, então, aparentemente, o símbolo não está disponível para negociação hoje. Para evitar chamar a função duas vezes, nós precisaremos reescrever o código mostrando apenas os símbolos abertos em:

      MqlDateTime curDay;
      TimeCurrent(curDay);
      MqlDateTime curDayFrom;
      datetime dfrom;
      datetime dto;
      
      bool sessionData=SymbolInfoSessionTrade(name, (ENUM_DAY_OF_WEEK) curDay.day_of_week, 0, dfrom, dto);

      // ocultar um símbolo se o mercado estiver fechado hoje
      if( hideClosed && !sessionData ){
         continue;
      }
      
      // Exibe apenas símbolos aberto em
      // obtém o dia atual para a variável curDay
      // se houver uma limitação no tempo de abertura do mercado, 
      // e nós conseguimos obter o horário de abertura da ação atual para o dia atual, então...
      if( hide_HOURS!=hour_any && sessionData){
         TimeToStruct(dfrom, curDayFrom);
         if(hide_HOURS==hour_9am && curDayFrom.hour != 9){
            continue;
         }
         if(hide_HOURS==hour_10am && curDayFrom.hour != 10){
            continue;
         }
         if(hide_HOURS==hour_4pm && curDayFrom.hour != 16){
            continue;
         }
         if(hide_HOURS==hour_0am && curDayFrom.hour != 0){
            continue;
         }
      }

Ocultar se o ATR for menor que o valor definido em dólares. Se você negocia no intradiário e espera o preço se movimentar por pelo menos 50-90 centavos, é improvável que você precise de símbolos que se movimentem estatisticamente por não mais do que 30 centavos por dia. Esse parâmetro nos permite classificar esses símbolos especificando o tamanho mínimo necessário do movimento diário do preço:

      // Ocultar se o ATR for menor que o valor definido em dólares
      if(hideATRcents>0){
         MqlRates rates[];
         ArraySetAsSeries(rates, true);
         double atr;
         if(CopyRates(name, PERIOD_D1, 1, 5, rates)==5){
            atr=0;
            for(int j=0; j<5; j++){
               atr+=rates[j].high-rates[j].low;
            }
            atr/=5;
            if( atr>0 && atr<hideATRcents ){
               continue;
            }
         }
      }

Eu acho que isso é suficiente para a filtragem completa pela maioria dos parâmetros. Mas se você precisar de outras condições de filtragem, você pode sempre adicioná-las dentro do loop da função prepare_symbols.

Abertura dos gráficos

Nós já aprendemos a desenhar botões de símbolos adequados às nossas condições. Nós poderíamos parar neste ponto e abrir gráficos de símbolos obtidos manualmente. Mas isso é inconveniente. Felizmente, nós podemos simplificar o processo e abrir um gráfico necessário ao clicar no botão.

Para permitir que a ação ocorra ao clicar no botão, essa ação deve ser descrita na função padrão da linguagem MQL OnChartEvent. A função intercepta qualquer evento do gráfico. Em outras palavras, ela é chamada em qualquer evento que esteja acontecendo no gráfico.

A função OnChartEvent possui quatro entradas. O primeiro parâmetro (id) contém o ID de um evento que foi atualmente interceptado pela função OnChartEvent. Para entender que a função OnChartEvent foi chamada exatamente depois de clicar em um botão do gráfico, compare o valor do parâmetro com o aquele necessário.

O evento de clique do botão tem o ID CHARTEVENT_OBJECT_CLICK. Assim, vamos adicionar o seguinte código à função OnChartEvent, a fim de lidar com os cliques de botão no gráfico:

void OnChartEvent(const int id,         // ID do evento   
                  const long& lparam,   // parâmetro do evento do tipo long 
                  const double& dparam, // parâmetro do evento do tipo double 
                  const string& sparam) // parâmetro do evento do tipo string 
  { 
   switch(id){
      case CHARTEVENT_OBJECT_CLICK:
         // código executado ao clicar no botão
         break;
   }

}

Como nós podemos entender exatamente qual botão foi pressionado no gráfico? O segundo parâmetro da função OnChartEvent (sparam) pode nos ajudar com isso. Para o evento CHARTEVENT_OBJECT_CLICK, ele contém o nome do botão clicado por um usuário. Nós apenas temos que comparar este nome com o gerado pelo nosso EA. Se este é o botão do EA, abra o gráfico de símbolos necessário. O nome do símbolo aberto é retirado do texto escrito no botão. Como resultado, nós temos o seguinte código que deve ser colocado dentro da condição case CHARTEVENT_OBJECT_CLICK:

         // se o nome do botão contém uma linha presente em todos os objetos gráficos
         // criado pelo seu EA, então...
         if( StringFind(sparam, exprefix+"btn")>=0 ){
            // coloca o índice do botão atual
            // (posição de um símbolo atual na lista de símbolos) para a variável panel1val
            string tmpme=sparam;
            StringReplace(tmpme, exprefix+"btn", "");
            panel1val=(int) tmpme;
            
            // abre o gráfico de símbolos atual
            showcharts(ObjectGetString(0,sparam,OBJPROP_TEXT));
         }

O gráfico de um símbolo selecionado é aberto usando a função personalizada showcharts. A função não abre apenas o gráfico necessário, mas também:

  • fecha os gráficos previamente abertos pelo EA;
  • adiciona um símbolo ao painel Market Watch (Observação do Mercado) se não estiver lá;
  • se necessário, ele alterna o gráfico para o modo de vela;
  • altera a escala do gráfico (eu adicionei esse recurso simplesmente porque eu uso a escala personalizada em vez da padrão).
void showcharts(string name){
   // se os gráficos já estiverem abertos, fecha-os
   closecharts();
   
   // Adiciona um símbolo ao painel "Market Watch" se não estiver lá
   // e se a entrada "Adicionar o gráfico ao Market Watch" for "true"
   if( addInfoWatch ){
      SymbolSelect(name, true);
   }
   
   // abre o gráfico e coloca o ID do gráfico no array curChartID
   curChartID[ArrayResize(curChartID,ArraySize(curChartID)+1)-1]=charts[(uchar) ArrayResize(charts,ArraySize(charts)+1)-1].Open( name, PERIOD_D1 );
   
   // se a entrada "Abrir gráficos de vela" for "true",
   // muda o gráfico para o modo de vela
   if(viewCandle){
      ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_MODE, CHART_CANDLES);
   }
   // se a entrada "Exibir volume de tick" for 'true',
   // exibe o volume de tick
   if(viewVolumes){
      ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SHOW_VOLUMES, CHART_VOLUME_TICK);
   }
   // altera a escala do gráfico para a mais conveniente
   ChartSetInteger( curChartID[ArraySize(curChartID)-1], CHART_SCALE, 2);
      
   // espera por um terço de segundo até que todas as mudanças sejam implementadas
   Sleep(333);
   // atualiza o gráfico aberto para fazer todas as alterações
   ChartRedraw(curChartID[ArraySize(curChartID)-1]);
   
}
A função closecharts fecha todos os gráficos abertos anteriormente pelo EA. Seu código é muito simples:
void closecharts(){
   // se o array de gráficos abertos pelo EA não estiver vazio, então...
   if(ArraySize(charts)){
      // fecha todos os gráficos consistentemente
      for( int i=0; i<ArraySize(charts); i++ ){
         charts[i].Close();
      }
      // limpa o array de gráficos
      ArrayFree(charts);
   }
   // se o array de IDs do gráfico aberto pelo EA não estiver vazio, desmarque-o
   if(ArraySize(curChartID)){
      ArrayFree(curChartID);
   }
}

Exibindo informações adicionais sobre o símbolo

Seria bom não apenas abrir um gráfico de símbolos, mas também exibir dados auxiliares nele, como descrição (algumas corretoras fazem nomes de símbolo tão incompreensíveis que parecem ser algum tipo de cifra) e direção de movimento de símbolo para o último dia e hora.

Esta informação pode ser exibida usando objetos gráficos. No entanto, nós usaremos uma abordagem mais simples. Nós vamos exibir as informações nos comentários do gráfico.

Para fazer isso, é adicionado o seguinte código à função showcharts antes de chamar a função Sleep:

   // mostra informações adicionais no gráfico
   string msg="";
   if(showNameSymbol){
      StringAdd(msg, getmename_symbol(name)+"\r\n");
   }
   if(showInfoSymbol){
      StringAdd(msg, getmeinfo_symbol(name, false)+"\r\n");
   }
   if( StringLen(msg)>0 ){
      ChartSetString(curChartID[ArraySize(curChartID)-1], CHART_COMMENT, msg);
   }

Se a entrada showNameSymbol for true, chama a função getmename_symbol que retorna a linha com um nome do símbolo. Se a entrada showInfoSymbol for true, chama a função getmeinfo_symbol que retorna a linha com um nome do símbolo:

string getmename_symbol(string symname){
   return SymbolInfoString(symname, SYMBOL_DESCRIPTION);
}
string getmeinfo_symbol(string symname, bool show=true){
   MqlRates rates2[];
   ArraySetAsSeries(rates2, true);
   string msg="";

   if(CopyRates(symname, PERIOD_D1, 0, 1, rates2)>0){
      if(show){
         StringAdd(msg, (string) symname+": ");
      }
      StringAdd(msg, "D1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2) +"%");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2) +"%");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   if(CopyRates(symname, PERIOD_H1, 0, 1, rates2)>0){
      StringAdd(msg, ", H1 ");
      if( rates2[0].close > rates2[0].open ){
         StringAdd(msg, "+"+DoubleToString(((rates2[0].close-rates2[0].open)/rates2[0].close)*100, 2)+"% (+"+DoubleToString(rates2[0].close-rates2[0].open, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
      }else{
         if( rates2[0].close < rates2[0].open ){
            StringAdd(msg, "-"+DoubleToString(((rates2[0].open-rates2[0].close)/rates2[0].close)*100, 2)+"% (-"+DoubleToString(rates2[0].open-rates2[0].close, (int) SymbolInfoInteger(symname, SYMBOL_DIGITS))+" "+SymbolInfoString(symname, SYMBOL_CURRENCY_PROFIT)+")");
         }else{
            StringAdd(msg, "0%");
         }
      }
   }
   
   return msg;
}

Como resultado, nós veremos as seguintes informações em um gráfico recém-aberto:

Exibindo informações adicionais sobre o símbolo

Gerenciando o EA a partir do teclado

Enquanto ainda nós estamos na função OnChartEvent, vamos adicionar uma resposta para pressionar algumas teclas lá:

  • ao pressionar R, atualiza a lista de símbolos que se ajustam às nossas condições:
  • ao pressionar X, remove o EA do gráfico.

O evento tendo o ID CHARTEVENT_KEYDOWN lida com as teclas pressionadas. O código de uma tecla pressionada é passado no parâmetro já mencionado sparam. Portanto, basta nós adicionarmos a seguinte condição ao operador switch:

      case CHARTEVENT_KEYDOWN:
         switch((int) sparam){
            case 45: //x
               ExpertRemove();
               break;
            case 19: //r
               start_symbols();
               break;
         }
         break;

Como você pode ver, ao pressionar a tecla R, nós simplesmente chamamos a funçãostart_symbols criada anteriormente.

Adicionando a navegação ao gráfico

Nós já aprendemos não apenas como mostrar os botões de símbolos, mas também como abrir gráficos dos símbolos necessários ao clicar nesses botões. No entanto, nós ainda não terminamos aqui. Nosso utilitário ainda é inconveniente de usar. Depois de abrir um gráfico de símbolos, nós precisamos fechá-lo manualmente e clicar no botão do próximo gráfico. Isso deve ser feito toda vez que nós precisarmos passar para o próximo símbolo, tornando o trabalho bastante tedioso. Vamos adicionar botões de navegação da lista de símbolos para abrir os gráficos.

Nós só adicionaremos três botões: para mover para o próximo gráfico, para mover para o gráfico anterior e fechar o gráfico.

Só resta decidir como implementá-los. Nós podemos adicionar os botões diretamente na função showcharts ao criar um novo gráfico. Mas o número de botões pode aumentar no futuro. Criar os botões e outros objetos gráficos pode desacelerar a abertura do gráfico, o que é indesejável.

Portanto, nós vamos criar os botões na função padrão OnTimer. Nós verificaremos periodicamente se um gráfico com o EA está aberto e se o gráfico está aberto, se ele possui botões. Se não houver botões, eles serão criados:

void OnTimer()
  {
   // se o array de ID dos gráficos abertos contiver valores, então...
   uchar tmpCIDcnt=(uchar) ArraySize(curChartID);
   if(tmpCIDcnt>0 ){
      // se o último ID no array não estiver corrompido, então...
      if(curChartID[tmpCIDcnt-1]>0){
         // se o gráfico com o ID não tiver botões, crie-os
         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }
      }
   }
   
  }

Os botões no gráfico são criados na função personalizada createBTNS. Seu código é muito simples:

void createBTNS(long CID){
   ObjectCreate(CID, exprefix+"_p_btn_prev", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YDISTANCE,90); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_prev",OBJPROP_TEXT,"Prev chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_prev",OBJPROP_SELECTABLE,false); 
      
   ObjectCreate(CID, exprefix+"_p_btn_next", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YDISTANCE,65); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_next",OBJPROP_TEXT,"Next chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_next",OBJPROP_SELECTABLE,false); 
      
   ObjectCreate(CID, exprefix+"_p_btn_close", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XDISTANCE,110); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YDISTANCE,40); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_XSIZE,BTN_WIDTH); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_YSIZE,BTN_HEIGHT); 
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_CORNER,CORNER_LEFT_LOWER); 
   ObjectSetString(CID,exprefix+"_p_btn_close",OBJPROP_TEXT,"Close chart");
   ObjectSetInteger(CID,exprefix+"_p_btn_close",OBJPROP_SELECTABLE,false); 
   
   // atualiza o gráfico para ver as mudanças implementadas
   ChartRedraw(CID);
}

Como resultado, um novo gráfico assume o seguinte formato:

Botões de navegação da lista de símbolos

Adicionando uma resposta ao botão pressionado

Até agora, os botões adicionados ao gráfico são apenas uma decoração. Nada acontece ao pressioná-los. Vamos instruí-los sobre como responder ao pressionamento.

Infelizmente, a função padrão OnChartEvent não é de nenhuma ajuda para nós, pois ela reage apenas aos eventos que ocorreram em um gráfico na qual o EA foi lançado, enquanto os botões são adicionados a um novo gráfico.

Talvez haja maneiras mais convenientes. Eu criei apenas uma maneira de responder às mudanças que ocorreram em outro gráfico. Envolve a função padrão OnTimer. Se o gráfico apresentar os botões, nós verificaremos se alguns deles estão pressionados. Se sim, uma ação necessária é executada. Como resultado, a condição:

         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }

...é reescrito da seguinte forma:

         if(ObjectFind(curChartID[tmpCIDcnt-1], exprefix+"_p_btn_next")<0){
            createBTNS(curChartID[tmpCIDcnt-1]);
         }else{
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_prev",OBJPROP_STATE)==true ){
               prevchart();
               return;
            }
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_next",OBJPROP_STATE)==true ){
               nextchart();
               return;
            }
            if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_close",OBJPROP_STATE)==true ){
               closecharts();
               return;
            }
         }

Ao pressionar o botão Prev chart, chama a função prevchart. Ao pressionar o botão Next chart, chama a função nextchart. Ao pressionar o botão Close chart, chama a função closecharts mencionada acima. As funções prevchart e nextchart são semelhantes:

void nextchart(){
   // se uma lista de símbolos contiver o próximo símbolo, o seu gráfico é aberto
   // caso contrário, fecha o gráfico atual
   if(arrPanel1.Total()>(panel1val+1)){
      panel1val++;
      showcharts(arrPanel1[panel1val]);
   }else{
      closecharts();
   }
}
void prevchart(){
   // se uma lista de símbolos contiver o símbolo anterior, o seu gráfico é aberto
   // caso contrário, fecha o gráfico atual
   if(arrPanel1.Total()>(panel1val-1) && (panel1val-1)>=0){
      panel1val--;
      showcharts(arrPanel1[panel1val]);
   }else{
      closecharts();
   }
}

Conclusão

É isso aí. Como você pode ver, a quantidade do código geral não é esmagadora, enquanto a vantagem é evidente. Nós não precisamos mais abrir gráficos e fechá-los repetidamente. Em vez disso, nós podemos clicar no botão necessário e tudo é feito para nós.

Claro, pode haver mais maneiras de melhorar nosso EA. Mas na sua forma atual, já é um produto completo que simplifica significativamente a seleção de ações.

Movendo o utilitário para a MQL4

Agora, vamos tentar mover nosso utilitário para a MQL4. Surpreendentemente, nós precisamos apenas reescrever um único bloco de código. Isso levará cerca de cinco minutos.

Primeiro, crie um novo EA no MetaEditor 4. Depois disso, mova o código-fonte do EA em MQL5 para ele.

Compile o EA. A tentativa termina em um erro, é claro. Mas, como resultado, nós temos uma lista de erros para corrigir. Existem apenas três deles:

  • 'PositionsTotal' - função não definida
  • 'PositionGetSymbol' - função não definida
  • 'OrderGetTicket' - função não definida

Clique duas vezes no primeiro erro para passar para a linha apropriada do EA.

'PositionsTotal' - função não definida. O erro é detectado no seguinte bloco do código da função prepare_symbols:

         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // pula se houver uma posição no símbolo atual
            if(PositionGetSymbol(ti) == name ){
               isskip=true;
               break;
            }
         }
         if(!isskip){
            int cntMyPosO=OrdersTotal();
            if(cntMyPosO>0){
               for(int ti=cntMyPosO-1; ti>=0; ti--){
                  ulong orderTicket=OrderGetTicket(ti);
                  if( OrderGetString(ORDER_SYMBOL) == name ){
                     isskip=true;
                     break;
                  }
               }
            }
         }

Uma das diferenças significativas entre as linguagens MQL4 e MQL5 é lidar com posições e ordens. Portanto, nós devemos reescrever o bloco de código da seguinte maneira para permitir que o EA funcione corretamente na MetaTrader 4:

         int cntMyPos=OrdersTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            if(OrderSelect(ti,SELECT_BY_POS,MODE_TRADES)==false) continue;
            if(OrderSymbol() == name ){
               isskip=true;
               break;
            }
         }

Como a MQL4 não tem diferenciação entre posições e ordens, o código resultante é muito menor.

Os erros restantes são corrigidos automaticamente desde que ocorram no bloco de código que corrigimos.

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

Arquivos anexados |
_finder.mq5 (40.88 KB)
_finder4.mq4 (40.09 KB)
_finder.ex5 (56.21 KB)
_finder4.ex4 (30.97 KB)
Padrões de reversão: Testando o padrão 'Ombro-Cabeça-Ombro' Padrões de reversão: Testando o padrão 'Ombro-Cabeça-Ombro'

Este artigo é uma continuação do artigo "Padrões de reversão: Testando o padrão 'topo/fundo duplo'" publicado anteriormente. Agora consideraremos o padrão de reversão O-C-O, o bem conhecido Ombro-Cabeça-Ombro, compararemos o desemprenho de dois padrões e, por último, tentaremos combinar o trading de dois padrões num só sistema de negociação.

Aplicando a teoria da probabilidade na negociação de gaps Aplicando a teoria da probabilidade na negociação de gaps

Neste artigo, nós aplicaremos a teoria da probabilidade e métodos da estatística matemática para criar e testar estratégias de negociação. Nós também veremos o risco de negociação ótimo usando as diferenças entre o preço e o passeio aleatório. Está provado que, se os preços se comportarem como um passeio aleatório de deslocamento de zero (sem tendência direcional), então a negociação com lucro é impossível.

Como criar e testar símbolos de ativos MOEX personalizados no MetaTrader 5 Como criar e testar símbolos de ativos MOEX personalizados no MetaTrader 5

O artigo descreve a criação de um símbolo de ativo personalizado da bolsa de valores usando a linguagem MQL5, em particular, descreve o uso de cotações no popular site "Finam". Outra opção considerada neste artigo é a possibilidade de trabalhar com um formato arbitrário de arquivos de texto, usados na criação do símbolo personalizado. Isso permite trabalhar com quaisquer símbolos financeiros e fontes de dados, depois de criar um símbolo personalizado, podemos usar todos os recursos do Testador de Estratégia do MetaTrader 5 a fim de testarmos os algoritmos de negociação para os instrumentos da bolsa.

Otimização separada de uma estratégia em condições de tendência e lateralizada Otimização separada de uma estratégia em condições de tendência e lateralizada

O artigo considera a aplicação do método de otimização separada durante várias condições de mercado. A otimização separada significa definir os parâmetros ideais do sistema de negociação, otimizando para uma tendência de alta e tendência de baixa separadamente. Para reduzir o efeito de sinais falsos e melhorar a lucratividade, os sistemas são flexíveis, o que significa que eles têm um conjunto específico de configurações ou dados de entrada, o que se justifica porque o comportamento do mercado está em constante alteração.