Visualização dos resultados de otimização pelo critério selecionado

Anatoli Kazharski | 28 junho, 2018


Sumário

Introdução

Continuamos a expandir o aplicativo para trabalhar com resultados de otimização que começamos a desenvolver em artigos anteriores. Veremos um exemplo em que podemos gerar uma tabela de melhores resultados após a otimização de parâmetros, especificando através da interface gráfica outro critério. No artigo anterior, mostrei como trabalhar com muitas categorias de dados, isto é, como armazená-las na matriz do quadro e, em seguida, extrai-las dele. Trabalhamos com indicadores estatísticos, com matrizes de saldos de símbolos e com rebaixamentos do depósito.

Após a otimização, podemos ver esses dados em gráficos separados, simplesmente selecionando as linhas correspondentes na tabela. Mas, quando se fala em perfeição, o céu é o limite. E para ver os resultados selecionados segundo certo critério, seria bom ver seus balanços de uma só vez num gráfico separado. A seleção de linhas na tabela formará a curva de balanço selecionada neste gráfico cumulativo. Assim, podemos avaliar melhor o resultado da otimização. 

Além disso, a pedido de alguns membros da comunidade, mostrarei como controlar a seleção de linhas numa tabela a partir do teclado. Para fazer isso, tivemos que aprimorar a classe CTable na nossa biblioteca.

Desenvolvimento da interface gráfica

Na nossa versão anterior do aplicativo, na GUI, havia três guias: Frames, Results e Balance.

Na guia Frames, estão os elementos para trabalhar e visualizar todos os resultados durante e após o processo de otimização.

Agora, combine a segunda e terceira guias - (Results) e (Balance), respectivamente. Desse modo, selecionando uma linha na tabela, verá imediatamente o resultado no gráfico e não precisará ir para outra guia.

Na guia Results, coloque outro grupo de guias: Balances e Favorites. Na guia Balances, haverá gráficos para visualizar saldos e rebaixamentos do depósito multissímbolos, bem como uma lista dos símbolos que participam do teste. Na guia Favorites, coloque o gráfico de todos os melhores resultados da tabela. Além disso, adicione o elemento de tipo CComboBox, isto é, uma lista suspensa. Ele ajudará a escolher os critérios para selecionar os melhores resultados da lista geral de quadros.

A hierarquia completa dos elementos da GUI agora tem a seguinte aparência:

  • Formulário para controles
  • Barra de status para mostrar informações adicionais finais
  • Primeiro grupo de guias:
    • Frames:
      • Campo para inserir o número de saldos exibidos dos resultados ao rolar após a otimização
      • Atraso em milissegundos durante a rolagem dos resultados
      • Botão de inicialização de nova rolagem dos resultados
      • Gráfico mostrando o número especificado de saldos
      • Gráfico mostrando todos os resultados
    • Results:
      • Tabela de melhores resultados
      • Caixa de combinação com uma lista suspensa para selecionar o critério pelo qual será gerada a tabela
      • Segundo grupo de guias:
        • Balance:
          • Gráfico para exibir o saldo multissímbolo do resultado selecionado
          • Gráfico para mostrar os rebaixamentos do resultado selecionado
          • Lista de saldos de símbolos que participam do teste
        • Favorites:
          • Gráfico com os melhores resultados da tabela
  • Indicador de reprodução de quadros

O código dos métodos para criação destes elementos é colocado num arquivo separado e é conectado a um arquivo com a classe do programa MQL:

//+------------------------------------------------------------------+
//| Classe para criar o aplicativo                                   |
//+------------------------------------------------------------------+
class CProgram : public CWndEvents
  {
private:
   //--- Janela
   CWindow           m_window1;
   //--- Barra de Status
   CStatusBar        m_status_bar;
   //--- Guias
   CTabs             m_tabs1;
   CTabs             m_tabs2;
   //--- Caixa de edição
   CTextEdit         m_curves_total;
   CTextEdit         m_sleep_ms;
   //--- Botões
   CButton           m_reply_frames;
   //--- Caixas de combinação
   CComboBox         m_criterion;
   //--- Gráficos
   CGraph            m_graph1;
   CGraph            m_graph2;
   CGraph            m_graph3;
   CGraph            m_graph4;
   CGraph            m_graph5;
   //--- Tabelas
   CTable            m_table_main;
   CTable            m_table_symbols;
   //--- Barra de progresso
   CProgressBar      m_progress_bar;
   //---
public:
   //--- Cria uma interface gráfica
   bool              CreateGUI(void);
   //---
private:
   //--- Formulário
   bool              CreateWindow(const string text);
   //--- Barra de Status
   bool              CreateStatusBar(const int x_gap,const int y_gap);
   //--- Guias
   bool              CreateTabs1(const int x_gap,const int y_gap);
   bool              CreateTabs2(const int x_gap,const int y_gap);
   //--- Caixa de edição
   bool              CreateCurvesTotal(const int x_gap,const int y_gap,const string text);
   bool              CreateSleep(const int x_gap,const int y_gap,const string text);
   //--- Botões
   bool              CreateReplyFrames(const int x_gap,const int y_gap,const string text);
   //--- Caixas de combinação
   bool              CreateCriterion(const int x_gap,const int y_gap,const string text);
   //--- Gráficos
   bool              CreateGraph1(const int x_gap,const int y_gap);
   bool              CreateGraph2(const int x_gap,const int y_gap);
   bool              CreateGraph3(const int x_gap,const int y_gap);
   bool              CreateGraph4(const int x_gap,const int y_gap);
   bool              CreateGraph5(const int x_gap,const int y_gap);
   //--- Botões
   bool              CreateUpdateGraph(const int x_gap,const int y_gap,const string text);
   //--- Tabelas
   bool              CreateMainTable(const int x_gap,const int y_gap);
   bool              CreateSymbolsTable(const int x_gap,const int y_gap);
   //--- Barra de progresso
   bool              CreateProgressBar(const int x_gap,const int y_gap,const string text);
  };
//+------------------------------------------------------------------+
//| Métodos para criar controles                         |
//+------------------------------------------------------------------+
#include "CreateGUI.mqh"
//+------------------------------------------------------------------+

Seleção de resultados de otimização

Para exibir todos os melhores resultados de otimização num único gráfico, você precisa de um método que retornará true até encontrar o número de resultados definidos nos parâmetros. Uma vez encontrado, o método retornará false. Trata-se do método CFrameGenerator::UpdateBestResultsGraph() apresentado abaixo. Por padrão, serão plotados os 100 melhores resultados de otimização.

O método usa um ciclo duplo. O primeiro ciclo é limitado pelo número de melhores resultados exibidos e pelo número de linhas na tabela, a fim de evitar sair do intervalo na estrutura das matrizes da tabela de dados. Em cada iteração deste ciclo, o ponteiro de quadros é movido para o topo da lista de quadros.

No segundo ciclo, movendo os quadros, procure o número de passagem que foi anteriormente armazenado na estrutura das matrizes. Antes da chamada do método CFrameGenerator::UpdateBestResultsGraph(), a estrutura das matrizes deve ser classificada pelo critério especificado. Depois, quando o número de passagem é encontrado, você obtém os parâmetros do EA nesta corrida e seu número. Após isso, você obtém o balanço do resultado da passagem atual a partir de sua matriz de dados (m_data[]). Deve lembrar que os dados do balanço total estão contidos na matriz de quadros após os indicadores estatísticos, enquanto o tamanho da matriz será igual ao valor no parâmetro double do quadro. Essa matriz, como se se tratasse de uma série de dados, é colocada no gráfico de balanço dos melhores resultados. Se o resultado final desse teste for maior do que o depósito inicial, a linha ficará verde, caso contrário, vermelha. Armazene o tamanho da série numa matriz separada, para que, após o término do ciclo, seja possível definir a série com o número máximo de elementos para definir os limites do eixo X. Finalmente, o contador de quadros deve ser aumentado num, para que, da próxima vez, seja possível continuar o ciclo, excluindo esta passagem.

Se o ciclo estiver completo, então:

  • é redefinido o contador de quadros,
  • é determinada a fila de dados com o número máximo de elementos,
  • é configurado e atualizado o gráfico dos melhores resultados.

Depois disso, o método CFrameGenerator::UpdateBestResultsGraph() retorna false, o que significa que a seleção de resultados está completa.

//+------------------------------------------------------------------+
//| Classe para trabalhar com os resultados da otimização            |
//+------------------------------------------------------------------+
class CFrameGenerator
  {
private:
   //--- Número de melhores resultados
   int               m_best_results_total;
   //---
public:
   //--- Atualizar o gráfico de melhores resultados
   bool              UpdateBestResultsGraph(void);
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CFrameGenerator::CFrameGenerator(void) : m_best_results_total(100)
  {
  }
//+------------------------------------------------------------------+
//| Atualizar o gráfico de melhores resultados                       |
//+------------------------------------------------------------------+
bool CFrameGenerator::UpdateBestResultsGraph(void)
  {
   for(int i=(int)m_frames_counter; i<m_best_results_total && i<m_rows_total; i++)
     {
      //--- Desloque o ponteiro dos quadros para o começo
      ::FrameFirst();
      //--- Extração de dados
      while(::FrameNext(m_pass,m_name,m_id,m_value,m_data))
        {
         //--- Os números de passagem não correspondem, ir para o próximo
         if(m_pass!=(ulong)m_columns[0].m_rows[i])
            continue;
         //--- Receba os parâmetros e seu número
         GetParametersTotal();
         //--- Obtenha o balanço do resultado atual
         double serie[];
         ::ArrayCopy(serie,m_data,0,STAT_TOTAL,(int)m_value);
         //--- Envie a matriz para exibição do balanço no gráfico
         CCurve *curve=m_graph_best.CurveGetByIndex(i);
         curve.Name((string)m_pass);
         curve.Color((m_data[m_profit_index]>=0)? ::ColorToARGB(clrLimeGreen) : ::ColorToARGB(clrRed));
         curve.Update(serie);
         //--- Obtenha o tamanho da série
         m_curve_max[i]=::ArraySize(serie);
         //--- Aumentar o contador de quadros
         m_frames_counter++;
         return(true);
        }
     }
//--- Redefinir o contador de quadros
   m_frames_counter=0;
//--- Defina a fila com o número máximo de elementos
   double x_max=m_curve_max[::ArrayMaximum(m_curve_max)];
//--- Propriedades do eixo horizontal
   CAxis *x_axis=m_graph_best.XAxis();
   x_axis.Min(0);
   x_axis.Max(x_max);
   x_axis.DefaultStep((int)(x_max/8.0));
//--- Atualizar gráfico
   m_graph_best.CalculateMaxMinValues();
   m_graph_best.CurvePlotAll();
   m_graph_best.Update();
   return(false);
  }

Para encontrar os resultados, você precisa percorrer todos os quadros da lista geral, demorando tempo, por isso, para acompanhar o estágio de busca, use a Barra de progresso (CProgressBar). Para isso, na classe de aplicativo (CProgram), é implementado o método CProgram::GetBestOptimizationResults(). Neste caso, no ciclo while, como condição, é chamado o método CFrameGenerator::UpdateBestResultsGraph(). Antes de iniciar o ciclo, torne a barra de progresso visível. Como no método CFrameGenerator::UpdateBestResultsGraph() é usado o contador de quadros, você pode obter seu valor atual. Depois que o ciclo estiver concluído, será necessário ocultar a barra de progresso.

class CProgram : public CWndEvents
  {
private:
   //--- Obtenção dos melhores resultado de otimização
   void              GetBestOptimizationResults(void);
  };
//+------------------------------------------------------------------+
//| Obtenção dos melhores resultado de otimização                    |
//+------------------------------------------------------------------+
void CProgram::GetBestOptimizationResults(void)
  {
//--- Mostrar barra de progresso
   m_progress_bar.Show(); 
//--- Visualize o processo de recepção dos melhores resultados
   int best_results_total=m_frame_gen.BestResultsTotal();
   while(m_frame_gen.UpdateBestResultsGraph() && !::IsStopped())
     {
      //--- Atualizar barra de progresso
      m_progress_bar.LabelText("Selection of results: "+string(m_frame_gen.CurrentFrame())+"/"+string(best_results_total));
      m_progress_bar.Update((int)m_frame_gen.CurrentFrame(),best_results_total);
     }
//--- Ocultar barra de progresso
   m_progress_bar.Hide();
  }

O método CProgram::GetBestOptimizationResults() deve ser chamado no método de conclusão da otimização. Assim, o usuário entenderá que o programa está sendo executado e não foi interrompido. Os restantes métodos foram considerados em artigos anteriores, pelo que não nos vamos debruçar sobre eles.

//+------------------------------------------------------------------+
//| Evento de fim do processo de otimização                          |
//+------------------------------------------------------------------+
void CProgram::OnTesterDeinitEvent(void)
  {
//--- Conclusão da otimização
   m_frame_gen.OnTesterDeinitEvent();
//--- Visualize o processo de recepção dos melhores resultados
   GetBestOptimizationResults();
//--- Disponibilizar a interface
   IsLockedGUI(true);
//--- Cálculo do índice de resultados positivos e negativos
   CalculateProfitsAndLosses();
//--- Obtenha os dados na tabela de resultados de otimização
   GetFrameDataToTable();
//--- Inicialização do kernel da GUI
   CWndEvents::InitializeCore();
  }

Imediatamente após a conclusão da otimização ou após ter sido interrompida forçosamente, na área da barra de status é exibida uma barra de progresso. Ela mostra ao usuário que o processo de seleção de resultados está em andamento:

 Fig. 1 – Visualização do processo de seleção de resultados.

Fig. 1. Visualização do processo de seleção de resultados.

Para conferir a visualização dos saldos de todos os resultados selecionados, vá para a guia Results e, em seguida, para a guia Favorites. Por padrão, a tabela adiciona os 100 melhores resultados de acordo com o critério Profit. A qualquer momento na lista suspensa Criterion, você pode escolher outro critério para selecionar os 100 melhores resultados. Voltaremos a isso mais tarde, mas, por enquanto, vamos considerar os métodos de organização desse processo.

 Fig. 2 – Gráfico dos melhores resultados de otimização.

Fig. 2. Gráfico dos melhores resultados de otimização.

Seleção programática de linhas na tabela

Até agora, você podia selecionar uma linha na tabela apenas clicando nela com o botão esquerdo. Mas, às vezes, isso precisa ser feito programaticamente, por exemplo, selecionando as linhas com as teclas Up, Down, Home e End. Para selecionar programaticamente uma linha, à classe CTable é adicionado o método público CTable::SelectRow(). Seu código é semelhante ao método privado CTable::RedrawRow(). Ele, por sua vez, é usado para redesenhar linhas após eventos de clique do mouse e para mover o cursor sobre a tabela quando estiver ativado o modo de realce de linhas ao passar o cursor.

Boa parte do código pode ser reutilizada nos dois métodos. É por isso que decidi colocá-lo no método separado CTable::DrawRow(). A ele é necessário transmitir:

  • a matriz de índices para determinar a sequência de plotagem,
  • o índice atual da linha selecionada,
  • o índice da linha selecionada anterior,
  • o modo de uso do método — programático (false) ou personalizado (true).
No método, são definidas as coordenadas para redesenhar as linhas e são desenhados em sequência seus elementos: fundo, grade, imagens e texto.
//+---------------------------------------------------------------------------+
//| Desenha a fila especificada na tabela de acordo com o modo especificado   |
//+---------------------------------------------------------------------------+
void CTable::DrawRow(int &indexes[],const int item_index,const int prev_item_index,const bool is_user=true)
  {
   int x1=0,x2=m_table_x_size-2;
   int y1[2]={0},y2[2]={0};
//--- Número de linhas e colunas para desenhar
   uint rows_total    =0;
   uint columns_total =m_columns_total-1;
//--- Se este for um método para selecionar linhas
   if(!is_user)
      rows_total=(prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1;
   else
      rows_total=(item_index!=WRONG_VALUE && prev_item_index!=WRONG_VALUE && item_index!=prev_item_index)? 2 : 1;
//--- Desenhe o plano de fundo das linhas
   for(uint r=0; r<rows_total; r++)
     {
      //--- Cálculo das coordenadas das bordas superior e inferior da linha
      y1[r] =m_rows[indexes[r]].m_y+1;
      y2[r] =m_rows[indexes[r]].m_y2-1;
      //--- Defina o foco na linha em relação ao modo de luz
      bool is_item_focus=false;
      if(!m_lights_hover)
         is_item_focus=(indexes[r]==item_index && item_index!=WRONG_VALUE);
      else
         is_item_focus=(item_index==WRONG_VALUE)?(indexes[r]==prev_item_index) :(indexes[r]==item_index);
      //--- Desenhar o plano de fundo da linha
      m_table.FillRectangle(x1,y1[r],x2,y2[r],RowColorCurrent(indexes[r],(is_user)? is_item_focus : false));
     }
//--- Desenhe as bordas
   for(uint r=0; r<rows_total; r++)
     {
      for(uint c=0; c<columns_total; c++)
         m_table.Line(m_columns[c].m_x2,y1[r],m_columns[c].m_x2,y2[r],::ColorToARGB(m_grid_color));
     } 
//--- Desenhe figuras
   for(uint r=0; r<rows_total; r++)
     {
      for(uint c=0; c<m_columns_total; c++)
        {
         //--- Desenhe a figura se (1) ela estiver nesta célula e (2) nesta coluna o texto estiver alinhado à esquerda
         if(ImagesTotal(c,indexes[r])>0 && m_columns[c].m_text_align==ALIGN_LEFT)
            CTable::DrawImage(c,indexes[r]);
        }
     }
//--- Para calcular as coordenadas
   int x=0,y=0;
//--- Método de alinhamento de texto
   uint text_align=0;
//--- Desenhe o texto
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- Obtenha (1) a coordenada X do texto e (2) o método de alinhamento de texto
      x          =TextX(c);
      text_align =TextAlign(c,TA_TOP);
      //---
      for(uint r=0; r<rows_total; r++)
        {
         //--- (1) Calcular a coordenada e (2) desenhar o texto
         y=m_rows[indexes[r]].m_y+m_label_y_gap;
         m_table.TextOut(x,y,m_columns[c].m_rows[indexes[r]].m_short_text,TextColor(c,indexes[r]),text_align);
        }
     }
  }

Para o método CTable::SelectRow() é necessário transferir apenas um argumento, nomeadamente o índice da linha a ser selecionada. Aqui, primeiro, verifique se há saída do intervalo da tabela e se já está selecionada a linha com este índice. Em seguida, defina os índices atual e anterior da linha selecionada e a sequência de plotagem. Transfira os valores obtidos para o método método CTable::DrawRow(). Tendo recebido índices nos limites da área visível da tabela, você pode determinar a posição para a qual é preciso mover a caixa de rolagem.

//+------------------------------------------------------------------+
//| Seleção da linha especificada na tabela                          |
//+------------------------------------------------------------------+
void CTable::SelectRow(const int row_index)
  {
//--- Verificar se está fora do intervalo
   if(!CheckOutOfRange(0,(uint)row_index))
      return;
//--- Se essa linha já estiver selecionada
   if(m_selected_item==row_index)
      return;
//--- Índices de linhas atuais e anteriores
   m_prev_selected_item =(m_selected_item==WRONG_VALUE)? row_index : m_selected_item;
   m_selected_item      =row_index;
//--- Matriz para valores numa determinada sequência
   int indexes[2];
//--- Se esta é a primeira vez aqui
   if(m_prev_selected_item==WRONG_VALUE)
      indexes[0]=m_selected_item;
   else
     {
      indexes[0] =(m_selected_item>m_prev_selected_item)? m_prev_selected_item : m_selected_item;
      indexes[1] =(m_selected_item>m_prev_selected_item)? m_selected_item : m_prev_selected_item;
     }
//--- Desenha a fila especificada da tabela de acordo com o modo especificado
   DrawRow(indexes,m_selected_item,m_prev_selected_item,false);
//--- Obter os índices dentro dos limites da área visível
   VisibleTableIndexes();
//--- Mover a barra de rolagem para a linha especificada
   if(row_index==0)
     {
      VerticalScrolling(0);
     }
   else if((uint)row_index>=m_rows_total-1)
     {
      VerticalScrolling(WRONG_VALUE);
     }
   else if(row_index<(int)m_visible_table_from_index)
     {
      VerticalScrolling(m_scrollv.CurrentPos()-1);
     }
   else if(row_index>=(int)m_visible_table_to_index-1)
     {
      VerticalScrolling(m_scrollv.CurrentPos()+1);
     }
  }

No final do artigo, você pode baixar uma versão atualizada da classe CTable. A versão mais recente da biblioteca EasyAndFast está disponível no CodeBase.

Métodos auxiliares para trabalhar com dados de quadros

Na versão do aplicativo apresentado no artigo anterior, ao selecionar uma linha na tabela de resultados, os gráficos exibiam saldos multissímbolos e rebaixamentos de depósito. Para entender a qual símbolo pertencia uma determinada curva num gráfico multissímbolo, os nomes das curvas eram exibidos separadamente, no lado direito do gráfico. Nesta versão, o tamanho deste gráfico no eixo Y será fixo. Se houver um grande número de símbolos nos testes, todos eles não caberão na área selecionada. É por isso que, à direita dos gráficos, você coloca a lista de tipo CTable com uma barra de rolagem que conterá todos os nomes dos saldos.

Para obter os símbolos, use o método CProgram::GetFrameSymbolsToTable(). Uma vez recebidos os dados do quadro, torna-se possível obter os símbolos dos resultados a partir do parâmetro string. Após passar a matriz de string, você obtém a lista de símbolos. Se houver mais de um símbolo, você deve fazer com que o número de saldos seja maior num elemento, reservando o primeiro para o saldo total.

Em seguida, defina a dimensão da tabela. Aqui é necessária apenas uma coluna, enquanto o número de linhas deve ser igual ao número de curvas no gráfico. Especifique a largura da coluna e defina o nome do cabeçalho da tabela. No ciclo, preencha a tabela com os nomes dos saldos. Para entender a qual curva pertence um nome específico, associe seu componente de cor. Para exibir as alterações feitas, você precisa atualizar os elementos.

//+------------------------------------------------------------------+
//| Obtendo os símbolos do quadro na tabela                          |
//+------------------------------------------------------------------+
void CProgram::GetFrameSymbolsToTable(void)
  {
//--- Obtenha a lista de símbolos e o número de curvas
   string symbols[];
   int symbols_total  =m_frame_gen.CopySymbols(symbols);
   int balances_total =(symbols_total>1)? symbols_total+1 : symbols_total;
//--- Definimos o tamanho da tabela
   m_table_symbols.Rebuilding(1,balances_total,true);
//--- Largura da coluna da lista
   int width[]={111};
   m_table_symbols.ColumnsWidth(width);
//--- Defina o cabeçalho
   m_table_symbols.SetHeaderText(0,"Balances");
//--- Preencha a tabela com dados dos quadros
   for(uint r=0; r<m_table_symbols.RowsTotal(); r++)
     {
      uint clr=m_graph3.GetGraphicPointer().CurveGetByIndex(r).Color();
      m_table_symbols.TextColor(0,r,::ColorToARGB(clr));
      m_table_symbols.SetValue(0,r,(symbols_total>1)?(r<1)? "BALANCE" : symbols[r-1]: symbols[r],0);
     }
//--- Atualizar tabela
   m_table_symbols.Update(true);
   m_table_symbols.GetScrollHPointer().Update(true);
   m_table_symbols.GetScrollVPointer().Update(true);
  }

Seria muito conveniente se, ao selecionar algum resultado na tabela, sua curva no gráfico também fosse destacada. Para fazer isso, escreva o método CProgram::SelectCurve(). A ele é transmitido o número de passagem para encontrar a curva desejada no gráfico. Os nomes das curvas correspondem aos números das passagens aos quais eles pertencem. Portanto, você pode encontrá-los simplesmente comparando no ciclo o número de passagem transmitido com o que está contido no nome da curva. Uma vez encontrada a curva desejada, lembre-se do seu índice e pare o ciclo.

Agora a curva encontrada deve ser transferida para a camada superior. Afinal, se você marcar uma curva simplesmente mudando sua cor, ela pode se perder entre todas as outras. Logo, você precisa trocar de lugar a curva encontrada com a última desenhada.

Para isso, de acordo com os índices, você obtém os ponteiros dessas duas curvas. Em seguida, copie seus nomes e matrizes de dados. Depois disso, mude seus lugares. Para a última curva, defina a espessura e a cor da linha. Use a cor negra para que se destaque melhor das outras. Você precisa atualizar o gráfico para que as alterações entrem em vigor.

//+------------------------------------------------------------------+
//| Evento de fim do processo de otimização                          |
//+------------------------------------------------------------------+
void CProgram::SelectCurve(const ulong pass)
  {
   CGraphic *graph=m_graph5.GetGraphicPointer();
//--- Procure uma curva pelo número de passagem
   ulong curve_index =0;
   int curves_total  =graph.CurvesTotal();
   for(int i=0; i<curves_total; i++)
     {
      if(pass==(ulong)graph.CurveGetByIndex(i).Name())
        {
         curve_index=i;
         break;
        }
     }
//--- Curva selecionada e última curva no gráfico
   CCurve *selected_curve =graph.CurveGetByIndex((int)curve_index);
   CCurve *last_curve     =graph.CurveGetByIndex((int)curves_total-1);
//--- Copie tanto a matriz de dados selecionada quanto a última matriz
   double y1[],y2[];
   string name1=selected_curve.Name();
   string name2=last_curve.Name();
   selected_curve.GetY(y1);
   last_curve.GetY(y2);
//---
   last_curve.Name(name1);
   selected_curve.Name(name2);
   last_curve.Update(y1);
   selected_curve.Update(y2);
//---
   last_curve.LinesWidth(2);
   last_curve.Color(clrBlack);
//--- Atualizar gráfico
   graph.CurvePlotAll();
   graph.Update();
  }

Agora, selecionando uma linha na tabela, observe a curva de saldo correspondente no gráfico.

 Fig. 3 – Seleção de curvas no gráfico.

Fig. 3. Seleção de curvas no gráfico.


Processamento de eventos ao interagir com a interface gráfica

Resta considerar os métodos de processamento de eventos que são gerados ao interagir com a interface gráfica de vosso aplicativo. Esses são os métodos.

  • Seleção de linha da tabela clicando com botão esquerdo do mouse.
  • Seleção de resultados na tabela através do teclado.
  • Seleção do critério para a escolha de resultados na lista suspensa.

Quando você seleciona uma linha clicando nela com o mouse, é gerado o evento personalizado ON_CLICK_LIST_ITEM. Para processá-lo, é chamado o método CProgram::TableRowSelection(). Para ele é transferido o parâmetro long de evento. Este parâmetro é o identificador do elemento a partir do qual foi gerado este evento. Se o identificador não pertence ao elemento, o programa sairá do método e verificará o próximo elemento no manipulador de eventos dos itens do aplicativo. Se o identificador coincidir com o que está na tabela de resultados, você obterá o número de passagem da primeira coluna da tabela. Por isso, para obter o número de passagem, você só precisa especificar os índices da coluna e da fila recém-selecionada, passando esses valores para o método CTable::GetValue().

Desse modo, você consegue o número da passagem. Agora você pode obter dados desse quadro e, em seguida, os símbolos contidos nesse resultado. Adicione-os à tabela na primeira guia do segundo grupo. No final, selecione a curva de saldo no gráfico de todos os resultados.

//+------------------------------------------------------------------+
//| Selecionando uma linha da tabela com o botão esquerdo do mouse   |
//+------------------------------------------------------------------+
bool CProgram::TableRowSelection(const long element_id)
  {
//--- Seleção da linha da tabela
   if(element_id!=m_table_main.Id())
      return(false);
//--- Obtenha o número da passagem da tabela
   ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem());
//--- Obtenha os dados do número da passagem
   m_frame_gen.GetFrameData(pass);
//--- Adição de símbolos à tabela
   GetFrameSymbolsToTable();
//--- Seleção da curva no gráfico pelo número de passagem
   SelectCurve(pass);
   return(true);
  }

Após a chegada do evento personalizado ON_CLICK_LIST_ITEM, também é processada a ação de selecionar o critério para escolha de resultados na lista suspensa da caixa de combinação (CComboBox). Isso é feito através do método CProgram::ShowResultsBySelectedCriteria(). Após a verificação bem-sucedida do ID do elemento, você obtém o índice do item selecionado na lista suspensa. Nesta versão do aplicativo, na lista suspensa são propostos três critérios:

  • Result — resultado personalizado retornado pela função OnTester().
  • Profit —  lucro final do resultado do teste.
  • Recovery factor — fator de recuperação.  

Em seguida, determine o índice da coluna com os dados que pertencem ao critério selecionado. O primeiro item pertence à coluna com índice 1, o segundo - à coluna com índice 2, o terceiro - à coluna com índice 5. Logo, obtenha os quadros com os melhores resultados para o critério selecionado. Para fazer isso, você deve chamar o método CFrameGenerator::OnChangedSelectionCriteria(), passando para ele o índice de coluna. Agora tudo está pronto para obter os balanços dos melhores resultados no gráfico. Este processo é visualizado na barra de progresso. O último desafio aqui é obter todos os dados na tabela de melhores resultados.

//+------------------------------------------------------------------+
//| Exibe resultados pelo critério especificado                      |
//+------------------------------------------------------------------+
bool CProgram::ShowResultsBySelectedCriteria(const long element_id)
  {
//--- Verificação do identificador do elemento
   if(element_id!=m_criterion.Id())
      return(false);
//--- Defina o índice do critério para obter os melhores resultados
   int index=m_criterion.GetListViewPointer().SelectedItemIndex();
   int column_index=(index<1)? 1 : (index==1)? 2 : 5;
   m_frame_gen.OnChangedSelectionCriteria(column_index);
//--- Visualize o processo de recepção dos melhores resultados
   GetBestOptimizationResults();
//--- Obtenha os dados na tabela de resultados de otimização
   GetFrameDataToTable();
   return(true);
  }

No manipulador de eventos do programa, os métodos acima são chamados sequencialmente à medida que chega um evento ON_CLICK_LIST_ITEM, até que um deles retorne true.

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- Evento de pressionado nas filas da tabela
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM)
     {
      //--- Seleção da linha da tabela
      if(TableRowSelection(lparam))
         return;
      //--- Seleção do critério para escolha de resultados
      if(ShowResultsBySelectedCriteria(lparam))
         return;
      //---
      return;
     }
...
  }

Veja como fica o processo de seleção da lista suspensa, a obtenção de dados e sua plotagem no gráfico:

 Fig. 4 – Seleção de resultados de acordo com os critérios especificados.

Fig. 4 – Seleção de resultados de acordo com os critérios especificados.

Para selecionar uma linha a partir do teclado, você precisa do método CProgram::SelectingResultsUsingKeys(). Para ele você precisa passar o código da tecla pressionada. Ele vem ao parâmetro long de evento CHARTEVENT_KEYDOWN. No início do método, você obtém o índice da linha atualmente selecionada na tabela. Em seguida, no operador interruptor switch, determine qual tecla foi pressionada. Veja um exemplo de processamento de quatro pressionamentos de tecla:

  • KEY_UP — se pressionada a tecla Up, reduza em 1 o índice atual da linha selecionada. Será destacada a próxima linha acima. 
  • KEY_DOWN — se pressionada a tecla Down, aumente em 1 o índice atual da linha selecionada. Será destacada a próxima linha abaixo.
  • KEY_HOME — se pressionada a tecla Home, defina o índice da primeira linha da tabela. 
  • KEY_END — se pressionada a tecla End,defina o índice da última linha da tabela.

Além disso, é necessário passar as verificações. O programa sairá do método nos seguintes casos:

  • se não estiver selecionada a linha,
  • se for selecionada a mesma linha,
  • se você sair da lista.

Se forem concluídas as verificações, a linha especificada será destacada na tabela e, se necessário, será deslocada a barra de rolagem vertical.

Depois que a linha é selecionada, acontece o seguinte.

  1. Você obtém o número de passagem da primeira coluna da tabela.
  2. Você obtém os dados sobre o número da passagem.
  3. Você adiciona símbolos à lista ao lado do gráfico multissímbolo.
  4. Você seleciona a curva de saldo no gráfico de todos os resultados selecionados.

Código do método CProgram::SelectingResultsUsingKeys():

//+------------------------------------------------------------------+
//| Selecionando resultados usando teclas                            |
//+------------------------------------------------------------------+
bool CProgram::SelectingResultsUsingKeys(const long key)
  {
//--- Obtenha o índice da linha selecionada
   int selected_row=m_table_main.SelectedItem();
//--- Definir a direção e a linha para mover a barra de rolagem
   switch((int)key)
     {
      case KEY_UP :
         selected_row--;
         break;
      case KEY_DOWN :
         selected_row++;
         break;
      case KEY_HOME :
         selected_row=0;
         break;
      case KEY_END :
         selected_row=(int)m_table_main.RowsTotal()-1;
         break;
     }
//--- Sair se (1) a fila não estiver destacada ou se (2) for selecionada a mesma fila que estava antes ou se (3) sair da lista
   if(selected_row==WRONG_VALUE || selected_row==m_table_main.SelectedItem() || 
      selected_row<0 || selected_row>=(int)m_table_main.RowsTotal())
      return(false);
//--- Selecionar a linha e mover a barra de rolagem
   m_table_main.SelectRow(selected_row);
   m_table_main.Update();
   m_table_main.GetScrollVPointer().Update(true);
//--- Obtenha o número de passagem da linha da tabela selecionada
   ulong pass=(ulong)m_table_main.GetValue(0,m_table_main.SelectedItem());
//--- Obtenha os dados do número da passagem
   m_frame_gen.GetFrameData(pass);
//--- Adição de símbolos à tabela
   GetFrameSymbolsToTable();
//--- Seleção da curva no gráfico pelo número de passagem
   SelectCurve(pass);
   return(true);
  }

O método CProgram::SelectingResultsUsingKeys() é chamado ao chegar o evento de pressionamento de tecla (CHARTEVENT_KEYDOWN) no manipulador de eventos do programa:

//+------------------------------------------------------------------+
//| Manipulador de eventos                                           |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- pressionamento de tecla
   if(id==CHARTEVENT_KEYDOWN)
     {
      //--- Seleção de resultados usando as teclas
      if(SelectingResultsUsingKeys(lparam))
         return;
      //---
      return;
     }
...
  }

Veja como isso funciona:

 Fig. 5 – Seleção de linhas da tabela usando o teclado.

Fig. 5 – Seleção de linhas da tabela usando o teclado.

Fim do artigo

O artigo mostra mais um caso em que a biblioteca de interfaces gráficas EasyAndFast é útil. Pelo que se pode observar, o componente visual é muito importante para a análise dos resultados do teste. A profunda e abrangente visão apresentada para as matrizes desses resultados de testes pode levar a novas ideias. Algumas delas já tem sido propostas pelos participantes da comunidade MQL.

Por exemplo, em vez de armazenar apenas indicadores estatísticos e dados de balanços numa matriz de quadros, armazenar relatórios de teste completos. Outra ideia que tem sido discutida no fórum fala de usar um critério personalizado na seleção de resultados. Por exemplo, usar várias listas suspensas ou caixas de seleção para formar um critério personalizado que seja calculado usando a fórmula especificada nas configurações. É difícil imaginar como tudo isso poderia ser implementado sem uma interface gráfica.

Ideias sugeridas por você nos comentários podem ser implementadas num dos seguintes artigos. Sendo assim, não hesite em oferecer suas sugestões sobre como desenvolver ainda mais o aplicativo para trabalhar com resultados de otimização.

Abaixo você pode baixar para o seu computador os arquivos de teste e um estudo mais detalhado do código apresentado no artigo.

Nome do arquivo Comentário
MacdSampleCFrames.mq5 Expert Advisor modificado da entrega padrão - MACD Sample
Program.mqh Arquivo com a classe do programa
CreateGUI.mqh Arquivo com a implementação dos métodos da classe do programa, no arquivo Program.mqh
Strategy.mqh Arquivo com a classe modificada de estratégia do MACD Sample (versão multissímbolo)
FormatString.mqh Arquivo com funções auxiliares para formatação de linhas
FrameGenerator.mqh Arquivo com uma classe para trabalhar com resultados de otimização.