Visualização dos resultados de otimização pelo critério selecionado
Anatoli Kazharski | 28 junho, 2018
Sumário
- Introdução
- Desenvolvimento da interface gráfica
- Seleção de resultados de otimização
- Seleção programática de linhas na tabela
- Métodos auxiliares para trabalhar com dados de quadros
- Processamento de eventos ao interagir com a interface gráfica
- Fim do artigo
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.
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.
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).
//+---------------------------------------------------------------------------+ //| 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.
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.
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.
- Você obtém o número de passagem da primeira coluna da tabela.
- Você obtém os dados sobre o número da passagem.
- Você adiciona símbolos à lista ao lado do gráfico multissímbolo.
- 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.
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. |