EA com interface gráfica: Fornecendo funcionalidade (Parte II)

Anatoli Kazharski | 1 agosto, 2018

Sumário

Introdução

No artigo anterior, mostrei como criar rapidamente a GUI do EA. Aqui continuaremos a trabalhar com esse material e vincularemos a interface gráfica à funcionalidade do EA. 

Adquirindo dados de símbolos e de indicadores

Primeiro, você precisa garantir que os dados de símbolos e de indicadores sejam recebidos. Você coletará numa tabela os símbolos forex, com base nos valores do filtro no campo inserido Symbols filter. Isso é feito através do método CProgram::GetSymbols().

No início do método, na barra de progresso indique que o processo de adquisição de símbolos está agora em progresso. Inicialmente, não se sabe quantos símbolos haverá. Portanto, defina em 50% a faixa do indicador. Em seguida, libere a matriz de símbolos. Ao trabalhar com o aplicativo, você pode precisar criar outra lista de símbolos, daí que é necessário fazer isso toda vez que chamar o método CProgram::GetSymbols().

O filtro no campo inserido Symbols filter será usado somente se sua caixa de verificação estiver ativada e se no campo inserido houver rótulos inseridos por meio de uma vírgula. Se estas condições forem atendidas, esses rótulos serão recebidos na matriz como elementos separados, a fim de usá-los para procurar os símbolos desejados. Por precaução, limpe, tirando os símbolos especiais das extremidades de cada elemento.

O próximo passo é o ciclo de coleta de símbolos forex. Ele percorrerá a lista completa dos símbolos disponíveis no servidor. No início de cada iteração, obtenha o nome do símbolo e o remova da janela Observação do mercado. Assim, as listas na interface gráfica do programa e nesta janela irão coincidir. Em seguida, verifique se o símbolo recebido pertence à categoria de símbolos forex. Se você precisar de todos os símbolos, apenas comente ou exclua essa condição. Neste artigo, você trabalhará apenas com símbolos forex.

Se o filtro de nome estiver ativado, verifique se no ciclo existe no nome do símbolo obtido nesta iteração uma correspondência com os rótulos do campo inserido Symbols filter. Se não houver correspondência, adicione o símbolo à matriz.

Se nenhum símbolo for encontrado, à matriz será adicionado somente o símbolo atual do gráfico principal. Depois disso, todos os símbolos adicionados à matriz ficam visíveis na janela Observação do mercado. 

//+-------------------------------------------------------------------+
//| Classe para criar o aplicativo                                    |
//+-------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Símbolos para negociação
   string            m_symbols[];
   //---
private:
   //--- Obtendo símbolos
   void              GetSymbols(void);
  };
//+-------------------------------------------------------------------+
//| Obtendo os símbolos                                               |
//+-------------------------------------------------------------------+
void CProgram::GetSymbols(void)
  {
   m_progress_bar.LabelText("Get symbols...");
   m_progress_bar.Update(1,2);
   ::Sleep(5);
//--- Libere a matriz de símbolos
   ::ArrayFree(m_symbols);
//--- Matriz de elementos de linhas
   string elements[];
//--- Filtrando os nomes dos símbolos
   if(m_symb_filter.IsPressed())
     {
      string text=m_symb_filter.GetValue();
      if(text!="")
        {
         ushort sep=::StringGetCharacter(",",0);
         ::StringSplit(text,sep,elements);
         //---
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            //--- Limpeza de extremos
            ::StringTrimLeft(elements[e]);
            ::StringTrimRight(elements[e]);
           }
        }
     }
//--- Colete uma matriz de símbolos forex
   int symbols_total=::SymbolsTotal(false);
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtenha o nome do símbolo
      string symbol_name=::SymbolName(i,false);
      //--- Oculte-o na janela ‘Observação do mercado’
      ::SymbolSelect(symbol_name,false);
      //--- Se não houver um símbolo forex, vá para o próximo
      if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX)
         continue;
      //--- Filtrando os nomes dos símbolos
      if(m_symb_filter.IsPressed())
        {
         bool check=false;
         int elements_total=::ArraySize(elements);
         for(int e=0; e<elements_total; e++)
           {
            //--- Procurando correspondências no nome do símbolo
            if(::StringFind(symbol_name,elements[e])>-1)
              {
               check=true;
               break;
              }
           }
         //--- Ir para o próximo, caso o filtro não deixe passar
         if(!check)
            continue;
        }
      //--- Salve o símbolo numa matriz
      int array_size=::ArraySize(m_symbols);
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=symbol_name;
     }
//--- Se a matriz estiver vazia, defina o símbolo atual como padrão
   int array_size=::ArraySize(m_symbols);
   if(array_size<1)
     {
      ::ArrayResize(m_symbols,array_size+1);
      m_symbols[array_size]=::Symbol();
     }
//--- Exiba na janela Observação do mercado
   int selected_symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<selected_symbols_total; i++)
      ::SymbolSelect(m_symbols[i],true);
  }

Agora considere obter os identificadores de indicador para todos os símbolos selecionados usando o método CProgram::GetHandles(). Primeiro, configure a matriz de identificadores com o mesmo tamanho da matriz de símbolos. Os identificadores serão obtidos com o timeframe, como indicado na caixa de combinação Timeframes. Como você pode obter um valor de string a partir da caixa de combinação, ele deve ser convertido no tipo correspondente (ENUM_TIMEFRAMES). No ciclo, preencha a matriz de identificadores. Neste caso, é um indicador Stochastic com valores padrão. Em cada iteração, atualize a barra de progresso. No final do método, lembre-se do primeiro índice do identificador do gráfico que será exibido.

class CProgram : public CWndEvents
  {
private:
   //--- Identificador do indicador
   int               m_handles[];
   //--- Índice do identificador do gráfico atual
   int               m_current_handle_index;
   //---
private:
   //--- Obtendo os identificadores
   void              GetHandles(void);
  };
//+-------------------------------------------------------------------+
//| Obtendo os identificadores do indicador para todos os símbolos    |
//+-------------------------------------------------------------------+
void CProgram::GetHandles(void)
  {
//--- Defina o tamanho da matriz dos identificadores
   int symbols_total=::ArraySize(m_symbols);
   ::ArrayResize(m_handles,symbols_total);
//--- Obtendo o valor da lista suspensa da caixa de combinação
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Percorrendo a lista de símbolos
   for(int i=0; i<symbols_total; i++)
     {
      //--- Obtendo o identificador do indicador
      m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
      //--- Barra de progresso
      m_progress_bar.LabelText("Get handles: "+string(symbols_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((m_handles[i]!=WRONG_VALUE)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,symbols_total);
      ::Sleep(5);
     }
//--- Lembre-se do primeiro índice de identificador do gráfico
   m_current_handle_index=0;
  }

Obtenha os valores dos indicadores usando o método CProgram::GetIndicatorValues(). Descreva seu algoritmo. Primeiro, defina o tamanho da matriz - para os valores do indicador - igual ao tamanho da matriz de identificadores. No ciclo principal, percorra a matriz de identificadores e, em cada iteração, faça cinco tentativas de obter dados do indicador. Por precaução, verifique o identificador e, se não foi recebido antes, tente obtê-lo novamente. No final do ciclo principal, atualize a barra de progresso para ver em qual estágio está atualmente o programa.

class CProgram : public CWndEvents
  {
private:
   //--- Valores do indicador
   double            m_values[];
   //---
private:
   //--- Obtendo os valores dos indicadores em todos os símbolos
   void              GetIndicatorValues(void);
  };
//+-------------------------------------------------------------------+
//| Obtendo os valores dos indicadores em todos os símbolos           |
//+-------------------------------------------------------------------+
void CProgram::GetIndicatorValues(void)
  {
//--- Defina o tamanho
   int handles_total=::ArraySize(m_handles);
   ::ArrayResize(m_values,handles_total);
//--- Obtendo o valor da lista suspensa da caixa de combinação
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtendo os valores dos indicadores para todos os símbolos na lista
   for(int i=0; i<handles_total; i++)
     {
      //--- Tente 5 vezes obter dados
      int attempts=0;
      int received=0;
      while(attempts<5)
        {
         //--- Se o identificador for inválido, tente obtê-lo novamente
         if(m_handles[i]==WRONG_VALUE)
           {
            //--- Obtenha o identificador do indicador
            m_handles[i]=::iStochastic(m_symbols[i],StringToTimeframe(tf),5,3,3,MODE_SMA,STO_LOWHIGH);
            continue;
           }
         //--- Tente obter os valores do indicador
         double values[1];
         received=::CopyBuffer(m_handles[i],1,0,1,values);
         if(received>0)
           {
            //--- Salve o valor
            m_values[i]=values[0];
            break;
           }
         //--- Aumente o contador
         attempts++;
         ::Sleep(100);
        }
      //--- Barra de progresso
      m_progress_bar.LabelText("Get values: "+string(handles_total)+"/"+string(i)+" ["+m_symbols[i]+"] "+((received>0)? "ok" : "wrong")+"...");
      m_progress_bar.Update(i,handles_total);
      ::Sleep(5);
     }
  }

Formada a lista de símbolos e obtidos os dados dos indicadores, é necessário adicionar os valores dessas matrizes à tabela na guia Trade. Isso é feito através do método CProgram::RebuildingTables(). O número de símbolos pode ser alterado. Portanto, toda vez que esse método é chamado, a tabela é completamente reconstruída.

Primeiro, todas as linhas são excluídas, exceto uma — a de backup. Em seguida, as linhas são adicionadas à tabela de acordo com o número de símbolos. Logo, você os itera num ciclo e adiciona os valores previamente recebidos em matrizes separadas. Além dos próprios valores, você ainda precisa destacar o texto para ver quais sinais já foram formados de acordo com os valores do indicador. Destaque com azul valores inferiores ao mínimo do indicador Stochastic como os sinais de compra, e com vermelho - os superiores ao máximo como sinais de venda. Em cada iteração, é atualizada a barra de progresso à medida que o programa progride. No final do método, você precisa atualizar a tabela e as barras de rolagem.

//+-------------------------------------------------------------------+
//| Reorganizando a tabela de símbolos                                |
//+-------------------------------------------------------------------+
void CProgram::RebuildingTables(void)
  {
//--- Excluindo todas as linhas
   m_table_symb.DeleteAllRows();
//--- Defina o número de linhas de acordo com o número de símbolos
   int symbols_total=::ArraySize(m_symbols);
   for(int i=0; i<symbols_total-1; i++)
      m_table_symb.AddRow(i);
//--- Defina os valores para a primeira coluna
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      //--- Defina os valores
      m_table_symb.SetValue(0,r,m_symbols[r]);
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      //--- Definas as cores
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr);
      m_table_symb.TextColor(1,r,clr);
      //--- Atualize a barra de progresso
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
//--- Atualizar tabela
   m_table_symb.Update(true);
   m_table_symb.GetScrollVPointer().Update(true);
   m_table_symb.GetScrollHPointer().Update(true);
  }

Todos os métodos descritos acima são chamados no método CProgram::RequestData(). Para ele é transferido um único argumento cujo valor permite que você verifique o identificador do controle — botão Request. Após esta verificação, oculte temporariamente a tabela e torne a barra de progresso visível. Em seguida, todos os métodos acima são sucessivamente chamados para obter os dados e inseri-los na tabela. Em seguida, oculte a barra de progresso, defina o intervalo de tempo a partir da caixa de combinação para o gráfico e torne as últimas alterações visíveis. 

//+-------------------------------------------------------------------+
//| Solicitando dados                                                 |
//+-------------------------------------------------------------------+
bool CProgram::RequestData(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_request.Id())
      return(false);
//--- Ocultando a tabela
   m_table_symb.Hide();
//--- Mostrando o progresso
   m_progress_bar.Show();
   m_chart.Redraw();
//--- Inicialização do gráfico e da tabela
   GetSymbols();
   GetHandles();
   GetIndicatorValues();
   RebuildingTables();
//--- Ocultado o progresso
   m_progress_bar.Hide();
//--- Obtenha o valor a partir da caixa de combinação
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtenha o ponteiro do gráfico usando o índice
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
//--- Mostrando a tabela
   m_table_symb.Show();
   m_chart.Redraw();
   return(true);
  }

Adquirindo dados de posições abertas

Quando o EA é carregado no gráfico, você precisa determinar imediatamente se há posições abertas para exibir essas informações, na tabela, na guia Positions. Você pode ver a lista de todas as posições, na janela Caixa de Ferramentas, na guia Negociação. Para fechar apenas uma posição do símbolo, você precisa clicar na cruz, na célula da tabela, na coluna Profit. Se houver várias posições no símbolo (numa conta de cobertura) e você precisar fechar tudo, serão necessários vários passos. Na tabela de posições da interface gráfica, faça que numa linha para cada símbolo haja uma informação cumulativa sobre o resultado atual, sobre a carga do depósito e sobre o preço médio. Além disso, adicione a capacidade de fechar simultaneamente todas as posições no símbolo especificado. 

Primeiro, considere o método CProgram::GetPositionsSymbols(), para obter a lista de símbolos das posições abertas. Para ele é transferida uma matriz dinâmica vazia em que serão obtidos os símbolos. Em seguida, no ciclo, passe por todas as posições abertas. Em cada iteração, obtenha o nome do símbolo da posição e o adicione à variável de string por meio do separador ",". Antes de adicionar o nome do símbolo, primeiro verifique se já está nesta linha

Concluído o ciclo e formada a string de símbolos, na matriz carregada obtenha os elementos dessa string e retorne o número de símbolos recebidos.

//+-------------------------------------------------------------------+
//| Obtendo numa matriz os símbolos das posições abertas              |
//+-------------------------------------------------------------------+
int CProgram::GetPositionsSymbols(string &symbols_name[])
  {
   string symbols="";
//--- Percorra pela primeira vez o ciclo e obtenha os símbolos das posições abertas
   int positions_total=::PositionsTotal();
   for(int i=0; i<positions_total; i++)
     {
      //--- Selecione a posição e obtenha seu símbolo
      string position_symbol=::PositionGetSymbol(i);
      //--- Se houver um nome de símbolo
      if(position_symbol=="")
         continue;
      //--- Se não existir essa linha, adicionamo-la
      if(::StringFind(symbols,position_symbol,0)==WRONG_VALUE)
         ::StringAdd(symbols,(symbols=="")? position_symbol : ","+position_symbol);
     }
//--- Obtemos os elementos da linha pelo separador
   ushort u_sep=::StringGetCharacter(",",0);
   int symbols_total=::StringSplit(symbols,u_sep,symbols_name);
//--- Retornamos o número de símbolos
   return(symbols_total);
  }

Agora que temos uma matriz de símbolos, pode-se obter dados para cada posição agregada, simplesmente especificando o nome do símbolo. Considere os métodos para obter indicadores em todas as colunas de dados na tabela de posições.

Para obter o número de posições no símbolo especificado, use o método CProgram::PositionsTotal(). No ciclo, ele percorre todas as posições e considera apenas aquelas que coincidem com o símbolo especificado no argumento do método.

//+-------------------------------------------------------------------+
//| Número de trades com as propriedades especificadas                |
//+-------------------------------------------------------------------+
int CProgram::PositionsTotal(const string symbol)
  {
//--- Contador de posições
   int pos_counter=0;
//--- Verifique se existe uma posição com as propriedades especificadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Se você não consegue selecionar a posição, vá para a próxima
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Aumentar o contador
      pos_counter++;
     }
//--- Retorne o número de posições
   return(pos_counter);
  }

Você pode obter o volume de posições usando o método CProgram::PositionsVolumeTotal(). Além do símbolo para o qual você precisa obter o volume total de posições, para método também pode ser transferido seu tipo. Mas o tipo de posições é um argumento opcional neste método. Por padrão, é especificado o valor WRONG_VALUE. Se o tipo não for especificado, não será usada essa verificação e o método retornará o volume total para todas as posições. 

//+-------------------------------------------------------------------+
//| Volume total de posições com as propriedades especificadas        |
//+-------------------------------------------------------------------+
double CProgram::PositionsVolumeTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
//--- Contador de volume
   double volume_counter=0;
//--- Verifique se existe uma posição com as propriedades especificadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Se você não consegue selecionar a posição, vá para a próxima
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Se você precisa verificar o tipo
      if(type!=WRONG_VALUE)
        {
         //--- Se o tipo não corresponder, vá para a próxima posição
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      //--- Soma do volume
      volume_counter+=::PositionGetDouble(POSITION_VOLUME);
     }
//--- Retorne o volume
   return(volume_counter);
  }

O método CProgram::PositionsFloatingProfitTotal() permite obter o lucro flutuante total das posições para o símbolo especificado. O cálculo leva em consideração o swap acumulado das posições. Aqui, você também pode especificar como argumento opcional o tipo de posições para as quais deseja receber lucro flutuante. Assim, o método se torna universal. 

//+-------------------------------------------------------------------+
//| Lucro flutuante total de posições com propriedades especificadas  |
//+-------------------------------------------------------------------+
double CProgram::PositionsFloatingProfitTotal(const string symbol,const ENUM_POSITION_TYPE type=WRONG_VALUE)
  {
//--- Contador de lucro atual
   double profit_counter=0.0;
//--- Verifique se existe uma posição com as propriedades especificadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Se você não consegue selecionar a posição, vá para a próxima
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Se você precisa verificar o tipo
      if(type!=WRONG_VALUE)
        {
         //--- Se o tipo não corresponder, vá para a próxima posição
         if(type!=(ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE))
            continue;
        }
      //--- Soma o lucro atual + swap acumulado
      profit_counter+=::PositionGetDouble(POSITION_PROFIT)+::PositionGetDouble(POSITION_SWAP);
     }
//--- Retornando o resultado
   return(profit_counter);
  }

O preço médio é calculado pelo método CProgram::PositionAveragePrice(). No ciclo, para cada posição do símbolo, obtenha o preço e o volume. Em seguida, some o produto desses valores e separadamente — o volume de posições. Concluído o ciclo, para obter o preço médio das posições do símbolo especificado, é necessário dividir a soma do produto de preços e de volumes pela soma dos volumes. É esse valor que retorna o método exibido.

//+-------------------------------------------------------------------+
//| Preço médio da posição                                            |
//+-------------------------------------------------------------------+
double CProgram::PositionAveragePrice(const string symbol)
  {
//--- Para cálculo do preço médio
   double sum_mult    =0.0;
   double sum_volumes =0.0;
//--- Verifique se existe uma posição com as propriedades especificadas
   int positions_total=::PositionsTotal();
   for(int i=positions_total-1; i>=0; i--)
     {
      //--- Se você não consegue selecionar a posição, vá para a próxima
      if(symbol!=::PositionGetSymbol(i))
         continue;
      //--- Obtenha o preço e o volume da posição
      double pos_price  =::PositionGetDouble(POSITION_PRICE_OPEN);
      double pos_volume =::PositionGetDouble(POSITION_VOLUME);
      //--- Some os valores intermediários
      sum_mult+=(pos_price*pos_volume);
      sum_volumes+=pos_volume;
     }
//--- Evitando a divisão por zero
   if(sum_volumes<=0)
      return(0.0);
//--- Retornando o preço médio
   return(::NormalizeDouble(sum_mult/sum_volumes,(int)::SymbolInfoInteger(symbol,SYMBOL_DIGITS)));
  }

Considere o valor da carga do depósito. Para obtê-lo, você precisa chamar o método genérico CProgram::DepositLoad(). Dependendo dos argumentos transmitidos, você pode obter valores em termos percentuais ou na moeda da conta. Além disso, você pode obter a carga geral do depósito para todas as posições abertas ou apenas para o símbolo especificado. 

O método tem quatro argumentos, três dos quais são opcionais. Se o primeiro argumento for false, o método retornará o valor na moeda de depósito. Se o valor for true, será retornado o valor em termos percentuais, em relação aos fundos disponíveis

Se você deseja obter a carga atual sobre o depósito do símbolo especificado, então, quando a moeda da conta é diferente da moeda base do símbolo, no cálculo é preciso ter o preço da posição. Se houver várias posições abertas no símbolo, deve ser transferido o preço médio. 

//+-------------------------------------------------------------------+
//| Carga do depósito                                                 |
//+-------------------------------------------------------------------+
double CProgram::DepositLoad(const bool percent_mode,const double price=0.0,const string symbol="",const double volume=0.0)
  {
//--- Calcule o valor atual da carga de depósito
   double margin=0.0;
//--- Carga total na conta
   if(symbol=="" || volume==0.0)
      margin=::AccountInfoDouble(ACCOUNT_MARGIN);
//--- Carga no símbolo especificado
   else
     {
      //--- Obtenha os dados para calcular a margem
      double leverage         =((double)::AccountInfoInteger(ACCOUNT_LEVERAGE)==0)? 1 : (double)::AccountInfoInteger(ACCOUNT_LEVERAGE);
      double contract_size    =::SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);
      string account_currency =::AccountInfoString(ACCOUNT_CURRENCY);
      string base_currency    =::SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);
      //--- Se a moeda da conta de negociação for a mesma que a moeda base do símbolo
      if(account_currency==base_currency)
         margin=(volume*contract_size)/leverage;
      else
         margin=(volume*contract_size)/leverage*price;
     }
//--- Obtenha os fundos atuais
   double equity=(::AccountInfoDouble(ACCOUNT_EQUITY)==0)? 1 : ::AccountInfoDouble(ACCOUNT_EQUITY);
//--- Retornando a carga do depósito
   return((!percent_mode)? margin : (margin/equity)*100);
  }

Todos os métodos para obter valores são adicionados à tabela chamando o método CProgram::SetValuesToPositionsTable(). Para o método você precisa transferir a matriz dos símbolos desejados. Primeiro, verifique se a matriz transmitida não é menor do que o número de linhas na tabela. Em seguida, no ciclo, percorra todas as linhas da tabela, recebendo os indicadores sequencialmente e enchendo as células da tabela com eles. Além dos valores propriamente ditos, também defina a cor do texto: verde para positivo, vermelho para negativo, cinza para zero. Repare que a carga do depósito será exibida para cada símbolo separadamente através de uma barra («/») em termos monetários e percentuais

//+-------------------------------------------------------------------+
//| Definindo os valores na tabela de posições                        |
//+-------------------------------------------------------------------+
void CProgram::SetValuesToPositionsTable(string &symbols_name[])
  {
//--- Verificar se está fora do intervalo
   uint symbols_total =::ArraySize(symbols_name);
   uint rows_total    =m_table_positions.RowsTotal();
   if(symbols_total<rows_total)
      return;
//--- Obtenha na tabela os valores
   for(uint r=0; r<rows_total; r++)
     {
      int    positions_total =PositionsTotal(symbols_name[r]);
      double pos_volume      =PositionsVolumeTotal(symbols_name[r]);
      double buy_volume      =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_volume     =PositionsVolumeTotal(symbols_name[r],POSITION_TYPE_SELL);
      double pos_profit      =PositionsFloatingProfitTotal(symbols_name[r]);
      double buy_profit      =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_BUY);
      double sell_profit     =PositionsFloatingProfitTotal(symbols_name[r],POSITION_TYPE_SELL);
      double average_price   =PositionAveragePrice(symbols_name[r]);
      string deposit_load    =::DoubleToString(DepositLoad(false,average_price,symbols_name[r],pos_volume),2)+"/"+
                              ::DoubleToString(DepositLoad(true,average_price,symbols_name[r],pos_volume),2)+"%";
      //--- Defina os valores
      m_table_positions.SetValue(0,r,symbols_name[r]);
      m_table_positions.SetValue(1,r,(string)positions_total);
      m_table_positions.SetValue(2,r,::DoubleToString(pos_volume,2));
      m_table_positions.SetValue(3,r,::DoubleToString(buy_volume,2));
      m_table_positions.SetValue(4,r,::DoubleToString(sell_volume,2));
      m_table_positions.SetValue(5,r,::DoubleToString(pos_profit,2));
      m_table_positions.SetValue(6,r,::DoubleToString(buy_profit,2));
      m_table_positions.SetValue(7,r,::DoubleToString(sell_profit,2));
      m_table_positions.SetValue(8,r,deposit_load);
      m_table_positions.SetValue(9,r,::DoubleToString(average_price,(int)::SymbolInfoInteger(symbols_name[r],SYMBOL_DIGITS)));
      //--- Defina a cor
      m_table_positions.TextColor(3,r,(buy_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(4,r,(sell_volume>0)? clrBlack : clrLightGray);
      m_table_positions.TextColor(5,r,(pos_profit!=0)? (pos_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(6,r,(buy_profit!=0)? (buy_profit>0)? clrGreen : clrRed : clrLightGray);
      m_table_positions.TextColor(7,r,(sell_profit!=0)?(sell_profit>0)? clrGreen : clrRed : clrLightGray);
     }
  }

Para atualizar a tabela de posições após as alterações feitas, faça um método separado, já que ele terá que ser chamado em vários lugares no programa.

//+-------------------------------------------------------------------+
//| Atualizando a tabela de posições                                  |
//+-------------------------------------------------------------------+
void CProgram::UpdatePositionsTable(void)
  {
//--- Atualizar tabela
   m_table_positions.Update(true);
   m_table_positions.GetScrollVPointer().Update(true);
   m_table_positions.GetScrollHPointer().Update(true);
  }

Inicialize a tabela de posições no método CProgram::InitializePositionsTable(). Nele são chamados todos os métodos discutidos nesta seção. Primeiro, obtenha os símbolos de posições abertas na matriz. Em seguida, prepare a tabela, para fazer isso, remova todas as linhas e adicione novas pelo número de símbolos recebidos na matriz. Se houver posições abertas, você deve primeiro estabelecer as células da primeira coluna como botões. Para fazer isso, defina o tipo apropriado (CELL_BUTTON) e adicione a imagem. Depois disso, nas células são definidos os valores, enquanto a tabela é atualizada.

//+-------------------------------------------------------------------+
//| Inicializando a tabela de posições                                |
//+-------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\close_black.bmp"
//---
void CProgram::InitializePositionsTable(void)
  {
//--- Obtenha os símbolos das posições abertas
   string symbols_name[];
   int symbols_total=GetPositionsSymbols(symbols_name);
//--- Excluindo todas as linhas
   m_table_positions.DeleteAllRows();
//--- Defina o número de linhas de acordo com o número de símbolos
   for(int i=0; i<symbols_total-1; i++)
      m_table_positions.AddRow(i);
//--- Se houver posições
   if(symbols_total>0)
     {
      //--- Matriz de imagens para os botões
      string button_images[1]={"Images\\EasyAndFastGUI\\Controls\\close_black.bmp"};
      //--- Defina os valores na terceira coluna
      for(uint r=0; r<(uint)symbols_total; r++)
        {
         //--- Defina o tipo e as imagens
         m_table_positions.CellType(0,r,CELL_BUTTON);
         m_table_positions.SetImages(0,r,button_images);
        }
      //--- Defina os valores na tabela
      SetValuesToPositionsTable(symbols_name);
     }
//--- Atualizar tabela
   UpdatePositionsTable();
  }

Inicializando tabelas com dados

Após a criação da interface gráfica do usuário, é necessário inicializar a tabela de símbolos e a tabela de posições. Você pode saber quando é concluída a formação da GUI, usando o evento personalizado ON_END_CREATE_GUI no processador de eventos. Para inicialização da tabela de símbolos, você deve chamar o método CProgram::RequestData(), que já foi considerado anteriormente. Para que o método funcione com sucesso, você precisa passar para ele o identificador do elemento do botão Request.

//+-------------------------------------------------------------------+
//| Manipulador de eventos                                            |
//+-------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Evento de criação da GUI
   if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI)
     {
      //--- Pedido de dados
      RequestData(m_request.Id());
      //--- Inicialização da tabela de posições
      InitializePositionsTable();
      return;
     }
  }

Como resultado, após carregado o programa no gráfico, a tabela de símbolos fica assim:

 Fig. 3 – Tabela de símbolos inicializada.

Fig. 1. Tabela de símbolos inicializada.

Se antes de carregado o programa na conta já havia posições abertas, a tabela de posições fica assim:

 Fig. 4 – Tabela de posições inicializada.

Fig. 2. Tabela de posições inicializada.

Atualizando as tabelas em tempo real

Como o preço se move o tempo todo, durante o pregão os dados nas tabelas devem ser constantemente recalculados. Você vai atualizar a tabela em determinados intervalos no temporizador do programa. Para garantir que os elementos sejam atualizados em intervalos diferentes, você pode usar objetos do tipo CTimeCounter. Esta classe está na compilação da biblioteca EasyAndFast. Para usá-la no projeto, basta conectar o arquivo com seu conteúdo:

//+-------------------------------------------------------------------+
//|                                                       Program.mqh |
//|                         Copyright 2018, MetaQuotes Software Corp. |
//|                                               http://www.mql5.com |
//+-------------------------------------------------------------------+
...
#include <EasyAndFastGUI\TimeCounter.mqh>
...

Nosso EA precisa de três contadores de tempo para atualizar os dados na barra de status e nas tabelas. 

Para configurar os contadores, basta declarar objetos do tipo CTimeCounter e definir seus parâmetros no construtor (veja a listagem abaixo). O primeiro parâmetro é a frequência do temporizador, enquanto o segundo — o intervalo de tempo após o qual o método CTimeCounter::CheckTimeCounter() retorna o valor true. Depois disso, o contador é reiniciado e iniciado novamente.

//+-------------------------------------------------------------------+
//| Classe para criar o aplicativo                                    |
//+-------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
...
   //--- Contadores de tempo
   CTimeCounter      m_counter1;
   CTimeCounter      m_counter2;
   CTimeCounter      m_counter3;
...
  };
//+-------------------------------------------------------------------+
//| Constructor                                                       |
//+-------------------------------------------------------------------+
CProgram::CProgram(void)
  {
//--- Defina os parâmetros para os contadores de tempo
   m_counter1.SetParameters(16,500);
   m_counter2.SetParameters(16,5000);
   m_counter3.SetParameters(16,1000);
...
  }

Para atualizar a barra de status no bloco do primeiro contador no temporizador do programa, adicione o código mostrado abaixo. Para exibir as alterações, não esqueça atualizar cada elemento separadamente.

//+-------------------------------------------------------------------+
//| Temporizador                                                      |
//+-------------------------------------------------------------------+
void CProgram::OnTimerEvent(void)
  {
...
//--- Atualizando os itens na barra de status
   if(m_counter1.CheckTimeCounter())
     {
      //--- Defina os valores
      m_status_bar.SetValue(1,"Deposit load: "+::DoubleToString(DepositLoad(false),2)+"/"+::DoubleToString(DepositLoad(true),2)+"%");
      m_status_bar.SetValue(2,::TimeToString(::TimeTradeServer(),TIME_DATE|TIME_SECONDS));
      //--- Atualize os itens
      m_status_bar.GetItemPointer(1).Update(true);
      m_status_bar.GetItemPointer(2).Update(true);
     }
...
  }

Para acelerar a atualização da tabela em que é necessário substituir apenas os valores dos indicadores, use o método separado — CProgram::UpdateSymbolsTable(). Antes de chamá-lo, você deve primeiro atualizar a matriz de valores do indicador. Em seguida, é chamado o método CProgram::UpdateSymbolsTable(). Aqui, em cada iteração, é verificado se a matriz está fora do intervalo. Se a verificação for passada, atualize as células da segunda coluna da tabela e corrija a cor do texto. O processo de aquisição de dados e inicialização de tabelas é mostrado na barra de progresso.

//+-------------------------------------------------------------------+
//| Atualizando a tabela de símbolos                                  |
//+-------------------------------------------------------------------+
void CProgram::UpdateSymbolsTable(void)
  {
   uint values_total=::ArraySize(m_values);
//--- Defina os valores na tabela de símbolos
   uint rows_total=m_table_symb.RowsTotal();
   for(uint r=0; r<(uint)rows_total; r++)
     {
      //--- Pare o ciclo, se a matriz estiver fora do intervalo
      if(r>values_total-1 || values_total<1)
         break;
      //--- Defina os valores
      m_table_symb.SetValue(1,r,::DoubleToString(m_values[r],2));
      //--- Definas as cores
      color clr=(m_values[r]>(double)m_up_level.GetValue())? clrRed :(m_values[r]<(double)m_down_level.GetValue())? C'85,170,255' : clrBlack;
      m_table_symb.TextColor(0,r,clr,true);
      m_table_symb.TextColor(1,r,clr,true);
      //--- Atualize a barra de progresso
      m_progress_bar.LabelText("Initialize tables: "+string(rows_total)+"/"+string(r)+"...");
      m_progress_bar.Update(r,rows_total);
      ::Sleep(5);
     }
//--- Atualizar tabela
   m_table_symb.Update();
  }

O bloco do segundo contador de tempo para atualizar a tabela de símbolos é mostrado abaixo. Assim, a cada 5 segundos o programa receberá os valores atuais dos indicadores para todos os símbolos e atualizará sua tabela.

void CProgram::OnTimerEvent(void)
  {
...
//--- Atualizando a tabela de símbolos
   if(m_counter2.CheckTimeCounter())
     {
      //--- Mostrando o progresso
      m_progress_bar.Show();
      m_chart.Redraw();
      //--- Atualizando os valores na tabela
      GetIndicatorValues();
      UpdateSymbolsTable();
      //--- Ocultado o progresso
      m_progress_bar.Hide();
      m_chart.Redraw();
     }
...
  }

Para atualizar a tabela de posições no cronômetro, primeiro obtenha a matriz de símbolos das posições abertas. Em seguida, atualize a tabela com os dados reais. Classifique-a pela mesma coluna e direção, como antes da atualização. Aplique à tabela para exibir as alterações feitas.

void CProgram::OnTimerEvent(void)
  {
...
//--- Atualizando a tabela de posições
   if(m_counter3.CheckTimeCounter())
     {
      //--- Obtenha os símbolos das posições abertas
      string symbols_name[];
      int symbols_total=GetPositionsSymbols(symbols_name);
      //--- Atualizando os valores na tabela
      SetValuesToPositionsTable(symbols_name);
      //--- Classifique se isso já foi feito pelo usuário antes da atualização
      m_table_positions.SortData((uint)m_table_positions.IsSortedColumnIndex(),m_table_positions.IsSortDirection());
      //--- Atualizar tabela
      UpdatePositionsTable();
     }
  }

Manipulando os eventos de controle

Nesta seção, vamos considerar os métodos de processamento de eventos que são gerados ao interagir com a interface gráfica do usuário de nosso aplicativo. Já consideramos o método CProgram::RequestData() para obter símbolos e dados de indicadores. Se a inicialização não for a primeira, o método é chamado ao pressionar o botão Request a qualquer momento durante a execução do programa. Quando o botão é pressionado, é gerado o evento personalizado com identificador ON_CLICK_BUTTON.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de pressionamento de botões
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Pedido de dados
      if(RequestData(lparam))
         return;
      //---
      return;
     }
...
  }

No gif abaixo, vemos que na tabela é gerada uma lista com símbolos forex contendo USD. Em seguida, é rapidamente gerada uma lista de símbolos contendo EUR. Para fazer isso, no campo inserido Filtro Symbols filter basta digitar o texto «EUR» e clicar no botão Request. Se você quiser ver todos os símbolos disponíveis no servidor com as moedas USD e EUR, insira estas moedas usando vírgulas: «USD,EUR».

 Fig. 5 – Gerando uma lista de símbolos forex

Fig. 3. Gerando uma lista de símbolos forex.

A geração da lista de símbolos forex e a obtenção dos identificadores dos indicadores são realizadas de acordo com o período indicado na caixa de combinação Timeframes. Se você selecionar outro timeframe na lista suspensa, precisará obter novos identificadores e atualizar os valores na tabela. Para fazer isso, é necessário o método CProgram::ChangePeriod(). Se o identificador da caixa de combinação tiver chegado, primeiro atualize o timeframe no objeto gráfico. Em seguida, obtenha os identificadores e os dados do indicador para todos os símbolos na tabela, após isso, ela é atualizada para exibir as alterações feitas. 

//+-------------------------------------------------------------------+
//| Alterando o timeframe                                             |
//+-------------------------------------------------------------------+
bool CProgram::ChangePeriod(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_timeframes.Id())
      return(false);
//--- Obtenha o valor a partir da caixa de combinação
   string tf=m_timeframes.GetListViewPointer().SelectedItemText();
//--- Obtenha o ponteiro do gráfico usando o índice
   m_sub_chart1.GetSubChartPointer(0).Period(StringToTimeframe(tf));
   m_sub_chart1.ResetCharts();
//--- Mostrando o progresso
   m_progress_bar.Show();
   m_chart.Redraw();
//--- Obtenha os identificadores e os dados dos indicadores
   GetHandles();
   GetIndicatorValues();
//--- Atualize a tabela
   UpdateSymbolsTable();
//--- Ocultado o progresso
   m_progress_bar.Hide();
   m_chart.Redraw();
   return(true);
  }

Ao selecionar o item na lista suspensa, são gerados os eventos personalizados com o identificador ON_CLICK_COMBOBOX_ITEM:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de seleção de item na caixa de combinação
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      //--- Alteração do timeframe
      if(ChangePeriod(lparam))
         return;
      //---
      return;
     }
...
  }

Veja a alteração do timeframe e a obtenção de novos dados:

 Fig. 6 – Alterando o timeframe.

Fig. 4 Alterando o timeframe.

Agora considere como alterar rapidamente o símbolo no objeto gráfico. Na tabela de símbolos, a primeira coluna já possui os nomes dos símbolos. Portanto, você pode alternar entre eles, simplesmente selecionando as linhas na tabela. Ao clicar numa linha, é chamado o método CProgram::ChangeSymbol(). Aqui, primeiro, é verificado o identificador da tabela de símbolos. Então, é preciso verificar se a linha na tabela está selecionada, uma vez que a seleção das linhas é removida por um segundo clique. Após essa verificação, você precisará salvar o índice da linha selecionada, como o índice do identificador que poderá usar depois para definir o indicador no gráfico (veja mais adiante no artigo).

Após recebido o símbolo pelo índice da linha selecionada na primeira coluna da tabela, defina-o no objeto gráfico. Como informação adicional, exiba a descrição completa do símbolo no primeiro item da barra de status. Ao desmarcar uma linha da tabela, o texto é definido com o valor padrão.

//+-------------------------------------------------------------------+
//| Alterando o símbolo                                               |
//+-------------------------------------------------------------------+
bool CProgram::ChangeSymbol(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_table_symb.Id())
      return(false);
//--- Sair se a linha estiver selecionada
   if(m_table_symb.SelectedItem()==WRONG_VALUE)
     {
      //--- Exiba a descrição completa do símbolo na barra de status
      m_status_bar.SetValue(0,"For Help, press F1");
      m_status_bar.GetItemPointer(0).Update(true);
      return(false);
     }
//--- Salve o índice do identificador
   m_current_handle_index=m_table_symb.SelectedItem();
//--- Obtenha o símbolo
   string symbol=m_table_symb.GetValue(0,m_current_handle_index);
//--- Atualizar gráfico
   m_sub_chart1.GetSubChartPointer(0).Symbol(symbol);
   m_sub_chart1.ResetCharts();
//--- Exiba a descrição completa do símbolo na barra de status
   m_status_bar.SetValue(0,::SymbolInfoString(symbol,SYMBOL_DESCRIPTION));
   m_status_bar.GetItemPointer(0).Update(true);
   m_chart.Redraw();
   return(true);
  }

Quando você seleciona uma linha na tabela, é gerado o evento personalizado com identificador ON_CLICK_LIST_ITEM. Você pode alternar os símbolos, usando as teclas «Up», «Down», «Home» e «End». Neste caso, é gerado o evento CHARTEVENT_KEYDOWN. Como o método para seu processamento foi considerado no artigo anterior, não vamos nos debruçar sobre ele aqui.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de seleção do item na lista/tabela
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Alteração do símbolo
      if(ChangeSymbol(lparam))
         return;
      //---
      return;
     }
//--- Pressionamento de tecla
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Seleção de resultados usando as teclas
      if(SelectingResultsUsingKeys(lparam))
         return;
      //---
      return;
     }
...
  }

Como resultado do processamento destes eventos, você verá algo assim:

 Fig. 7 – Alternando símbolos.

Fig. 5. Alternando símbolos.

Às vezes você precisa ver no gráfico o indicador pelo qual recebemos sinais. A caixa de verificação Show indicator é projetada para ativar a exibição do indicador. O método CProgram::ShowIndicator() responde pela interação com ela. Aqui, você também precisa verificar o ID do elemento e a saída para além dos limites do intervalo da matriz dos identificadores. Para adicionar ou remover o indicador de um objeto gráfico, é necessário o identificador para este gráfico. Em seguida, se a caixa de verificação estiver ativada, o indicador deverá ser adicionado ao gráfico. Como o indicador será sempre um, defina o número da subjanela como 1. Para casos mais complexos, você precisa determinar o número de indicadores no gráfico. 

//+-------------------------------------------------------------------+
//| Visibilidade do indicador                                         |
//+-------------------------------------------------------------------+
bool CProgram::ShowIndicator(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_show_indicator.Id())
      return(false);
//--- Verificando se a matriz está fora do intervalo
   int handles_total=::ArraySize(m_handles);
   if(m_current_handle_index<0 || m_current_handle_index>handles_total-1)
      return(true);
//--- Obtenha o identificador do gráfico
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
//--- Número da subjanela do indicador
   int subwindow =1;
//--- Obtenha o ponteiro do gráfico usando o índice
   if(m_show_indicator.IsPressed())
     {
      //--- Adicione o indicador ao gráfico
      ::ChartIndicatorAdd(sub_chart_id,subwindow,m_handles[m_current_handle_index]);
     }
   else
     {
      //--- Remova o indicador do gráfico
      ::ChartIndicatorDelete(sub_chart_id,subwindow,ChartIndicatorName(sub_chart_id,subwindow,0));
     }
//--- Atualizar gráfico
   m_chart.Redraw();
   return(true);
  }

Ao interagir com a caixa de verificação, é gerado o evento personalizado ON_CLICK_CHECKBOX:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de pressionamento no elemento "Caixa de verificação"
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_CHECKBOX)
     {
      //--- Se pressionada a caixa de verificação "Show indicator"
      if(ShowIndicator(lparam))
         return;
      //---
      return;
     }
  }

Veja como funciona durante o trabalho:

 Fig. 8 – Exibição do indicador.

Fig. 6. Exibição do indicador.

Mais dois controles estão relacionados com o indicador na interface gráfica do usuário do EA. Eles são os campos inseridos numéricos dos níveis do indicador Stochastic: Up level e Down level. Por padrão, eles são definidos como 80 e 20. Se os valores dos indicadores em cada símbolo excederem esses limites para cima e para baixo, o texto nas células da tabela de símbolos mudará de preto para azul para o nível superior e de preto para vermelho para o inferior. Se você alterar os valores nestes campos inseridos, na seguinte atualização (a cada cinco segundos) será alterada a exibição de cores. 

Veja como isso funciona quando você altera valores de 80/20 para 90/10 e vice-versa:

 Fig. 9 – Alterando os níveis de sinal do indicador.

Fig. 7. Alterando os níveis de sinal do indicador.

Vários controles são projetados para trabalhar com as propriedades do gráfico, isto é:

Os métodos de processamento a partir das caixas de verificação Date scale e Price scale são muito semelhantes. Neles, dependendo do estado da caixa de verificação, é ativada ou desativada a propriedade correspondente do gráfico. O método CStandardChart::ResetCharts() desloca o gráfico para o final.

//+-------------------------------------------------------------------+
//| Visibilidade da escala de tempo                                   |
//+-------------------------------------------------------------------+
bool CProgram::DateScale(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_date_scale.Id())
      return(false);
//--- Obtenha o ponteiro do gráfico usando o índice
   m_sub_chart1.GetSubChartPointer(0).DateScale(m_date_scale.IsPressed());
   m_sub_chart1.ResetCharts();
//--- Atualizar gráfico
   m_chart.Redraw();
   return(true);
  }
//+-------------------------------------------------------------------+
//| Visibilidade da escala de tempo                                   |
//+-------------------------------------------------------------------+
bool CProgram::PriceScale(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_price_scale.Id())
      return(false);
//--- Obtenha o ponteiro do gráfico usando o índice
   m_sub_chart1.GetSubChartPointer(0).PriceScale(m_price_scale.IsPressed());
   m_sub_chart1.ResetCharts();
//--- Atualizar gráfico
   m_chart.Redraw();
   return(true);
  }

Para gerenciar a escala do gráfico, é usado o método CProgram::ChartScale(). Aqui, se o valor no campo inserido for alterado, ele será atribuído ao gráfico.

//+-------------------------------------------------------------------+
//| Escala do gráfico                                                 |
//+-------------------------------------------------------------------+
bool CProgram::ChartScale(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_chart_scale.Id())
      return(false);
//--- Defina a escala
   if((int)m_chart_scale.GetValue()!=m_sub_chart1.GetSubChartPointer(0).Scale())
      m_sub_chart1.GetSubChartPointer(0).Scale((int)m_chart_scale.GetValue());
//--- Atualização
   m_chart.Redraw();
   return(true);
  }

A alteração do valor no campo inserido Chart scale é processado na chegada de eventos personalizados com identificadores ON_CLICK_BUTTON e ON_END_EDIT.

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de pressionamento de botões
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      //--- Escala do gráfico
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
//--- Eventos do final da mudança do valor no campo inserido
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      //--- Escala do gráfico
      if(ChartScale(lparam))
         return;
      //---
      return;
     }
  }

Abaixo é mostrado o código do método CProgram::ChartShift() para ativar no gráfico o recuo à direita. Nele, após verificar o ID do elemento, primeiro obtenha o identificador do gráfico, em seguida, usando-o como chave de acesso, você pode definir o recuo (CHART_SHIFT).

//+-------------------------------------------------------------------+
//| Deslocando o gráfico                                              |
//+-------------------------------------------------------------------+
bool CProgram::ChartShift(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_chart_shift.Id())
      return(false);
//--- Obtenha o identificador do gráfico
   long sub_chart_id=m_sub_chart1.GetSubChartPointer(0).GetInteger(OBJPROP_CHART_ID);
//--- Defina o recuo no lado direito do gráfico
   ::ChartSetInteger(sub_chart_id,CHART_SHIFT,true);
   m_sub_chart1.ResetCharts();
   return(true);
  }

Veja como fica:

 Fig. 10 – Gerenciando as propriedades do gráfico.

Fig. 8.  Gerenciando as propriedades do gráfico.

Métodos para realizar operações de negociação

Vou exemplificar como você pode fácil e rapidamente vincular métodos de negociação à interface gráfica do EA. Além de visualizar dados, nosso EA realizará operações de negociação. É mais fácil trabalhar, quando tudo está num só lugar, alternando rapidamente gráficos. Como exemplo para operações de negociação, usaremos os recursos da biblioteca padrão. No entanto, você pode conectar outras bibliotecas de negociação. 

Conecte ao projeto o arquivo Trade.mqh com classe CTrade e declare a instância desta classe:

//--- Classe para operações de negociação
#include <Trade\Trade.mqh>
//+-------------------------------------------------------------------+
//| Classe para criar o aplicativo                                    |
//+-------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Operações de negociação
   CTrade            m_trade;
  };

Defina o modo assíncrono para conclusão do trade, a fim de que o programa não espere o resultado de cada operação de negociação. Além disso, defina a derrapagem máxima permitida. Ou seja, os trades serão concluídos após qualquer desvio em relação ao preço especificado na operação de negociação.

//+-------------------------------------------------------------------+
//| Constructor                                                       |
//+-------------------------------------------------------------------+
CProgram::CProgram(void)
  {
...
   m_trade.SetAsyncMode(true);
   m_trade.SetDeviationInPoints(INT_MAX);
  }

O pressionamento dos botões Buy e Sell será processado usando os botões CProgram::OnBuy() e CProgram::OnSell(). Recebemos o volume do trade a partir do campo inserido Lot. No objeto gráfico, tome o símbolo em que você está operando. Este é o mínimo necessário para concluir a operação de negociação. Na classe CTrade, estão os métodos CTrade::Buy() e CTrade::Sell(), que ao serem chamadas permitem passar estes dois argumentos. 

//+-------------------------------------------------------------------+
//| Compra                                                            |
//+-------------------------------------------------------------------+
bool CProgram::OnBuy(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_buy.Id())
      return(false);
//--- Volume e símbolo para abertura da posição
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
//--- Abrir posição
   m_trade.Buy(lot,symbol);
   return(true);
  }
//+-------------------------------------------------------------------+
//| Venda                                                             |
//+-------------------------------------------------------------------+
bool CProgram::OnSell(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_sell.Id())
      return(false);
//--- Volume e símbolo para abertura da posição
   double lot    =::NormalizeDouble((double)m_lot.GetValue(),2);
   string symbol =m_sub_chart1.GetSubChartPointer(0).Symbol();
//--- Abrir posição
   m_trade.Sell(lot,symbol);
   return(true);
  }

Para fechamento de todas as posições de uma vez ou apenas no símbolo especificado, você precisa escrever um método separado — na classe CTrade não existe tal coisa. Se o símbolo for transferido para o método (parâmetro opcional), somente as posições neste símbolo serão fechadas. Se o símbolo não for especificado, todas as posições serão fechadas.

//+-------------------------------------------------------------------+
//| Fechando todas as posições                                        |
//+-------------------------------------------------------------------+
bool CProgram::CloseAllPosition(const string symbol="")
  {
//--- Verifique se existe uma posição com as propriedades especificadas
   int total=::PositionsTotal();
   for(int i=total-1; i>=0; i--)
     {
      //--- Selecione a posição
      string pos_symbol=::PositionGetSymbol(i);
      //--- Se especificado o fechamento de acordo com o símbolo
      if(symbol!="")
         if(symbol!=pos_symbol)
            continue;
      //--- Obtenha a boleta
      ulong position_ticket=::PositionGetInteger(POSITION_TICKET);
      //--- Redefina o último erro
      ::ResetLastError();
      //--- Se a posição não estiver fechada, exiba uma mensagem sobre isso
      if(!m_trade.PositionClose(position_ticket))
         ::Print(__FUNCTION__,": > An error occurred when closing a position: ",::GetLastError());
     }
//---
   return(true);
  }

O fechamento de todas as posições está associado ao botão Close all positions. Seu pressionamento será processado no método CProgram::OnCloseAllPositions(). Para evitar pressionamentos acidentais no botão, será aberta a caixa de diálogo para confirmação da operação.

//+-------------------------------------------------------------------+
//| Fechando todas as posições                                        |
//+-------------------------------------------------------------------+
bool CProgram::OnCloseAllPositions(const long id)
  {
//--- Verificação do identificador do elemento
   if(id!=m_close_all.Id())
      return(false);
//--- Caixa de diálogo
   int mb_id=::MessageBox("Are you sure you want to close \nall positions?","Close positions",MB_YESNO|MB_ICONWARNING);
//--- Feche as posições
   if(mb_id==IDYES)
      CloseAllPosition();
//---
   return(true);
  }

Veja como fica isso:

 Fig. 11 – Fechando todas as posições.

Fig. 9. Fechando todas as posições.

Você pode fechar posições do símbolo especificado na guia Positions. Às células da primeira coluna da tabela de posições foram adicionados botões na forma de cruzes. Eles podem fechar imediatamente todas as posições do símbolo cujos dados são mostrados nesta linha. Ao pressionar os botões nas células, é gerado o evento personalizado com identificador ON_CLICK_BUTTON. No entanto, no elemento do tipo CTable existem barras de rolagem cujos botões geram os mesmos eventos, e o identificador do elemento também coincide. Portanto, você precisa rastrear o parâmetro de string (sparam) do evento, a fim de não processar acidentalmente um clique nos outros botões do elementos. O parâmetro de string especifica o tipo do elemento no qual houve pressionamento. As barras de rolagem têm o valor «scroll». Se um evento com esse valor chegar, o programa sai do método. Depois disso, você precisa verificar se ainda há posições abertas.

Se todas as verificações forem concluídas, você precisará extrair o índice de string a partir da descrição do parâmetro de string que define o símbolo na primeira coluna da tabela. Aqui também, para excluir cliques acidentais nos botões, primeiro é exibida uma caixa de diálogo para confirmar a ação. O clique no botão Sim fecha posições apenas no símbolo especificado

//+-------------------------------------------------------------------+
//| Fechando todas as posições do símbolo definido                    |
//+-------------------------------------------------------------------+
bool CProgram::OnCloseSymbolPositions(const long id,const string desc)
  {
//--- Verificação do identificador do elemento
   if(id!=m_table_positions.Id())
      return(false);
//--- Sair, se pressionado o botão da barra de rolagem
   if(::StringFind(desc,"scroll",0)!=WRONG_VALUE)
      return(false);
//--- Sair se não houver posições
   if(::PositionsTotal()<1)
      return(true);
//--- Extraia os dados da linha
   string str_elements[];
   ushort sep=::StringGetCharacter("_",0);
   ::StringSplit(desc,sep,str_elements);
//--- Obtenha o índice e o símbolo
   int    row_index =(int)str_elements[1];
   string symbol    =m_table_positions.GetValue(0,row_index);
//--- Caixa de diálogo
   int mb_id=::MessageBox("Are you sure you want to close \nall positions on symbol "+symbol+"?","Close positions",MB_YESNO|MB_ICONWARNING);
//--- Feche todas as posições para o símbolo especificado
   if(mb_id==IDYES)
      CloseAllPosition(symbol);
//---
   return(true);
  }

Veja como fica:

 Fig. 11 – Fechando todas as posições do símbolo especificado.

Fig. 10. Fechando todas as posições do símbolo especificado.

Todas as operações de negociação descritas acima são processadas após a chegada do evento ON_CLICK_BUTTON:

void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de pressionamento de botões
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- Compra
      if(OnBuy(lparam))
         return;
      //--- Venda
      if(OnSell(lparam))

         return;
      //--- Fechar todas as posições
      if(OnCloseAllPositions(lparam))
         return;
      //--- Fechar todas as posições do símbolo especificado
      if(OnCloseSymbolPositions(lparam,sparam))
         return;
      //---
      return;
     }
...
  }

Cada operação de negociação deve ser refletida na tabela de posições. Para fazer isso, você precisa acompanhar os eventos de negociação e o histórico das trades da conta de negociação. Se o número de trades foi alterado, a tabela deverá ser gerada novamente. Para verificar se o histórico foi alterado, é usado o método CProgram::IsLastDealTicket(). Cada vez após a verificação, você precisa salvar o tempo e a boleta do último trade. Salve o tempo, para que você não peça todo o histórico de trades toda vez. Use a boleta para verificar se o número de trades no histórico foi alterado. Como o trade inicia vários eventos de negociação, esse método retornará true apenas uma vez.

class CProgram : public CWndEvents
  {
private:
   //--- Hora e boleta do último trae verificado
   datetime          m_last_deal_time;
   ulong             m_last_deal_ticket;
   //---
private:
   //--- Verificação de um novo trade no histórico
   bool              IsLastDealTicket(void);
  };
//+-------------------------------------------------------------------+
//| Constructor                                                       |
//+-------------------------------------------------------------------+
CProgram::CProgram(void) : m_last_deal_time(NULL),
                           m_last_deal_ticket(WRONG_VALUE)
  {
...
  }
//+-------------------------------------------------------------------+
//| Verificando um novo trade no histórico                            |
//+-------------------------------------------------------------------+
bool CProgram::IsLastDealTicket(void)
  {
//--- Sair se o histórico não for recebido
   if(!::HistorySelect(m_last_deal_time,UINT_MAX))
      return(false);
//--- Obtemos o número de trades na lista
   int total_deals=::HistoryDealsTotal();
/--- Percorremos todos os trades na lista recebida, desde a última transação até a primeira
   for(int i=total_deals-1; i>=0; i--)
     {
      //--- Obtemos o boleto do trade
      ulong deal_ticket=::HistoryDealGetTicket(i);
      //--- Se as boletas forem iguais, sairemos
      if(deal_ticket==m_last_deal_ticket)
         return(false);
      //--- Se as boletas não forem iguais, informamos sobre isso
      else
        {
         datetime deal_time=(datetime)::HistoryDealGetInteger(deal_ticket,DEAL_TIME);
         //--- Lembramos a hora a boleta do último trade
         m_last_deal_time   =deal_time;
         m_last_deal_ticket =deal_ticket;
         return(true);
        }
     }
//--- Boletas de outro símbolo
   return(false);
  }

Chame uma vez o método CProgram::IsLastDealTicket() no manipulador de eventos. Se o histórico mudou, a tabela de posições será gerada de novo:

//+-------------------------------------------------------------------+
//| Evento de operação de negociação                                  |
//+-------------------------------------------------------------------+
void CProgram::OnTradeEvent(void)
  {
//--- Se houver um novo trade
   if(IsLastDealTicket())
     {
      //--- Inicialização da tabela de posições
      InitializePositionsTable();
     }
  }

Veja como fica:

 Fig. 12 – Gerando da tabela ao fechar as posições pelo símbolo.

Fig. 11. Gerando a tabela ao fechar as posições pelo símbolo.

Fim do artigo

No artigo, mostrei como, sem muito esforço, você pode criar interfaces gráficas de qualquer complexidade para seus programas. Você pode continuar desenvolvendo este programa e usá-lo para seus próprios propósitos. A ideia pode ser melhorada, adicionando seus próprios indicadores e resultados de cálculos.

Para quem não quer estudar com cuidado o código e compilar o programa, no mercado já foi publicado o aplicativo pronto Trading Exposure.

O artigo contém arquivos para testes e um estudo mais detalhado do código apresentado nele.

Nome do arquivo Comentário
MQL5\Experts\TradePanel\TradePanel.mq5 EA - para negociação manual - com interface gráfica
MQL5\Experts\TradePanel\Program.mqh Arquivo com a classe do programa
MQL5\Experts\TradePanel\CreateGUI.mqh Arquivo com a implementação dos métodos para criar uma interface gráfica a partir da classe de programa no arquivo Program.mqh
MQL5\Include\EasyAndFastGUI\Controls\Table.mqh Classe atualizada CTable
MQL5\Include\EasyAndFastGUI\Keys.mqh Classe atualizada CKeys