Utilitário de seleção e navegação em MQL5 e MQL4: Adição da busca automática de padrões e exibição dos símbolos detectados

Roman Klymenko | 4 março, 2019

Introdução

Muitas estratégias de negociação exigem uma análise constante dos símbolos ao procurar vários padrões de entrada no mercado. Embora a busca e análise dos símbolos possa ser útil em termos de obtenção das informações sobre o mercado, às vezes, você pode querer que uma lista de símbolos com um parâmetro necessário seja simplesmente exibida para você. Neste artigo, nós tentaremos desenvolver essa ferramenta para alguns padrões de negociação no intradiário.

Mais especificamente, hoje nós expandiremos os recursos de nosso utilitário adicionando a classificação automática dos símbolos por parâmetros específicos. Para fazer isso, nós criaremos uma série de guias para a busca dos símbolos que atualmente apresentam determinados padrões de negociação: curvas parabólicas, níveis de ar (faixas de lateralização), intervalos etc. Nós também aprenderemos como adicionar guias personalizadas, desde que você conheça os fundamentos da programação MQL.

Como no artigo anterior, nosso utilitário funcionará tanto em MQL4 quanto em MQL5. Olhando para o futuro, devo dizer que abrir as guias de classificação automática em MQL5 é mais lento que em MQL4. Isso acontece quando o símbolo solicitado não possui um histórico até a profundidade desejada. Nesses casos, a MetaTrader 5 solicita o histórico do servidor de negociação e cria os períodos gráficos necessários.

Portanto, se nada acontecer quando você clicar na guia, ou se todos os botões desaparecerem, não entre em pânico e apenas espere. Em quinze vinte segundos, os símbolos necessários serão exibidos. Ainda é mais rápido do que visualizar os gráficos de centenas de símbolos manualmente. Por outro lado, na MetaTrader 4, você precisa fornecer, independentemente, o histórico dos símbolos e períodos gráficos necessários, o que também requer tempo e atenção para tais ninharias.

Além disso, se o seu computador tiver uma pequena quantidade de RAM (ou se você estiver trabalhando com uma máquina virtual, que tem uma quantidade limitada de RAM, como 1 GB), então, em MQL5, a operação do EA pode ser interrompida pelo erro de memória insuficiente, abrindo as guias de classificação automática. O MQL4 não tem esse problema, pois todo o histórico do período gráfico é carregado de forma independente e em diferentes profundidades. No MQL5, o problema pode ser resolvido limitando o parâmetro "Máx. Barras no gráfico".

Adicionando a funcionalidade às guias de classificação automática

Primeiro, vamos definir como nós podemos adicionar a guia de classificação automática ao utilitário. Para fazer isso, nós precisamos entender como o mecanismo dessas guias é projetado por dentro.

Nós já implementamos a capacidade de adicionar guias no artigo anterior. Como você deve se lembrar, nós fazemos isso pelo loop. Os nomes das guias da área de trabalho são salvos no array. Os nomes das guias de classificação automática também são salvos em um array separado:

string panelNamesAddon[9]={"Air Level", "Parabolic", "Gap", "4 weeks Min/Max", "365 days Min/Max", "Round levels", "Mostly Up/Down", "All time high/low", "High=Close"};

Em outras palavras, para adicionar uma guia personalizada, nós precisamos aumentar o tamanho do array por 1 e adicionar o nome da guia ao final da lista de guias. Depois disso, a nova guia aparece no utilitário.

A função show_panel_buttons é responsável por exibir as guias. Seu código foi modificado, todas as guias de classificação automática são exibidas além das guias da área de trabalho:

void show_panel_buttons(){
   int btn_left=0;
   // define a coordenada máxima x possível, na qual as guias podem ser exibidas.
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // se a coordenada de início do novo botão exceder a máxima possível,
      // move para a nova linha.
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // se as guias "homework" exibirem os símbolos, adiciona o número delas
      // para o nome da guia
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // exibe os botões da guia
      ObjectCreate(0, exprefix+"panels"+(string) i, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_BGCOLOR,clrSilver); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // se a guia do botão estiver ativa no momento,
      // faz ela ser pressionada
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }
   // Se a exibição das guias de classificação automática for permitida nos parâmetros de entrada, exibe-as
   if(useAddonsLevels){
      for( int i=0; i<ArraySize(panelNamesAddon); i++ ){
         if( btn_left>btn_right-BTN_WIDTH ){
            btn_line++;
            btn_left=0;
         }
         tmpName=panelNamesAddon[i];
         // Se a guia é chamada de Gap, exibe o valor do parâmetro de entrada atual
         // definindo a porcentagem de gap lá
         if(tmpName=="Gap"){
            StringAdd(tmpName, " ("+(string) gap_min+"%)");
         }
         
         ObjectCreate(0, exprefix+"panels"+(string) (i+ArraySize(panelNames)), OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XDISTANCE,btn_left); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_FONTSIZE,8); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_COLOR,clrBlack); 
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_BGCOLOR,clrLightSteelBlue); 
         ObjectSetString(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_TEXT,tmpName);
         ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_SELECTABLE,false);
         if( cur_panel == i+ArraySize(panelNames) ){
            ObjectSetInteger(0,exprefix+"panels"+(string) (i+ArraySize(panelNames)),OBJPROP_STATE, true);
         }
         
         btn_left+=BTN_WIDTH;
      }
   }

   // exibe o comentário se definido:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      if(from_txt){
         StringAdd(tmpCMT, ", from txt");
      }
      ObjectCreate(0, exprefix+"title", OBJ_EDIT, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XDISTANCE,btn_left+11); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_XSIZE,133); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_COLOR,clrGold); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BGCOLOR,clrNONE); 
      ObjectSetInteger(0,exprefix+"title",OBJPROP_BORDER_COLOR,clrBlack);
      ObjectSetString(0,exprefix+"title",OBJPROP_TEXT,tmpCMT);
      ObjectSetInteger(0,exprefix+"title",OBJPROP_SELECTABLE,false);
   }
   

}

Como nós podemos ver no código da função, as guias de ordenação automática são exibidas somente se isso for permitido pelo parâmetro de entrada addons_infowatch. Além disso, nós adicionamos mais dois parâmetros para configurar as guias de ordenação automática:

sinput string        delimeter_05="";      // --- guias adicionais ---
input bool           useAddonsLevels=true; // Exibe guias adicionais
input bool           addons_infowatch=true;// Oculta o símbolo se não está no Market Watch
input int            addon_tabs_scale=3;   // Escala das guias adicionais (0-5)

Eu acredito que somente o parâmetro addons_infowatch requer esclarecimentos adicionais aqui. Se estiver definido, apenas os símbolos exibidos na guia Market Watch são classificados. Caso contrário, a classificação é executada para todos os símbolos oferecidos pela sua corretora.

Como resultado, ao lançar uma nova versão do utilitário, você pode ver as novas guias de classificação automática além das da área de trabalho:

Adicionando as guias de classificação automática

Mas vamos voltar ao nosso código. Agora, nós sabemos como adicionar as guias de classificação automática. No entanto, nenhum símbolo é exibido ao clicar neles até o momento, já que a exibição do símbolo ainda não foi implementada. Todos os botões da guia aberta no momento são exibidos usando a função show_symbols. Nós vamos adicionar a exibição das guias de classificação automática aqui. Como resultado, a função é a seguinte:

void show_symbols(){
   
   // inicializa as variáveis para definir as coordenadas X e Y
   int btn_left=0;
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   btn_line++;
   
   
   
   if( cur_panel==0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear All");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"showpos", OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"showpos",OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"showpos",OBJPROP_TEXT,"Show Pos");
      btn_left+=BTN_WIDTH;
   }else if( cur_panel<ArraySize(arrPanels) && arrPanels[cur_panel].Total()>0 ){
      ObjectCreate(0, exprefix+"clear_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"clear_"+(string) cur_panel,OBJPROP_TEXT,"Clear");
      btn_left+=BTN_WIDTH;
      ObjectCreate(0, exprefix+"new_"+(string) cur_panel, OBJ_BUTTON, 0, 0, 0);
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XDISTANCE,btn_left); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YDISTANCE,BTN_HEIGHT*btn_line); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_XSIZE,BTN_WIDTH); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_YSIZE,BTN_HEIGHT); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_FONTSIZE,8); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_COLOR,clrBlack); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_BGCOLOR,clrPaleTurquoise); 
      ObjectSetInteger(0,exprefix+"new_"+(string) cur_panel,OBJPROP_SELECTABLE,false);
      ObjectSetString(0,exprefix+"new_"+(string) cur_panel,OBJPROP_TEXT,"Open All");
      btn_left+=BTN_WIDTH;
   }
   
   // se o índice atual da guia aberta exceder o número de elementos do array de elementos da guia da área de trabalho
   // e da aba com todos os símbolos,
   // então, essa é a guia de classificação automática
   if(cur_panel>(ArraySize(arrPanels)-1)){
      MqlRates rates[];
      ArraySetAsSeries(rates, true);
      int tmpNumAddon=cur_panel-ArraySize(arrPanels);
      addonArr.Resize(0);
      arrTT.Resize(0);
      string addonName;
      
      CArrayString tmpSymbols;
      if( tmpNumAddon==0 && air_only_home ){
         for( int j=1; j<ArraySize(arrPanels); j++ ){
            for( int k=0; k<arrPanels[j].Total(); k++ ){
               string curName=arrPanels[j].At(k);
               bool isYes=false;
               for( int r=0; r<tmpSymbols.Total(); r++ ){
                  if(tmpSymbols.At(r)==curName){
                     isYes=true;
                     break;
                  }
               }
               if(!isYes){
                  tmpSymbols.Add(arrPanels[j].At(k));
               }
            }
         }
      }else{
         if( ArraySize(result)>1 ){
            for(int j=0;j<ArraySize(result);j++){
               StringReplace(result[j], " ", "");
               if(StringLen(result[j])<1){
                  continue;
               }
               tmpSymbols.Add(onlySymbolsPrefix+result[j]+onlySymbolsSuffix);
            }
         }else{
            for( int i=0; i<SymbolsTotal(addons_infowatch); i++ ){
               tmpSymbols.Add(SymbolName(i, addons_infowatch));
            }
         }
      }
      
      switch(tmpNumAddon){
         case 0: // níveis de ar
            // código de exibição do conteúdo da guia
            break;
         case 1: // parabólico
            // código de exibição do conteúdo da guia
            break;
         case 2: // Gap
            // código de exibição do conteúdo da guia
            break;
         case 3: //4 semanas min/max
            // código de exibição do conteúdo da guia
            break;
         case 4: //365 dias min/max
            // código de exibição do conteúdo da guia
            break;
         case 5: // níveis de arredondamento
            // código de exibição do conteúdo da guia
            break;
         case 6: // cima/baixo principalmente
            // código de exibição do conteúdo da guia
            break;
         case 7: // high/low de todo o intervalo
            // código de exibição do conteúdo da guia
            break;
         case 8: // high=close
            // código de exibição do conteúdo da guia
            break;
      }
      
      // um botão é exibido no gráfico para cada símbolo no array
      // o nome do símbolo é adicionado ao botão
      for( int i=0; i<addonArr.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,addonArr.At(i));    
         ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);
         if( arrTT.At(i)>0 ){
            ObjectSetString(0,exprefix+"btn"+(string) i,OBJPROP_TOOLTIP,(string) arrTT.At(i));    
         }
   
         if( checkSYMBwithPOS(addonArr.At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
         
         btn_left+=BTN_WIDTH;
      }
      
      // já que os botões já estão exibidos, saí da função
      return;
   }
   
   // exibe o botão no gráfico para cada símbolo no array
   // da guia atualmente ativa
   // o nome do símbolo é adicionado ao botão
   for( int i=0; i<arrPanels[cur_panel].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,arrPanels[cur_panel].At(i));    
      ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_SELECTABLE,false);

      if( !noSYMBwithPOS || cur_panel>0 ){
         if( checkSYMBwithPOS(arrPanels[cur_panel].At(i)) ){
            ObjectSetInteger(0,exprefix+"btn"+(string) i,OBJPROP_BGCOLOR,clrPeachPuff);
         }
      }
      
      btn_left+=BTN_WIDTH;
   }
   
}

Como nós podemos ver, o código para exibir o conteúdo da guia está localizado dentro do interruptor operador. O índice de elemento do array que contém o nome da guia é indicado no operador case. Assim, para adicionar uma guia personalizada após adicionar seu nome ao aray, basta adicionar o novo case com o índice 1 excedendo o último índice aplicado.

Nós teremos mais informações sobre os exemplos de código para os símbolos de classificação automática por determinados parâmetros ao considerar as guias específicas. Mas nós já podemos ver que o código de todas as guias começa de maneira semelhante.

A lista de todos os símbolos a serem classificados já estão presentes no array tmpSymbols. Portanto, o código de cada guia começa no loop for:

            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];
               // código que define se um símbolo deve ser exibido
            }

Níveis de ar (faixas de lateralização)

Ao negociar a partir de níveis, todas as entradas são realizadas perto das faixas de lateralização, ou seja, quando o preço toca o mesmo ou aproximadamente o mesmo preço por sua máxima ou mínima em cada barra. O exemplo é exibido na imagem abaixo:

Exemplo de faixa de lateralização

Este pode não ser o exemplo perfeito, uma vez que o nível obtido experimenta constantes falsos rompimentos. Mas por outro lado, acredita-se que a presença de falsos avanços reforça o nível. =)

Procurar por tais situações é uma tarefa tediosa, então vamos tentar automatizá-las.

A busca por faixas de lateralização é normalmente executada nos gráficos M5, embora não seja uma condição obrigatória. Alguns traders trabalham nos gráficos M15 ou M30. Acredita-se que quanto maior o intervalo de tempo em que uma faixa de lateralização é detectada, melhor a entrada em potencial.

Portanto, vamos adicionar as entradas, permitindo-nos definir os períodos de tempo necessários:

sinput string        delimeter_06=""; // --- Guia dos níveis de ar adicionais ---
input bool           air_level_m5=true; // Busca pelos níveis de ar em M5
input bool           air_level_m15=true; // Busca pelos níveis de ar em M15
input bool           air_level_m30=false; // Busca pelos níveis de ar em M30
input bool           air_level_h1=false; // Busca pelos níveis de ar em H1
input bool           air_level_h4=false; // Busca pelos níveis de ar em H4
input bool           air_level_d1=false; // Busca pelos níveis de ar em D1

Deve ser lembrado que quanto mais períodos gráficos você selecionar, mais lento será o processo de busca. Portanto, é melhor limitar a busca por um ou dois períodos de tempo.

Além disso, vamos adicionar outras entradas:

input uchar          air_level_count=4; // Número das barras de nível
input uchar          air_level_offset=3; // Deslocamento relativo ao nível em pontos
input bool           air_level_cur_day=true; // Apenas os níveis na direção do dia atual
input bool           air_level_prev_day=true; // Apenas os níveis na direção do dia anterior
input bool           air_only_home=false; // Busca apenas na "área de trabalho"

O parâmetro Número de barras de nível permite especificar o número de barras que devem tocar em um preço com localização próxima a fim de assumir isso como uma faixa de lateralização. O valor ideal para este parâmetro é de 4 barras. Quanto maior o número de barras, maior a qualidade da faixa de lateralização, mas elas são encontradas com menos frequência.

O parâmetro Deslocamento relativo ao nível em pontos permite especificar o intervalo de pontos, dentro do qual o preço deve estar para ser assumido "localização próxima". Em outras palavras, se o parâmetro for 0, as barras devem tocar exatamente o mesmo preço até centavos. Geralmente, haverá menos casos desse tipo, especialmente em períodos gráficos mais altos. Na maioria dos casos, o preço não pode atingir o nível especificado parando em alguns pontos, portanto, o valor do parâmetro padrão é de 3 pontos.

Se você negociar apenas na direção atual e/ou do dia anterior, o Apenas os níveis na direção do dia atual e Apenas os níveis na direção do dia anterior permite a classificação dos símbolos, nos quais uma faixa de lateralização detectada tem a direção oposta ao movimento do preço.

A direção do movimento de preços é definida pela razão de um preço de fechamento em relação ao de abertura. Se o preço de fechamento da barra do dia anterior exceder o preço de abertura, busca-se apenas as faixas de lateralização em Long (comprado), ou seja, aquelas que tocam o preço próximo por 'low'.

Finalmente, vamos dar uma olhada no parâmetro final - Busca apenas em "área de trabalho". Se durante um dia de trabalho, você negociar apenas os símbolos adicionados às guias da área de trabalho, defina o parâmetro para que o utilitário procure intervalos simples apenas nos símbolos adicionados atualmente às guias da área de trabalho.

Agora vamos finalmente olhar para o código que classifica os símbolos que atualmente apresentam intervalos simples. O código para o período gráfico em M5 é fornecido abaixo:

               if(air_level_m5 && CopyRates(addonName, PERIOD_M5, 0, air_level_count+1, rates)==air_level_count+1){
                  if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)<=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)<=0) && rates[0].high<rates[1].high ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].high-rates[j+1].high) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].high);
                     }
                  }else if( (!air_level_cur_day || getmeinfoday_symbol(addonName, 0)>=0) && (!air_level_prev_day || getmeinfoday_symbol(addonName, 1)>=0) && rates[0].low>rates[1].low ){
                     bool isOk=true;
                     for( int j=1; j<air_level_count; j++ ){
                        if( MathAbs(rates[1].low-rates[j+1].low) <= air_level_offset*SymbolInfoDouble(addonName, SYMBOL_POINT) ){
                        }else{
                           isOk=false;
                        }
                     }
                     if(isOk && !skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(rates[1].low);
                     }
                  }
               }

Como em todos os outros exemplos de código das guias de classificação automática, aqui nós começamos a trabalhar com os resultados da função CopyRates imediatamente após a chamada em si. Isso não é inteiramente correto. A Ajuda da MQL recomenda aguardar algum tempo para que o utilitário receba e escreva os dados no array. Mas mesmo se esperarmos por 50 milissegundos, a verificação de 100 símbolos significa um atraso adicional de 5 segundos, enquanto muitos corretores oferecem centenas de símbolos do mercado de ações. Como resultado, ao usar um atraso, a exibição de um conteúdo da guia pode levar muito mais tempo.

Portanto, nós começamos imediatamente a trabalhar com o array de resultados. Na prática, não há problemas com isso, exceto talvez o causa pela ausência de atraso.

O fato é que os dados reais nem sempre são transferidos para o array como resultado da função CopyRates. Às vezes, a classificação usa dados obsoletos. Neste caso, basta atualizar a guia (tecla R) para obter a lista de símbolos relevantes.

Vamos voltar ao código. Se você decidir adicionar a guia de classificação automática personalizada, preste atenção em como a seleção dos símbolos ocorre.

Se o símbolo se encaixa em nossas condições, nós colocamos seu nome no array addonArr. Além disso, nós podemos especificar o período gráfico a ser usado ao abrir um gráfico de símbolos em vez de um período gráfico padrão entre parênteses.

Nós também precisamos inserir o valor para o array arrTT. Se definirmos o valor 0 no array, nada acontece. Mas se nós adicionarmos algum preço, a linha horizontal é construída no nível de preço especificado ao abrir um gráfico do símbolo apropriado. Isso é feito por conveniência, para que você possa ver imediatamente o preço em que a faixa de lateralização é detectada.

Arredondamentos Parabólicos

Os arredondamentos parabólicos aparecem após um movimento direcional quando o preço começa a se mover na direção oposta, e a máxima ou mínima de cada nova barra é maior ou menor que a anterior. Acredita-se que, neste caso, o preço é mais provável de ir para o aumento da mínima ou diminuição da máxima. Neste caso, é possível usar um pequeno stop loss.

Em outras palavras, depois da diminuição, o preço começa a subir, e a mínima de cada barra é maior do que a anterior. Nesse caso, entre comprado colocando um nível de stop atrás de pelo menos uma das mínimas da barra anterior.

Considere o seguinte gráfico de exemplo:

Exemplo de arredondamento parabólico

Os arredondamentos parabólicos de exemplo são marcados com setas.

Nós vamos procurar por arredondamentos parabólicos no M5 usando o seguinte código:

               if(CopyRates(addonName, PERIOD_M5, 0, 6, rates)==6){
                  if( rates[0].low>rates[1].low && rates[1].low>rates[2].low && rates[2].low<=rates[3].low && rates[3].low<=rates[4].low && rates[4].low<=rates[5].low ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }else if( rates[0].high<rates[1].high && rates[1].high<rates[2].high && rates[2].high>=rates[3].high && rates[3].high>=rates[4].high && rates[4].high>=rates[5].high ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Em outras palavras, se a mínima da barra atual exceder a mínima da anterior, a mínima dessa barra excederá a mínima de sua própria barra anterior. Enquanto as 3 barras restantes anteriores são iguais umas às outras pelas suas mínimas ou cada uma delas é menor que a outra. Em outras palavras, se as 3 barras caem e são seguidas por 2 altas, isso é considerado como um começo de um arredondamento parabólico de compra.

Gaps

Se você usar estratégias de negociação que funcionam com gaps de ações em uma direção ou outra, a guia Gap irá ajudá-lo a selecionar as ações necessárias. Ele exibe os símbolos com intervalos durante o dia atual. Você pode alterar o intervalo mínimo (em porcentagem do preço atual) usando o Tamanho mínimo entrada.

Por padrão, ele exibe apenas os símbolos com intervalos de pelo menos 1%.

O código fonte da guia é simples:

               if(CopyRates(addonName, PERIOD_D1, 0, 2, rates)==2){
                  if( rates[0].open>rates[1].close+(rates[0].open*(gap_min/100)) || rates[0].open<rates[1].close-(rates[0].open*(gap_min/100)) ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

4 semanas de máximas/mínimas

A guia 4 semanas Min/Max fornece a lista de símbolos que têm seu preço atual no ponto máximo/mínimo mais baixo dentro de 4 semanas. Seu código é o seguinte:

               if(CopyRates(addonName, PERIOD_W1, 0, 4, rates)==4){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<4; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Máximas/Mínimas anuais

Similarmente à guia anterior, a atual exibe os símbolos com seus preços no ponto da máxima/mínima dentro de um ano:

               if(CopyRates(addonName, PERIOD_W1, 0, 52, rates)==52){
                  bool newMin=true;
                  bool newMax=true;
                  if( rates[0].close!=rates[0].high && rates[0].close!=rates[0].low ){
                     newMin=false;
                     newMax=false;
                  }else{
                     for( int j=1; j<52; j++ ){
                        if( rates[0].high < rates[j].high ){
                           newMax=false;
                        }
                        if( rates[0].low > rates[j].low ){
                           newMin=false;
                        }
                     }
                  }
                  if( newMin || newMax ){
                     if(!skip_symbol(addonName)){
                        addonArr.Add(addonName+" (M5)");
                        arrTT.Add(0);
                     }
                  }
               }

Preço próximo dos níveis arredondados

Acredita-se que os preços das ações sejam níveis de suporte/resistência "naturais". Portanto, alguns sistemas de negociação se concentram em instrumentos atualmente negociados em seus níveis arredondados.

Um preço arredondado é aquele que termina em 0 ou 50 centavos, por exemplo, 125 dólares 0 centavos ou 79 dólares 50 centavos.

Como resultado, nós temos o seguinte código:

               switch((int) SymbolInfoInteger(addonName, SYMBOL_DIGITS)){
                  case 0:
                     break;
                  case 2:
                     if(CopyRates(addonName, PERIOD_M5, 0, 1, rates)==1){
                        double tmpRound=rates[0].close - (int) rates[0].close;
                        if( (tmpRound>0.46 && tmpRound<0.54) || tmpRound>0.96 || tmpRound<0.04 ){
                           if(!skip_symbol(addonName)){
                              addonArr.Add(addonName+" (M5)");
                              arrTT.Add(0);
                           }
                        }
                     }
                     break;
               }
Em outras palavras, nós definiremos os preços arredondados apenas para os símbolos com os seus preços apresentando duas casas decimais. Se você trabalha com outros instrumentos, basta adicionar sua própria verificação para eles de maneira semelhante.

Alta/Queda na maior parte do tempo

Instrumentos movendo-se para cima ou para baixo na maior parte do tempo também podem ser de particular interesse. Vamos adicionar as seguintes entradas para encontrá-las:

sinput string        delimeter_08=""; // --- Adicional da guia Alta/Queda ---
input int            mostly_count=15; // Verifica o último número especificado de dias
input int            mostly_percent=90; // Porcentagem especificada em uma direção excedida

O parâmetro Verifica o último número especificado de dias permite definir o número de dias, durante o qual procuramos os instrumentos necessários. Em outras palavras, nós procuraremos movimentos unidirecionais principalmente em D1.

O Porcentagem especificada em uma direção excedida permite especificar a porcentagem mínima, pela qual uma direção de movimento excede a outra. O valor padrão é 90. Isso significa que, para chegar a esta guia, o preço deve se mover em uma determinada direção 90% dos dias fora de todo o período analisado.

Geralmente, o número de tais símbolos é pequeno. Portanto, pode ser necessário reduzir esse percentual.

O código da guia é o seguinte:

               if(CopyRates(addonName, PERIOD_D1, 1, mostly_count, rates)==mostly_count){
                     int mostlyLong=0;
                     int mostlyShort=0;
                     for( int j=0; j<mostly_count; j++ ){
                        if(rates[j].close>rates[j].open){
                           mostlyLong++;
                        }else if(rates[j].close<rates[j].open){
                           mostlyShort++;
                        }
                     }
                     if( !mostlyLong || !mostlyShort ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyLong*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( ((mostlyShort*100)/(mostlyLong+mostlyShort)) >= mostly_percent ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Cada barra com um nova máxima/mínima

Essa guia permite detectar o processo de acumulação dentro de um símbolo, ou seja, o período em que o preço se move em uma direção lenta mas consistente. Geralmente, o acúmulo termina com um rompimento (barras grandes) em sua direção.

As seguintes entradas nos ajudam a detectar movimentos unidirecionais:

sinput string        delimeter_09=""; // --- Adicional da guia Máxima/Mínima de todos os tempos ---
input ENUM_TIMEFRAMES alltime_period=PERIOD_D1; // Período
input int            alltime_count=15; // Verifica o último número especificado de barras

O código de classificação é o seguinte:

               if(CopyRates(addonName, alltime_period, 1, alltime_count, rates)==alltime_count){
                     bool alltimeHigh=true;
                     bool alltimeLow=true;
                     for( int j=1; j<alltime_count; j++ ){
                        if(rates[j].high>rates[j-1].high){
                           alltimeHigh=false;
                        }
                        if(rates[j].low<rates[j-1].low){
                           alltimeLow=false;
                        }
                     }
                     if( alltimeHigh || alltimeLow ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Sessão encerrada nas máximas/mínimas do dia

Esta é a última guia que nós vamos adicionar. Dependendo da hora da chamada, ela permite detectar:

Acredita-se que, se o preço fechar na máxima/mínima do dia, um comprador/vendedor ainda não teve tempo de implementar seus planos. Isso significa que o preço vai na mesma direção no dia seguinte.

Os seguintes parâmetros nos ajudarão a procurar os símbolos adequados:

sinput string        delimeter_10=""; // --- Guia Máxima=Fechamento adicional ---
input ENUM_TIMEFRAMES highclose_period=PERIOD_D1; // Período
input int            highclose_offset=0; // Deslocamento Máxima/Mínimo em pontos

Ao buscar por máxima/mínima do dia, o preço pode não necessariamente fechar diretamente em um ponto extremo. É possível um retrocesso de alguns ou até mesmo uma dúzia de pontos do preço extremo. O parâmetro Margem de erro de máximas/mínimas em pontos permite definir uma reversão permitida em pontos.

O código da guia é o seguinte:

               if(CopyRates(addonName, highclose_period, 0, 1, rates)==1){
                     if( rates[0].close+highclose_offset >= rates[0].high ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }else if( rates[0].close-highclose_offset <= rates[0].low ){
                        addonArr.Add(addonName);
                        arrTT.Add(0);
                     }
               }

Adicionando guias personalizadas

Naturalmente, não consideramos todos os padrões possíveis. Se você usar outros padrões e tiver habilidades de programação em MQL, poderá adicionar facilmente suas próprias guias ao utilitário. Você é bem-vindo para postar os códigos de suas próprias guias com descrições de quais padrões eles pesquisam nos comentários.

Sugestões sobre como melhorar o código acima também são bem-vindas.

Para concluir, deixe-me lembrá-lo de como adicionar sua própria guia de classificação automática ao utilitário. Isso é feito em duas etapas.

Primeiro, adicione o nome da nova guia ao array name da guia panelNamesAddon. Não se esqueça de aumentar o tamanho do array por 1.

Segundo, o operador switch da função show_symbols deve caracterizar o novo case com o seu valor excedendo o maior dos aplicados por 1. O código para verificar se o símbolo atual se ajusta às condições é implementado dentro do operador case. O modelo de código é o seguinte:

         case index: // nome da guia
            for( int i=0; i<tmpSymbols.Total(); i++ ){
               addonName=tmpSymbols[i];

               // verifica o código
            }
            
            break;

Conclusão

Nós expandimos ainda mais a funcionalidade do nosso utilitário. Eu acredito, que ele tornou-se mais útil para os traders.

Neste artigo, nós não reescrevemos o código dependendo da versão da linguagem MQL, pois ele funciona tanto em MQL4 quanto em MQL5.

Como você pode ver, o desenvolvimento de utilitários cross-platform em MQL não é um grande desafio. A maior parte das funcionalidades em MQL5 é suportada no MQL4 de maneira semelhante. Portanto, pode valer a pena esquecer temporariamente todos os tipos de classes e outros recursos exclusivos do MQL5 e deixar seu trabalho disponível para o maior número possível de traders.

Claro que eu não declaro guerra às classes. As classes já estão presentes em MQL4 e os seus códigos já aparecem na CodeBase. Eu proponho adiar esse recurso da linguagem MQL5 por um tempo.