Utilitário de seleção e navegação em MQL5 e MQL4: incremetando abas de "lembretes" e salvando objetos gráficos

18 fevereiro 2019, 07:42
Roman Klymenko
1
357

Introdução

No artigo anterior, desenvolvemos um utilitário que separa e seleciona símbolos com um ponto de entrada adequado. Aprendemos a separar símbolos através de vários parâmetros, bem como navegar entre os símbolos usando botões especialmente desenvolvidos, no entanto, o cenário não é tão otimista em termos de seleção dos símbolos. Atualmente, temos que escrever os ativos dos instrumentos selecionados em um pedaço de papel, isto acarreta um impacto muito negativo nas "populações florestais do planeta".

Neste artigo, salvaremos as árvores da destruição e aprenderemos como salvar automaticamente os objetos gráficos criados na plataforma, assim você não precisará criá-los novamente no futuro.

Aplicando características de compilação condicional

Primeiramente vamos simplificar a portabilidade do utilitário para a linguagem MQL4, no artigo anterior, substituímos um bloco de código por outro, permitindo que o programa funcione na MQL4. Agora nos deparamos com uma tarefa mais difícil, podemos realizar desenvolvimento em uma única linguagem, por exemplo, em MQL5, e então substituir constantemente os blocos de código necessários que não funcionam na MQL4, ou podemos desenvolver simultaneamente dois programas: em MQL5 e MQL4.

Nenhuma das opções é a ideal, pois temos de substituir constantemente os blocos (em cada versão) que não funcionam no MQL4, ou ter em mente as partes do código que alteramos na implementação das mudanças no utilitário em outra linguagem.

Logo, usaremos uma abordagem diferente. Ambas linguagens de programação, MQL5 e MQL4, suportam diretivas de compilação condicional, permitindo a execução de um bloco de código ou outro, dependendo das condições, entre as diretivas, existe uma construção executada que depende da versão atual da linguagem MQL. Sua sintaxe principal é a seguinte:

      #ifdef __MQL5__ 
         // bloco de código a ser executado apenas em MQL5
      #else 
         // bloco de código a ser executado somente em MQL4
      #endif 

Vamos usá-lo para deixar a nossa função checkSYMBwithPOS trabalhar corretamente, tanto em MQL5 quanto em MQL4, sem a necessidade de substituir constantemente:

bool checkSYMBwithPOS(string name){
   // Ocultar símbolos com posições e ordens
   bool isskip=false;
   if( noSYMBwithPOS ){
      #ifdef __MQL5__ 
         // visualiza a lista de todas as posições abertas
         int cntMyPos=PositionsTotal();
         for(int ti=cntMyPos-1; ti>=0; ti--){
            // avança 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;
                  }
               }
            }
         }
      #else 
         // visualiza a lista de todas as posições abertas
         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;
            }
         }
      #endif 
   }
   return isskip;
}

Além disso, os códigos de blocos que não funcionam em MQL4 com essa construção serão reportados imediatamente no artigo.

Abas de lembretes

Para salvarmos as florestas da Terra, criaremos três abas para exibir apenas os símbolos previamente selecionados e vamos nomear estas abas como Long, Short e Range. Naturalmente, você não precisa adicionar exclusivamente símbolos ascendentes, descendentes ou laterais, você pode usá-los a seu critério.

Como resultado, os nosso utilitário é anexado ao gráfico com uma linha composta por quatro botões: o botão All e os três descritos anteriormente.

Por padrão, o botão All é pressionado e uma lista de todos os símbolos que se encaixam em nossos filtros serão exibidos abaixo:

Adicionando abas para selecionar os lembretes

Assim, o objetivo foi definido e agora temos que implementá-lo, para fazer isso, teremos que reescrever uma pequena parte de nosso utilitário.

Arrays para armazenar o conteúdo das abas. Primeiramente, vamos acescentar variáveis para armazenar o conteúdo de nossas abas. Anteriormente, tínhamos apenas uma aba e seu conteúdo era armazenado na variável arrPanel1, agora vamos colocar variáveis semelhantes para outras abas:

// símbolos de array exibidos na aba correta:
CArrayString arrPanel1;
CArrayString arrPanel2;
CArrayString arrPanel3;
CArrayString arrPanel4;

Adicionalmente vamos criar outro array para poder acessar as abas em um loop. Este outro array armazenará ponteiros para todos os quatro arrays criados anteriormente:

// array para combinar todas as abas
CArrayString *arrPanels[4];

Vamos inicializar o array na função OnInit():

   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;

Índice das abas. Uma vez que o trabalho com as abas é para ser executado no loop, será perfeito para armazenar os nomes das abas, bem como acessá-las a partir do loop. Portanto, vamos criar o array com os nomes das abas:

// Array dos nomes das abas
string panelNames[4]={"All", "LONG", "SHORT", "Range"};

Variáveis Auxiliares. Outra alteração diz respeito à variável panel1val. Nós mudamos seu nome para panelval, esta é uma emenda puramente estética, você deve ficar atento.

O parâmetro cur_panel contém o índice da aba atualmente ativa, que também foi introduzida. O tipo de variável é uchar, isso significa que pode levar valores de 0 a 255, o que é suficiente, pois temos apenas 4 abas.

Por padrão, a primeira aba (com índice 0 no array) fica ativa, portanto, colocaremos uma string com valor 0 à variável na função OnInit(), finalmente, a função OnInit() assume a forma final:

int OnInit()
  {
   // índice da aba atualmente ativa
   cur_panel=0;
   // inicializar o array das abas
   arrPanels[0]=&arrPanel1;
   arrPanels[1]=&arrPanel2;
   arrPanels[2]=&arrPanel3;
   arrPanels[3]=&arrPanel4;
   
   start_symbols();

//--- criar timer
   EventSetTimer(1);
      
//---
   return(INIT_SUCCEEDED);
  }

Outras mudanças. Será desnecessário listar todas as alterações implementadas, pois muitas delas são insignificantes, então, vamos passar para as principais mudanças agora. Quanto as pequenas alterações, você pode detectá-las por conta própria, comparando o código-fonte do novo utilitário com o código anexado ao artigo anterior.

As principais mudanças são principalmente aquelas relacionadas à exibição de nossas novas abas, decidimos trabalhar com nossas abas dentro de um loop. Vamos ver como fazemos isso.

Como temos uma linha com abas, precisamos exibi-la de alguma forma, para conseguir isso, criamos uma função separada. O código está escrito abaixo:

void show_panel_buttons(){
   int btn_left=0;
   // define a coordenada máxima possível do eixo x para exibir as abas.
   int btn_right=(int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS)-77;
   string tmpName="";
   
   for( int i=0; i<ArraySize(panelNames); i++ ){
      // se a coordenada inícial do novo botão exceder uma máxima possível,
      // vá para a nova linha.
      if( btn_left>btn_right-BTN_WIDTH ){
         btn_line++;
         btn_left=0;
      }
      // se as abas de "lembretes" exibirem símbolos, adicione o número deles
      // para o nome da aba
      tmpName=panelNames[i];
      if(i>0 && arrPanels[i].Total()>0 ){
         tmpName+=" ("+(string) arrPanels[i].Total()+")";
      }
      
      // botões da aba de exibição
      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); 
      ObjectSetString(0,exprefix+"panels"+(string) i,OBJPROP_TEXT,tmpName);
      ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_SELECTABLE,false);
      // se a aba de botão estiver ativa no momento,
      // mantê-la pressionada
      if( cur_panel == i ){
         ObjectSetInteger(0,exprefix+"panels"+(string) i,OBJPROP_STATE, true);
      }
      
      btn_left+=BTN_WIDTH;
   }

}

Vamos chamar a função dentro da função start_symbols antes de chamar a função show_symbols, que exibe os botões de símbolo.

A função show_symbols também mudou, embora ligeiramente. Agora exibimos apenas os botões dos símbolos localizados na aba que está ativa no momento:

   // exibe um botão no gráfico para cada símbolo no array da
   // aba atualmente ativa
   // vamos escrever o nome do símbolo no 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;
   }

Botões para adicionar símbolos nas abas. Agora precisamos acrescentar os símbolos a uma aba selecionada, faremos isso com a ajuda de novos botões, a serem introduzidos na página aberta do gráfico: Add LONG, Add SHORT e Add Range.

Se você recordar, no último artigo nós implementamos a condição de clicar no botão do símbolo desejado. Após clicar no símbolo, um gráfico é aberto contendo o bloco de botões para navegar por toda a lista de símbolos, no canto inferior esquerdo. Vamos acrescentar nossos botões a este bloco, dependendo do símbolo que está na aba correspondente, o botão irá acrescentá-lo na aba ou excluí-lo de lá.

O bloco de botões é exibido usando a função createBTNS, vamos incrementar o loop com novos botões:

   for( int i=ArraySize(panelNames)-1; i>0; i-- ){
      isyes=false;
      if(arrPanels[i].Total()){
         for(int j=0; j<arrPanels[i].Total(); j++){
            if( arrPanels[i].At(j)==name ){
               isyes=true;
               break;
            }
         }
      }
      if( isyes ){
         ObjectCreate(CID, exprefix+"_p_btn_panelfrom"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_TEXT,"Del "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_BGCOLOR,clrPink); 
      }else{
         ObjectCreate(CID, exprefix+"_p_btn_panelto"+(string) i, OBJ_BUTTON, 0, 0, 0);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XDISTANCE,110); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YDISTANCE,tmpHeight);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_XSIZE,BTN_WIDTH); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_YSIZE,BTN_HEIGHT); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_CORNER,CORNER_LEFT_LOWER); 
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_SELECTABLE,false); 
         ObjectSetString(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_TEXT,"Add "+panelNames[i]);
         ObjectSetInteger(CID,exprefix+"_p_btn_panelto"+(string) i,OBJPROP_BGCOLOR,clrHoneydew); 
      }
      tmpHeight+=25;
   }

Bloco de botões de navegação

Para ativar os novos botões, acrescente o código de verificação do status do botão da função OnTimer() , como fizemos no último artigo:

            for( uchar i=1; i<ArraySize(panelNames); i++ ){
               // se o botão de remoção da aba for pressionado, primeiro remove o símbolo e abre o símbolo novamente no gráfico
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelfrom"+(string) i,OBJPROP_STATE)==true ){
                  delToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  curchart();
                  return;
               }
               // se o botão de adicionar a aba for pressionado, primeiro adiciona o símbolo e e abre o símbolo novamente no gráfico
               if(ObjectGetInteger(curChartID[tmpCIDcnt-1],exprefix+"_p_btn_panelto"+(string) i,OBJPROP_STATE)==true ){
                  addToPanel(i, ChartSymbol(curChartID[tmpCIDcnt-1]));
                  nextchart();
                  return;
               }
            }

A função delToPanel primeiro remove o símbolo da aba selecionada e atualiza todos os botões de símbolo no gráfico, onde o utilitário é anexado ou apenas os botões do índice:

void delToPanel(uchar num, string name){
   // move ao longo de todo array e remove o primeiro elemento
   // com o seu nome similar ao nosso símbolo
   for(int i=0; i<arrPanels[num].Total(); i++){
      if( arrPanels[num].At(i)==name ){
         arrPanels[num].Delete(i);
         break;
      }
   }
   // se a aba estiver aberta no momento,
   if(num==cur_panel){
      initial_btn_line();
      // remove no gráfico os botões de símbolos criados anteriormente:
      ObjectsDeleteAll(0, exprefix);
      // exibe a lista atualizada de símbolos:
      show_panel_buttons();
      show_symbols();
   }else{
      // se alguma outra aba estiver aberta, basta atualizar os botões do índice
      upd_panel_title();
   }
   
   
}

A função addToPanel é o oposto da que acabamos de considerar, ela coloca um símbolo na aba e também verifica se o símbolo está presente em outras abas. Se o símbolo estiver presente, ele será removido:

void addToPanel(uchar num, string name){
   // adiciona um símbolo à aba
   arrPanels[num].Add(name);
   // remove um símbolo de outras abas
   // se presente
   for( int j=1; j<ArraySize(arrPanels); j++ ){
      if(j==num) continue;
      for(int i=0; i<arrPanels[j].Total(); i++){
         if( arrPanels[j].At(i)==name ){
            if( panelval==i && i>0 ){
               panelval--;
            }
            arrPanels[j].Delete(i);
            break;
         }
      }
   }
   if(num==cur_panel){
      initial_btn_line();
      // remove no gráfico os botões de símbolos criados anteriormente:
      ObjectsDeleteAll(0, exprefix);
      // exibe a lista de símbolos:
      show_panel_buttons();
      show_symbols();
   }else{
      upd_panel_title();
   }
}

Salvando o conteúdo das abas entre os acionamentos do utilitário. E se fecharmos acidentalmente o EA? Então todos os nossos esforços serão desperdiçados, devemos começar tudo de novo? Vamos nos certificar de que as listas de símbolos que colocamos nas abas de lembretes sejam salvas no arquivo e restauradas após a abertura.

Usamos os objetos do tipo CArrayString para armazenar as listas de símbolos selecionados por um motivo. Uma das muitas vantagens dos objetos desse tipo, são os métodos padrão que facilitam, tanto enviar todo o conteúdo de uma array para um arquivo, como restaurar um array de um arquivo. Vamos utilizar os objetos para salvar o conteúdo dos arrays no arquivo antes de fechar o utilitário. Em outras palavras, devemos acrescentar a chamada de nossa nova função savePanels para a função padrão OnDeinit():

void savePanels(){
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

O conteúdo do array será restaurado na função padrão OnInit():

   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Load(fh);
         FileClose(fh); 
      }
   }

Acrescentando um índice para identificar os parâmetros atuais

Se você negociar em mercados diferentes, terá que constantemente ajustar o utilitário quando mudar de um mercado para outro. Afinal, se você está negociando no mercado americano de ações, à espera de um rompimento na primeira hora e meia após a abertura do mercado, então você não precisa do mercado europeu ou russo de ações, onde o pregão de abertura aconteceu há muito tempo. Da mesma forma, se você está negociando no mercado russo, você não precisa do mercado ações dos EUA.

Para não se distrair e focar unicamente nos símbolos necessários, seria sensato criar conjuntos separados de parâmetros para mercados de diferentes países, bem como para o mercado Forex, carregando cada uma das configurações dos arquivos quando for necessário. Isso é bem simples e leva apenas alguns segundos, no entanto, é difícil entender quais configurações são carregadas no momento.

Para ver as configurações dos parâmetros, vamos acrescentar uma entrada cmt, onde colocamos uma nota sobre o mercado em que estamos trabalhando:

input string         cmt=""; //Parâmetros para coleta das notícias

Vamos exibir este comentário em uma linha junto com os botões de nossas abas:

Exibindo o índice do conjunto atual

Para isso, coloque o seguinte bloco de código na função show_panel_buttons, depois de exibir todos os botões:

   // exibir um comentário, se especificado:
   if(StringLen(cmt)>0){
      string tmpCMT=cmt;
      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);
   }

Além de identificar o conjunto atual de parâmetros, a entrada cmt nos ajuda a separar as listas de símbolos nas abas de lembretes. Depois de tudo isto, se colocarmos um símbolo na aba de lembretes para trabalhar no mercado americano, não precisaremos deste símbolo quando trabalharmos no mercado russo de ações. Configurar arquivos com diferentes seleções de parâmetros também devem conter listas separadas para as abas de lembretes.

Para implementar estas configurações, temos que modificar ligeiramente o código que salva os arrays no arquivo e/ou restaura. Como exemplo, vamos considerar a função modificada para salvar um arquivo:

void savePanels(){
   string tmpCmt=cmt;
   StringReplace(tmpCmt, " ", "_");
   for( int i=1; i<ArraySize(arrPanels); i++ ){
      fh=FileOpen(exprefix+"panel"+(string) (i+1)+tmpCmt+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
      if(fh>=0){ 
         arrPanels[i].Save(fh);
         FileClose(fh);
      }
   }
}

Salvando objetos gráficos

Outra questão que precisamos resolver para trabalhar com gráficos é salvar automaticamente e restaurar objetos gráficos que criamos na plataforma. Se definirmos um nível no gráfico, esperamos reabri-lo novamente depois de fechar a janela do gráfico, nós certamente não queremos colocar dezenas de níveis toda vez que abrimos um gráfico de símbolo.

O código que escrevemos até agora funciona igualmente bem tanto em MQL5 quanto em MQL4, este não é o caso das funções que salvam e restauram objetos gráficos. Em MQL4, o tipo de objeto gráfico e suas propriedades separadas são descritos por constantes do tipo numérico, enquanto MQL5 aplica enumerações (tipo enum) para este objetivo, por isto é muito difícil salvar e restaurar em um arquivo, eu, pelo menos, não consegui lidar com essa tarefa de uma maneira geral. A funcionalidade de salvar objetos gráficos em MQL4 é de maior ajuda para nós aqui, teoricamente, pode-se salvar qualquer objeto gráfico (eu não testei com todos os objetos, então são possíveis exceções). A funcionalidade MQL5 permite que se trabalhe apenas com linhas horizontais, etiquetas e campos de texto, se você precisar salvar outros objetos gráficos, terá que implementá-los por conta própria.

MQL4. É mais simples escrever o código para salvar objetos gráficos, vamos começar com funções para esta linguagem. A função para salvar objetos geográficos para um arquivo:

   void savechart(ulong id){
      // salvar objetos gráficos somente se o 
      // Parâmetro "Salvar objetos gráficos criados" = true
      if(saveGraphics){
         // obter um nome de símbolo
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         // limpar o array de objetos gráficos
         saveG.Resize(0);
         
         // adicionar ao array todos os objetos gráficos criados pelo usuário e suas propriedades 
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         // salve o conteúdo do array no arquivo
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }

Como você pode observar, não retemos todas as propriedades do objeto, mas apenas as seguintes: BJPROP_COLOR, OBJPROP_STYLE, OBJPROP_WIDTH, OBJPROP_TIME, OBJPROP_TIMEFRAMES, OBJPROP_ANCHOR, OBJPROP_XDISTANCE, OBJPROP_YDISTANCE, OBJPROP_STATE, OBJPROP_XSIZE, OBJPROP_YSIZE, OBJPROP_XOFFSET, OBJPROP_YOFFSET, OBJPROP_BGCOLOR, OBJPROP_BORDER_COLOR, OBJPROP_PRICE and OBJPROP_TEXT. Se algum dos objetos gráficos aplicados for salvo incorretamente durante sua execução com o utilitário, nem todas as propriedades aplicadas serão salvas, Neste caso, simplesmente salve as propriedades ausentes nessa função, para que estes tipos de objetos gráficos também sejam suportados.

Agora vamos dar uma olhada na função que carrega objetos gráficos de um arquivo e os exibe no gráfico:

   void loadchart(ulong id){
      // exibir objetos gráficos somente se 
      // Parâmetro "Salvar objetos gráficos criados" = true
      if(saveGraphics){
         // obter um nome de símbolo
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         // limpar o array de objetos gráficos
         saveG.Resize(0);
         // carregar a lista de objetos gráficos do arquivo para o array
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         // exibir objetos gráficos no gráfico sucessivamente
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // o tipo de objeto sempre vai primeiro na linha
                        // de modo que inicialmente criamos um objeto e formamos suas propriedades depois
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           ObjectCreate(id, tmpObjName, (int) tmpObjSubLine[3], 0, 0, 0);
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           ObjectSetInteger(id, tmpObjName, (int) tmpObjSubLine[2], (int) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           ObjectSetDouble(id, tmpObjName, (int) tmpObjSubLine[2], (double) tmpObjSubLine[3]);
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           ObjectSetString(id, tmpObjName, (int) tmpObjSubLine[2], tmpObjSubLine[3]);
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

MQL5. Como já mencionado, essas funções em MQL5 não são tão eficientes:

   void savechart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         
         saveG.Resize(0);
         
         int obj_total=ObjectsTotal((long) id); 
         string name;
         string tmpObjLine="";
         for(int i=0;i<obj_total;i++){
            name = ObjectName((long) id, i);
            if( StringFind(name, exprefix)<0 && StringFind(name, "fix")<0 && StringFind(name, "take")<0 && StringFind(name, "stop loss")<0 && StringFind(name, "sell")<0 && StringFind(name, "buy")<0 ){
               tmpObjLine=name;
               // somente podemos trabalhar com os tipos de objeto OBJ_HLINE, OBJ_TEXT e OBJ_LABEL,
               //portanto, pulamos objetos de outros tipos
               if( ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_HLINE && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_TEXT && ObjectGetInteger(id, name, OBJPROP_TYPE)!=OBJ_LABEL ){
                  continue;
               }
               StringAdd(tmpObjLine, "|int~OBJPROP_TYPE~"+(string)(int) OBJPROP_TYPE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TYPE));
               StringAdd(tmpObjLine, "|int~OBJPROP_COLOR~"+(string)(int) OBJPROP_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_COLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_STYLE~"+(string)(int) OBJPROP_STYLE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STYLE));
               StringAdd(tmpObjLine, "|int~OBJPROP_WIDTH~"+(string)(int) OBJPROP_WIDTH+"~"+(string) ObjectGetInteger(id, name, OBJPROP_WIDTH));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIME~"+(string)(int) OBJPROP_TIME+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIME));
               StringAdd(tmpObjLine, "|int~OBJPROP_TIMEFRAMES~"+(string)(int) OBJPROP_TIMEFRAMES+"~"+(string) ObjectGetInteger(id, name, OBJPROP_TIMEFRAMES));
               StringAdd(tmpObjLine, "|int~OBJPROP_ANCHOR~"+(string)(int) OBJPROP_ANCHOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_ANCHOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_XDISTANCE~"+(string)(int) OBJPROP_XDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YDISTANCE~"+(string)(int) OBJPROP_YDISTANCE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YDISTANCE));
               StringAdd(tmpObjLine, "|int~OBJPROP_STATE~"+(string)(int) OBJPROP_STATE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_STATE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XSIZE~"+(string)(int) OBJPROP_XSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_YSIZE~"+(string)(int) OBJPROP_YSIZE+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YSIZE));
               StringAdd(tmpObjLine, "|int~OBJPROP_XOFFSET~"+(string)(int) OBJPROP_XOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_XOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_YOFFSET~"+(string)(int) OBJPROP_YOFFSET+"~"+(string) ObjectGetInteger(id, name, OBJPROP_YOFFSET));
               StringAdd(tmpObjLine, "|int~OBJPROP_BGCOLOR~"+(string)(int) OBJPROP_BGCOLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BGCOLOR));
               StringAdd(tmpObjLine, "|int~OBJPROP_BORDER_COLOR~"+(string)(int) OBJPROP_BORDER_COLOR+"~"+(string) ObjectGetInteger(id, name, OBJPROP_BORDER_COLOR));
               StringAdd(tmpObjLine, "|double~OBJPROP_PRICE~"+(string)(int) OBJPROP_PRICE+"~"+(string) ObjectGetDouble(id, name, OBJPROP_PRICE));
               StringAdd(tmpObjLine, "|string~OBJPROP_TEXT~"+(string)(int) OBJPROP_TEXT+"~"+(string) ObjectGetString(id, name, OBJPROP_TEXT));
               
               saveG.Add(tmpObjLine);
            }
         }
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_WRITE|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Save(fh);
            FileClose(fh);
         }
      }
   }
   void loadchart(ulong id){
      if(saveGraphics){
         string tmpName="";
         if(cur_panel<ArraySize(arrPanels)){
            tmpName=arrPanels[cur_panel][panelval];
         }
         tmpName=clean_symbol_name(tmpName);
         StringReplace(tmpName, " ", "");
         string tmpObjLine[];
         string tmpObjName="";
         string sep1="|";
         string sep2="~";
         
         saveG.Resize(0);
         fh=FileOpen(exprefix+"_graph_"+tmpName+".bin",FILE_READ|FILE_BIN|FILE_ANSI); 
         if(fh>=0){ 
            saveG.Load(fh);
            FileClose(fh); 
         }
         for( int i=0; i<saveG.Total(); i++ ){
            StringSplit(saveG.At(i), StringGetCharacter(sep1,0), tmpObjLine);
            for( int j=0; j<ArraySize(tmpObjLine); j++ ){
               if(j>0){
                  string tmpObjSubLine[];
                  StringSplit(tmpObjLine[j], StringGetCharacter(sep2,0), tmpObjSubLine);
                  if(ArraySize(tmpObjSubLine)==4){
                     if(tmpObjSubLine[0]=="int"){
                        // criar um objeto dependendo do seu tipo
                        if(tmpObjSubLine[1]=="OBJPROP_TYPE"){
                           switch((int) tmpObjSubLine[3]){
                              case 1:
                                 ObjectCreate(id, tmpObjName, OBJ_HLINE, 0, 0, 0);
                                 break;
                              case 101:
                                 ObjectCreate(id, tmpObjName, OBJ_TEXT, 0, 0, 0);
                                 break;
                              case 102:
                                 ObjectCreate(id, tmpObjName, OBJ_LABEL, 0, 0, 0);
                                 break;
                           }
                        }else if( (int) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_COLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STYLE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STYLE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_WIDTH"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_WIDTH, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIME"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIME, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_TIMEFRAMES"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_TIMEFRAMES, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_ANCHOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_ANCHOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YDISTANCE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YDISTANCE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_STATE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_STATE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YSIZE"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YSIZE, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_XOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_XOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_YOFFSET"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_YOFFSET, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BGCOLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BGCOLOR, (int) tmpObjSubLine[3]);
                           }else if(tmpObjSubLine[1]=="OBJPROP_BORDER_COLOR"){
                              ObjectSetInteger(id, tmpObjName, OBJPROP_BORDER_COLOR, (int) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="double"){
                        if( (double) tmpObjSubLine[3] >= 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_PRICE"){
                              ObjectSetDouble(id, tmpObjName, OBJPROP_PRICE, (double) tmpObjSubLine[3]);
                           }
                        }
                     }else if(tmpObjSubLine[0]=="string"){
                        if( StringLen(tmpObjSubLine[3]) > 0 ){
                           if(tmpObjSubLine[1]=="OBJPROP_TEXT"){
                              ObjectSetString(id, tmpObjName, OBJPROP_TEXT, tmpObjSubLine[3]);
                           }
                        }
                     }
                  }
               }else{
                  tmpObjName=tmpObjLine[j];
               }
            }
            ObjectSetInteger(id, tmpObjName, OBJPROP_SELECTABLE, true);
         }
         
         
      }
   }

Se dermos uma olhada mais de perto, vamos notar que temos que usar uma string separada para criar um objeto para objetos de diferentes tipos, enquanto em MQL4, uma string é suficiente para todos os objetos, também é o caso das propriedades do objeto. Em MQL4, usamos uma string de criação de propriedade por tipo (string, real ou intenger). Em MQL5, cada propriedade requer uma string separada de sua criação.

Combinando Linguagens. Vamos usar a compilação condicional, para que o EA aplique a versão necessária das funções, dependendo da linguagem:

#ifdef __MQL5__ 
   void savechart(ulong id){
      // Função MQL5
   }
   void loadchart(ulong id){
      // ...
   }
#else 
   void savechart(ulong id){
      // Função MQL4
   }
   void loadchart(ulong id){
      // ...
   }
#endif 

Aplicando as funções. Agora vamos incrementar as funções adequadas ao programa.

A chamada da função loadchart é introduzida dentro da função showcharts, que abre o gráfico de acordo com o botão que pressionamos.

A chamada da função de salvamento do gráfico deve ser incluida aos blocos de código relacionados para responder o pressionamento dos botões de navegação do gráfico:Next chart, Prev chart e Close chart, bem como os botões para adicionar/remover um símbolo das abas de lembretes.

Vamos utilizar o site finviz.com para seleção preliminar das ações

No artigo anterior, mencionamos que podemos obter os símbolos também de uma outra entrada, não apenas da lista de símbolos oferecidos pela corretora. Primeiro, isso permite exibir apenas o conjunto limitado de símbolos na ordem necessária. Em segundo lugar, um configuração personalizada de símbolos pode ser usada para realizar a filtragem preliminar no site finviz.com ou outro similar.

No artigo anterior, formamos um conjunto de várias entradas que permitem aos usuários classificar símbolos por preço, ATR, etc, no entanto, esses recursos empalidecem em comparação com o filtro do site finviz.com. Mais importante ainda, a linguagem MQL4 não possui possibilidade de classificar os símbolos pelo volume real e este é um indicador muito importante para muitas estratégias baseadas em níveis de negociação. O site finviz.com permite que você classifique pelo volume médio das ações, bem como pelo volume negociado no dia atual.

Incluindo a condição de obter uma lista de símbolos apartir do parâmetro de entrada. Para usar uma lista de símbolos de terceiros, vamos acrescentar três entradas adicionais ao utilitário:

input string         ""; // Apenas símbolos (separador - ; ou espaço)
input string         ""; // Adicionar prefixo aos símbolos
input string         ""; // Adicionar sufixo aos símbolos

Precisamos dos parâmetros onlySymbolsPrefix e onlySymbolsSuffix se os nomes dos símbolos na corretora forem diferentes dos códigos oficiais dos ativos finaceiros. Algumas corretoras incorporam o sufixo .us para ações dos EUA e do sufixo .eu para ações européias, enquanto algumas corretores usam o sufixo m para quaisquer códigos de ativos. As corretoras também podem incluir o símbolo # no início dos códigos de ativos das ações.

Incluindo a condição de importar símbolos de um arquivo. Olhando a frente, direi imediatamente que teremos um problema com a importação de símbolos a partir das entradas. Esta questão refere-se ao tamanho máximo da string, quando usamos a entrada, estamos limitados no máximo a 15-20 códigos de ativos, portanto, a entrada está limitada a um pequeno número de símbolos.

Além da entrada, você também pode colocar os símbolos necessários no arquivo symbols.txt criado na pasta Files.

Implementação no código. Vamos dividir o processo de formar a lista de símbolos para a aba All em dois blocos.

O primeiro bloco verifica se existem símbolos no arquivo ou na entrada. Se sim, o array result é preenchido por eles. O array é adicionado à função OnInit():

         // se a entrada "Somente símbolos (separador - ou espaço)"
         // apresenta todos os dados
         if( StringLen(onlySymbols)>0 ){
            // dividir a linha da entrada nos elementos do array
            // os elementos são separados por ;
            StringSplit(onlySymbols,StringGetCharacter(";",0),result); 
            if( ArraySize(result)>1 ){
            }else{
               // se apenas um valor estiver presente no array como resultado de uma divisão,
               // então a divisão falhou, e aparentemente, o separador na linha é o espaço
               // portanto, agora dividimos a linha da entrada nos elementos do array
               // e espaço é usado como separador de elementos
               StringSplit(onlySymbols,StringGetCharacter(" ",0),result); 
            }
         // caso contrário, verifique se a pasta Arquivos contém o arquivo symbols.txt
         }else if( FileIsExist("symbols.txt") ){
            // se o arquivo existir, coloque seu conteúdo na variável temporária 'outfile'
            int filehandle=FileOpen("symbols.txt",FILE_READ|FILE_TXT); 
            if(filehandle>=0){
               string outfile=FileReadString(filehandle);
               // se a variável 'outfile' contém uma string,
               // tente dividi-la em elementos do array
               // primeiro, usando separador ; seguido por espaço
               if(StringLen(outfile)>0){
                  StringSplit(outfile,StringGetCharacter(";",0),result); 
                  if( ArraySize(result)>1 ){
                  }else{
                     StringSplit(outfile,StringGetCharacter(" ",0),result); 
                  }
                  if( ArraySize(result)>1 ){
                     from_txt=true;
                  }
               }
               FileClose(filehandle);
            }
         }

Na função prepare_symbols, primeiro verificamos se o array result apresenta todos os dados e os usa se isso acontecer. Caso contrário, usaremos todos os símbolos oferecidos pela corretora ou aqueles acrescentados no painel Observação do Mercado para classificação adicional:

   // se o array tiver mais de dois símbolos, use-os
   // adicionando preliminarmente o sufixo ou prefixo requerido, se necessário
   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);
      }
   // caso contrário, use todos os símbolos fornecidos pela corretora
   }else{
      for( int i=0; i<SymbolsTotal(noSYMBmarketWath); i++ ){
         tmpSymbols.Add(SymbolName(i, noSYMBmarketWath));
      }
   }

Formando uma lista de símbolos usando o finviz.com como filtro. Finalmente, vamos dar uma olhada em como importar símbolos de ativos financeiros selecionados no site finviz.com para o nosso utilitário.

Tudo é simples, vá para a aba Tickers na página Screener, você verá a nuvem com os nomes dos ativos para selecionar, selecione todos, copie e cole no arquivo symbols.txt ou na entrada. Se houver várias páginas contendo resultados da pesquisa, vá para a próxima página e faça o mesmo.

Conclusão

Fizemos muito trabalho e tornamos nosso utilitário mais funcional, agora podemos facilmente usá-lo para selecionar ações, esquecendo as notas de papel. Eu espero que as florestas do planeta apreciem isso. =)

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

Arquivos anexados |
finder4.mq4 (125.66 KB)
finder4.ex4 (83.41 KB)
finder.mq5 (125.65 KB)
finder.ex5 (151.23 KB)
Últimos Comentários | Ir para discussão (1)
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.

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.

Diagramas horizontais nos gráficos do MetaTrader 5 Diagramas horizontais nos gráficos do MetaTrader 5

Embora a tarefa de plotar diagramas horizontais no gráfico do terminal não seja frequente, é o desenvolvedor que deve lidar com ela. Essa tarefa envolve indicadores de distribuição de volumes para um período específico. Também implica distribuição de preços, diversos livros de ofertas, etc. O artigo considera a criação e o gerenciamento de diagramas horizontais em gráficos, arrays de primitivas gráficas.

Uso Prático das Redes Neurais de Kohonen na Negociação Algorítmica. Parte I. Ferramentas Uso Prático das Redes Neurais de Kohonen na Negociação Algorítmica. Parte I. Ferramentas

O presente artigo desenvolve a ideia de usar os Mapas de Kohonen na MetaTrader 5, abordado em algumas publicações anteriores. As classes avançadas e aprimoradas fornecem ferramentas para solucionar as tarefas da aplicação.