O poder do ZigZag (parte II). Exemplos de recebimento, processamento e exibição de dados
Conteúdo
- Introdução
- Indicadores definindo o comportamento de preços
- Indicador FrequencyChangeZZ
- Indicador SumSegmentsZZ
- Indicador PercentageSegmentsZZ
- Indicador MultiPercentageSegmentsZZ
- EA para coleta e exibição de estatísticas
- Contagem do número de segmentos por tamanho
- Contagem do número de segmentos por duração
- Alguns detalhes de como trabalhar com a interface gráfica
- Conclusão
Introdução
Na primeira parte do artigo eu descrevi um indicador ZigZag modificado e uma classe para receber os dados desses tipos de indicadores. Aqui, eu mostrarei como desenvolver indicadores baseados nessas ferramentas e escrever um EA para testes que apresentem operações de acordo com os sinais formados pelo indicador ZigZag.
Como complemento, o artigo introduzirá uma nova versão da biblioteca EasyAndFast para desenvolvimento de interfaces gráficas do usuário.
Principais tópicos do artigo:
- indicadores que definem o comportamento do preço;
- EA com uma interface gráfica para coletar as estatísticas de comportamento do preço;
- EA para calcular o número de segmentos do indicador ZigZag em intervalos especificados.
Indicadores definindo o comportamento de preços
Vamos considerar três indicadores que definem o comportamento do preço.
- FrequencyChangeZZ calcula a frequência de formação de segmentos do indicador ZigZag direcionados de forma oposta.
- SumSegmentsZZ calcula a soma dos segmentos do conjunto obtido e seu o valor médio.
- PercentageSegmentsZZ define a razão percentual das somas do segmento e a diferença entre elas.
- MultiPercentageSegmentsZZ define a natureza da formação de vários segmentos a partir de um período de tempo maior, com base nos valores do indicador PercentageSegmentsZZ .
A estrutura de código de cada um desses indicadores é a mesma do indicador ZigZag descrito na primeira parte do artigo. Portanto, nós vamos nos deter apenas na função principal (FillIndicatorBuffers) onde os dados são recebidos e os buffers dos indicadores são preenchidos.
Indicador FrequencyChangeZZ
Para o indicador FrequencyChangeZZ , o código da função principal é o mesmo do código exibido abaixo. O índice das barras e o array de tempo são passados para a função. Em seguida, são copiados da hora da barra atual um número necessário do indicador ZigZag e os elementos do array de tempo (dados de origem). Se os dados de origem forem recebidos, os dados finais são solicitados. Depois disso, resta apenas chamar um método que retorne o número de barras no conjunto de segmentos. O resultado é salvo no elemento atual do buffer do indicador.
//+------------------------------------------------------------------+ //| Preenche os buffers do indicador | //+------------------------------------------------------------------+ void FillIndicatorBuffers(const int i,const datetime &time[]) { int copy_total=1000; for(int t=0; t<10; t++) { if(::CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total && ::CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total && ::CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total) { //--- Obtém os dados ZZ zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); //--- Salva o número de barras no segmento definido no buffer do indicador segments_bars_total_buffer[i]=zz.SegmentsTotalBars(); break; } } }
Nos parâmetros de entrada do indicador, nós especificaremos o seguinte:
(1) os valores devem ser calculados com base em todos os dados disponíveis,
(2) o desvio mínimo para a formação do novo segmento do indicador ZigZag e
(3) o número de extremos para obtenção dos dados finais.
Todos os indicadores neste artigo terão os mesmos parâmetros.
Fig. 1. Parâmetros de entrada do indicador
O FrequencyChangeZZ indicador exibe o gráfico em uma subjanela conforme é exibido abaixo. O indicador ZigZag é carregado no gráfico principal para maior visibilidade. O indicador mostra claramente quando o preço diminui na escolha da direção.
Fig. 2. Indicador FrequencyChangeZZ
Indicador SumSegmentsZZ
No indicador SumSegmentsZZ, a função principal para obter os dados se parece conforme exibido no trecho de código a seguir. Todo código é o mesmo que no exemplo anterior. A única diferença é que três buffers do indicador são preenchidos aqui para segmentos ascendentes e descendentes separadamente. Mais um buffer é usado para calcular a média desses parâmetros nos valores atuais.
//+------------------------------------------------------------------+ //| Preenche os buffers do indicador | //+------------------------------------------------------------------+ void FillIndicatorBuffers(const int i,const datetime &time[]) { int copy_total=1000; for(int t=0; t<10; t++) { if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total && CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total && CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total) { //--- Obtém os dados ZZ zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); //--- Obtém os dados por segmentos segments_up_total_buffer[i] =zz.SumSegmentsUp(); segments_dw_total_buffer[i] =zz.SumSegmentsDown(); segments_average_buffer[i] =(segments_up_total_buffer[i]+segments_dw_total_buffer[i])/2; break; } } }
Após anexar o indicador SumSegmentsZZ no gráfico, você verá o resultado como na imagem abaixo. Aqui nós podemos ver que depois que a linha azul excede a linha vermelha, a soma dos segmentos ascendentes é maior que a soma dos descendentes. A situação é invertida se a linha vermelha exceder a linha azul. Somente os experimentos no testador de estratégia podem nos informar se essa é uma fonte confiável de informação sobre a direção futura do preço. À primeira vista, quanto maior a soma dos segmentos unidirecionais exceder a soma dos segmentos opostos, maior a probabilidade de reversão.
Fig. 3. Indicador SumSegmentsZZ
Indicador PercentageSegmentsZZ
Agora, vamos dar uma olhada no indicador PercentageSegmentsZZ. Como no caso anterior, três buffers do indicador devem ser preenchidos na função principal do indicador: um buffer para cada uma das taxas percentuais da soma dos segmentos direcionadas (1) para cima e (2) para baixo, bem como um buffer (3) pela diferença entre esses valores.
//+------------------------------------------------------------------+ //| Preenche os buffers do indicador | //+------------------------------------------------------------------+ void FillIndicatorBuffers(const int i,const datetime &time[]) { int copy_total=1000; for(int t=0; t<10; t++) { if(CopyBuffer(zz_handle,2,time[i],copy_total,h_zz_buffer_temp)==copy_total && CopyBuffer(zz_handle,3,time[i],copy_total,l_zz_buffer_temp)==copy_total && CopyTime(_Symbol,_Period,time[i],copy_total,t_zz_buffer_temp)==copy_total) { //--- Obtém os dados ZZ zz.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); //--- Obtém os dados sobre os segmentos double sum_up =zz.SumSegmentsUp(); double sum_dw =zz.SumSegmentsDown(); double sum =sum_up+sum_dw; //--- Taxa percentual e diferença if(sum>0) { segments_up_total_buffer[i] =zz.PercentSumSegmentsUp(); segments_dw_total_buffer[i] =zz.PercentSumSegmentsDown(); segments_difference_buffer[i] =fabs(segments_up_total_buffer[i]-segments_dw_total_buffer[i]); break; } } } }
O resultado é exibido abaixo. Vamos tentar interpretar isso. Quando a diferença nas taxas percentuais entre as quantidades de segmentos multidirecionais é menor que um determinado limite, isso pode ser considerado como lateralizado. Nesse caso, nós devemos também ter em mente que as taxas devem ser alteradas frequentemente, uma vez que o preço pode mudar em uma direção por um longo tempo, enquanto a diferença é menor do que o nível selecionado pelo otimizador. Nestes casos, nós devemos aplicar os modelos considerando a formação dos padrões em uma determinada sequência.
Fig. 4. Indicador PercentageSegmentsZZ
Indicador MultiPercentageSegmentsZZ
No artigo anterior, nós demonstramos o EA analisando os dados do indicador ZigZag a partir de períodos de tempo maiores e menores, simultaneamente. Assim, foi possível analisar com mais detalhes como o preço se comportou dentro dos segmentos a partir do período de tempo maior. Em outras palavras, nós definimos como os segmentos de período de tempo maiores se formaram em um período de tempo menor. Vamos ver como esse grupo de parâmetros se parecerá na forma de um indicador separado exibindo esses valores no histórico de preços.
Como no EA do artigo anterior, nós receberemos quatro valores da diferença entre as taxas percentuais das somas do segmento de direção oposta: um valor é para o período de tempo maior e três valores são para o período menor. Os valores são calculados pelos três últimos segmentos do indicador ZigZag no período de tempo maior. As cores dos buffers do indicador serão as mesmas que no EA da parte anterior. Depois disso, nós desenvolveremos um EA para testar o indicador, de modo que seja muito mais fácil entendermos quais dados e para qual período de tempo nós observamos no gráfico.
//--- Número de buffers #property indicator_buffers 4 #property indicator_plots 4 //--- Cores dos buffers de cor #property indicator_color1 clrSilver #property indicator_color2 clrRed #property indicator_color3 clrLimeGreen #property indicator_color4 clrMediumPurple
Declaração de quatro instâncias da classe CZigZagModule:
#include <Addons\Indicators\ZigZag\ZigZagModule.mqh>
CZigZagModule zz_higher_tf;
CZigZagModule zz_current0;
CZigZagModule zz_current1;
CZigZagModule zz_current2;
Vamos adicionar a capacidade de definir um período de tempo maior para o indicador nos parâmetros de entrada:
input int NumberOfBars =0; // Número de barras para calcular o ZZ input int MinImpulseSize =0; // Pontos mínimos em um raio input int CopyExtremum =5; // Copiar os extremos input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Período de tempo maior
A principal função para preencher os buffers do indicador é implementada da seguinte forma. Primeiro, obtenha os dados de origem do maior período de tempo especificado nos parâmetros de entrada. Então obtenha os dados finais e salve o valor do parâmetro. Em seguida, nós obtemos consistentemente os dados sobre os três segmentos do indicador do maior período de tempo. Depois disso, preencha todos os buffers do indicador. Eu tive que desenvolver dois blocos de código separados, para que o indicador pudesse ser calculado corretamente no histórico e na última barra em tempo real/testador.
//+------------------------------------------------------------------+ //| Preenche os buffers do indicador | //+------------------------------------------------------------------+ void FillIndicatorBuffers(const int i,const int total,const datetime &time[]) { int index=total-i-1; int copy_total=1000; int h_buff=2,l_buff=3; datetime start_time_in =NULL; datetime stop_time_in =NULL; //--- Obtém os dados de origem do maior período de tempo datetime stop_time=time[i]-(PeriodSeconds(HigherTimeframe)*copy_total); CopyBuffer(zz_handle_htf,2,time[i],stop_time,h_zz_buffer_temp); CopyBuffer(zz_handle_htf,3,time[i],stop_time,l_zz_buffer_temp); CopyTime(_Symbol,HigherTimeframe,time[i],stop_time,t_zz_buffer_temp); //--- Obtém os dados finais do maior período de tempo zz_higher_tf.GetZigZagData(h_zz_buffer_temp,l_zz_buffer_temp,t_zz_buffer_temp); double htf_value=zz_higher_tf.PercentSumSegmentsDifference(); //--- Dado do primeiro segmento zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,0,start_time_in,stop_time_in); zz_current0.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in); //--- Dado do segundo segmento zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,1,start_time_in,stop_time_in); zz_current1.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in); //--- Dado do terceiro segmento zz_higher_tf.SegmentTimes(zz_handle_current,h_buff,l_buff,_Symbol,HigherTimeframe,_Period,2,start_time_in,stop_time_in); zz_current2.GetZigZagData(zz_handle_current,_Symbol,_Period,start_time_in,stop_time_in); //--- Na última barra if(i<total-1) { buffer_zz_higher_tf[i] =htf_value; buffer_segment_0[i] =zz_current0.PercentSumSegmentsDifference(); buffer_segment_1[i] =zz_current1.PercentSumSegmentsDifference(); buffer_segment_2[i] =zz_current2.PercentSumSegmentsDifference(); } //--- No histórico else { //--- Caso de haver uma nova barra do maior período de tempo if(new_bar_time!=t_zz_buffer_temp[0]) { new_bar_time=t_zz_buffer_temp[0]; //--- if(i>2) { int f=1,s=2; buffer_zz_higher_tf[i-f] =buffer_zz_higher_tf[i-s]; buffer_segment_0[i-f] =buffer_segment_0[i-s]; buffer_segment_1[i-f] =buffer_segment_1[i-s]; buffer_segment_2[i-f] =buffer_segment_2[i-s]; } } else { buffer_zz_higher_tf[i] =htf_value; buffer_segment_0[i] =zz_current0.PercentSumSegmentsDifference(); buffer_segment_1[i] =zz_current1.PercentSumSegmentsDifference(); buffer_segment_2[i] =zz_current2.PercentSumSegmentsDifference(); } } }
Vamos fazer uma cópia do EA do artigo anterior e adicionar algumas linhas para testar o indicador MultiPercentageSegmentsZZ. Adiciona o parâmetro de entrada para definir um período de tempo maior. Para que o indicador seja exibido durante o teste do EA no modo de visualização do testador, é suficiente para obter o seu manipulador.
//--- Parâmetros de entrada input uint CopyExtremum =3; // Copia os extremos input int MinImpulseSize =0; // Tamanho do impulso mínimo input ENUM_TIMEFRAMES HigherTimeframe =PERIOD_H1; // Período de tempo maior ... //+------------------------------------------------------------------+ //| Função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit(void) { ... //--- Caminho para o indicador ZZ string zz_path1="Custom\\ZigZag\\ExactZZ_Plus.ex5"; string zz_path2="Custom\\ZigZag\\MultiPercentageSegmentsZZ.ex5"; //--- Obtemos os manipuladores do indicador zz_handle_current =::iCustom(_Symbol,_Period,zz_path1,0,MinImpulseSize,false,false); zz_handle_higher_tf =::iCustom(_Symbol,HigherTimeframe,zz_path1,0,MinImpulseSize,false,false); zz_handle =::iCustom(_Symbol,_Period,zz_path2,0,MinImpulseSize,CopyExtremum,HigherTimeframe); ... return(INIT_SUCCEEDED); }
É assim que fica no testador:
Fig. 5. Indicador MultiPercentageSegmentsZZ
Todos os indicadores descritos acima podem ser usados em várias combinações e ao mesmo tempo em diferentes períodos de tempo. Agora, vamos usar as ferramentas descritas para coletar algumas estatísticas sobre o conjunto de símbolos para entender quais delas são mais adequadas para a negociação no canal de preço.
EA para coleta e exibição de estatísticas
Como complemento, o artigo apresenta uma nova versão da biblioteca EasyAndFast para desenvolvimento de interfaces gráficas do usuário. Aqui nós vamos listar apenas os novos recursos da biblioteca:
- Alterar a cor de fundo de cada célula da tabela (classe CTable).
- Direção da ordenação.
- Se o modo apropriado estiver ativado, uma linha não será destacada ao clicar em uma caixa de seleção na célula da tabela.
- Adicionado o suporte para o teclado numérico na classe CKeys.
- Adicionado a classe CFrame para combinar os elementos em grupos:
Fig. 6. Combinando elementos em grupos
- Rolagem vertical em tabelas e listas.
- Adicionado a classe CWndCreate, que inclui os métodos de modelos básicos para a criação rápida da maioria dos elementos. Ele deve ser usado como base para a classe personalizada. O uso dessa classe permite que você não repita a declaração e a implementação dos mesmos métodos de criação de elementos em diferentes projetos, o que acelera bastante o desenvolvimento.
- Adicionada a verificação da sequência correta de criação de elementos para a classe CElement.
- Na classe CWndEvents, o ID é sempre redefinido após a remoção de um elemento.
- Adicionado o método GetActiveWindowIndex() para o CWndEvents para receber o índice da janela ativada.
- Corrigido a classe CListView. Alguns campos auxiliares devem ser redefinidos no método Clear() para evitar arrays fora de alcance em outros métodos da classe CListView.
A nova versão da biblioteca pode ser baixada na CodeBase.
Em seguida, vamos criar um EA de teste para reunir algumas estatísticas usando a nova versão da biblioteca EasyAndFast. Nós vamos começar desenvolvendo a interface gráfica do usuário (GUI) do aplicativo e, em seguida, procederemos aos métodos para coletar e exibir as estatísticas.
Vamos definir quais controles da GUI nós precisamos:
- Formulário de controles.
- Barra de estado.
- Campo de entrada para a ordenação de moedas que devem ser coletadas na lista da janela do Observador do Mercado.
- Calendários suspensos para indicar as datas de início e término da coleta das estatísticas.
- Campo de entrada para definir o nível do indicador.
- Botão de solicitação de dados.
- Tabela para exibir os dados coletados.
- Barra de progresso.
Como mencionado anteriormente, a classe CWndCreate deve ser incluída na classe personalizada como base para desenvolver uma GUI mais rápida e mais conveniente. A conexão completa é a seguinte: CWndContainer -> CWndEvents -> CWndCreate -> CProgram. A presença da classe CWndCreate permite criar elementos da GUI em uma única linha, sem criar métodos separados em uma classe personalizada. A classe contém modelos diferentes para quase todos os elementos da biblioteca. Você pode adicionar novos modelos, se necessário.
Para criar uma GUI, declare os elementos contidos na lista acima, conforme mostrado no código a seguir. A versão atual da classe CWndCreate não tem nenhum modelo de criação de tabela rápida, portanto vamos desenvolver nossa próprio método.
//+------------------------------------------------------------------+ //| Program.mqh | //| Copyright 2018, MetaQuotes Software Corp. //| http://www.mql5.com | //+------------------------------------------------------------------+ #include <EasyAndFastGUI\WndCreate.mqh> //+------------------------------------------------------------------+ //| Classe para o desenvolvimento da aplicação | //+------------------------------------------------------------------+ class CProgram : public CWndCreate { private: //--- Janela CWindow m_window; //--- Barra de status CStatusBar m_status_bar; //--- Calendários suspensos CDropCalendar m_from_date; CDropCalendar m_to_date; //--- Botões CButton m_request; //--- Campos de entrada CTextEdit m_filter; CTextEdit m_level; //--- Caixas de combinação CComboBox m_data_type; //--- Tabelas CTable m_table; //--- Barra de progresso CProgressBar m_progress_bar; //--- public: //--- Cria uma GUI bool CreateGUI(void); //--- private: //--- Tabelas bool CreateTable(const int x_gap,const int y_gap); };
Para criar uma interface gráfica com tal conteúdo, basta chamar os métodos necessários da classe CWndCreate especificando os valores das propriedades como argumentos, conforme mostrado no bloco de código abaixo. Para definir uma propriedade com a qual um parâmetro do método está relacionado, defina o cursor de texto nele e clique em Ctrl + Shift + Espaço:
Fig. 7. Visualizando os parâmetros do método
Se você precisar definir propriedades adicionais, poderá fazer isso da mesma maneira mostrada no exemplo que envolve o campo de entrada do filtro de moeda. Aqui, é indicado que a caixa de seleção deve ser ativada por padrão logo após a criação do elemento.
//+------------------------------------------------------------------+ //| Cria a GUI | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Cria os formulários de controle if(!CWndCreate::CreateWindow(m_window,"ZZ Market Scanner",1,1,640,480,true,true,true,true)) return(false); //--- Barra de status string text_items[1]; text_items[0]="For Help, press F1"; int width_items[]={0}; if(!CWndCreate::CreateStatusBar(m_status_bar,m_window,1,23,22,text_items,width_items)) return(false); //--- Campo de entrada do filtro de moeda if(!CWndCreate::CreateTextEdit(m_filter,"Symbols filter:",m_window,0,true,7,25,627,535,"USD","Example: EURUSD,GBP,NOK")) return(false); else m_filter.IsPressed(true); //--- Calendário suspenso if(!CWndCreate::CreateDropCalendar(m_from_date,"From:",m_window,0,7,50,130,D'2018.01.01')) return(false); if(!CWndCreate::CreateDropCalendar(m_to_date,"To:",m_window,0,150,50,117,::TimeCurrent())) return(false); //--- Campo de entrada para especificar o nível if(!CWndCreate::CreateTextEdit(m_level,"Level:",m_window,0,false,280,50,85,50,100,0,1,0,30)) return(false); //--- Botão if(!CWndCreate::CreateButton(m_request,"Request",m_window,0,375,50,70)) return(false); //--- Tabela if(!CreateTable(2,75)) return(false); //--- Barra de progresso if(!CWndCreate::CreateProgressBar(m_progress_bar,"Processing:",m_status_bar,0,2,3)) return(false); //--- Fim do desenvolvimento da GUI CWndEvents::CompletedGUI(); return(true); }
No caso de uma tabela, crie um método personalizado, já que é um elemento complexo com um grande número de propriedades que devem ser especificadas antes de criar um elemento. É para apresentar quatro colunas. A primeira mostrará os pares de moedas. Os restantes mostrarão dados estatísticos em três períodos de tempo: M5, H1 e H8.
//+------------------------------------------------------------------+ //| Cria a tabela | //+------------------------------------------------------------------+ bool CProgram::CreateTable(const int x_gap,const int y_gap) { #define COLUMNS1_TOTAL 4 #define ROWS1_TOTAL 1 //--- Salva um ponteiro para o elemento principal m_table.MainPointer(m_window); //--- Array de largura da coluna int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,50); width[0]=80; //--- Array de deslocamento de texto em colunas pelo eixo X int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,7); //--- Array do alinhamento de texto em colunas ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[0]=ALIGN_LEFT; //--- Propriedades m_table.TableSize(COLUMNS1_TOTAL,ROWS1_TOTAL); m_table.TextAlign(align); m_table.ColumnsWidth(width); m_table.TextXOffset(text_x_offset); m_table.ShowHeaders(true); m_table.IsSortMode(true); m_table.IsZebraFormatRows(clrWhiteSmoke); m_table.AutoXResizeMode(true); m_table.AutoYResizeMode(true); m_table.AutoXResizeRightOffset(2); m_table.AutoYResizeBottomOffset(24); //--- Cria um controle if(!m_table.CreateTable(x_gap,y_gap)) return(false); //--- Cabeçalhos string headers[]={"Symbols","M5","H1","H8"}; for(uint i=0; i<m_table.ColumnsTotal(); i++) m_table.SetHeaderText(i,headers[i]); //--- Adiciona um objeto ao array comum de grupos de objetos CWndContainer::AddToElementsArray(0,m_table); return(true); }
Agora vamos considerar os métodos para a obtenção dos dados. Primeiro, nós precisamos obter os símbolos com os quais nós devemos trabalhar. Nesta versão do EA, nós receberemos os dados de símbolos de Forex. Ao mesmo tempo, nós vamos excluir os símbolos, para os quais a negociação está desativada. Aqui, nós também precisaremos do método auxiliar CheckFilterText() para verificar o símbolo pelo filtro. No campo de entrada, os usuários podem inserir os valores de texto separados por vírgulas que devem estar presentes nos nomes dos símbolos. Se a caixa de seleção de campo estiver desativada ou o texto não for digitado, a verificação não é executada. Se as verificações forem passadas e uma correspondência for encontrada, o texto inserido será dividido em substrings e a pesquisa por uma string necessária será executada.
class CProgram : public CWndCreate { private: //--- Verifica um símbolo por filtro bool CheckFilterText(const string symbol_name); }; //+------------------------------------------------------------------+ //| Verifica um símbolo por filtro | //+------------------------------------------------------------------+ bool CProgram::CheckFilterText(const string symbol_name) { bool check=false; //--- Se o filtro com o nome do símbolo estiver ativado if(!m_filter.IsPressed()) return(true); //--- Se um texto for digitado string text=m_filter.GetValue(); if(text=="") return(true); //--- Divide em substrings string elements[]; ushort sep=::StringGetCharacter(",",0); ::StringSplit(text,sep,elements); //--- Verifique se há correspondência int elements_total=::ArraySize(elements); for(int e=0; e<elements_total; e++) { //--- Exclui espaços externos ::StringTrimLeft(elements[e]); ::StringTrimRight(elements[e]); //--- Se uma correspondência for detectada if(::StringFind(symbol_name,elements[e])>-1) { check=true; break; } } //--- Resultado return(check); }
No método CProgram::GetSymbols(), ele repassa todos os símbolos presentes no servidor em um loop e coleta os que se encaixam nos critérios especificados no array. No loop geral, todos os símbolos são excluídos da janela da Observação do Mercado. Apenas os contidos no array são adicionados à janela depois.
class CProgram : public CWndCreate { private: //--- array de símbolos string m_symbols[]; //--- private: //--- Obtém os símbolos void GetSymbols(void); }; //+------------------------------------------------------------------+ //| Obtém os símbolos | //+------------------------------------------------------------------+ void CProgram::GetSymbols(void) { //--- Progresso m_progress_bar.LabelText("Get symbols..."); m_progress_bar.Update(0,1); //--- Limpa um array de símbolos ::ArrayFree(m_symbols); //--- Coleta o array de símbolos de Forex int symbols_total=::SymbolsTotal(false); for(int i=0; i<symbols_total; i++) { //--- Obtém um nome do símbolo string symbol_name=::SymbolName(i,false); //--- Oculta na janela de Observação do Mercado ::SymbolSelect(symbol_name,false); //--- Se este não for um símbolo de Forex, vá para o próximo if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_CALC_MODE)!=SYMBOL_CALC_MODE_FOREX) continue; //--- Se a negociação estiver desativada, vá para a próximo if(::SymbolInfoInteger(symbol_name,SYMBOL_TRADE_MODE)==SYMBOL_TRADE_MODE_DISABLED) continue; //--- Verifica um símbolo por filtro if(!CheckFilterText(symbol_name)) continue; //--- Salve um símbolo no array int array_size=::ArraySize(m_symbols); ::ArrayResize(m_symbols,array_size+1,1000); m_symbols[array_size]=symbol_name; } //--- Se um array estiver vazio, define 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; } //--- Exibe na janela de 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); }
Para obter os dados sobre os símbolos coletados, nós devemos primeiro obter os manipuladores do indicador neles. Toda vez que nós obtemos o manipulador do indicador, nós precisamos esperar até o final do cálculo antes de copiar os dados para análise posterior. Depois que todos os dados são recebidos, os cálculos necessários são realizados.
O método CProgram::GetSymbolsData() é usado para isso. Ele aceita dois parâmetros: símbolo e período de tempo. Depois de receber o manipulador do indicador, descubra quantas barras estão presentes no intervalo de tempo especificado. O intervalo de datas pode ser especificado usando os controles da GUI do aplicativo. Em seguida, nós tentamos obter a quantidade de dados do indicador calculado. O cálculo do indicador pode não ser concluído imediatamente após o recebimento do manipulador. Portanto, se a função BarsCalculated() retorna -1, nós fazemos novas tentativas para obter um valor válido até que seja igual ou exceda o número total de barras no intervalo de tempo especificado.
Depois que os dados do indicador são calculados, nós podemos tentar colocá-los no array. Ele pode levar a várias tentativas até que a quantidade seja também maior ou igual ao número total de barras.
Se os indicadores forem copiados com sucesso para o array, resta apenas fazer os cálculos necessários. Neste caso, nós calculamos a porcentagem da quantidade total de dados para o valor, em que o valor do indicador está acima do nível especificado. Esse nível também pode ser especificado na GUI do aplicativo.
No final do método, remova o manipulador do indicador liberando sua parte de cálculo. O método CProgram::GetSymbolsData() é chamado várias vezes para uma lista selecionada de símbolos e vários períodos de tempo. O cálculo para cada um deles deve ser executado apenas uma vez e o valor resultante é exibido na tabela da GUI, portanto, os manipuladores não são mais necessárias e podem ser removidos.
class CProgram : public CWndCreate { private: //--- Obtém os dados do símbolo double GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period); }; //+------------------------------------------------------------------+ //| Obtém os dados do símbolo | //+------------------------------------------------------------------+ double CProgram::GetSymbolsData(const string symbol,const ENUM_TIMEFRAMES period) { double result =0.0; int buffer_index =2; //--- Obtém o manipulador do indicador string path ="::Indicators\\Custom\\ZigZag\\PercentageSegmentsZZ.ex5"; int handle =::iCustom(symbol,period,path,0,0,5); if(handle!=INVALID_HANDLE) { //--- Copia os dados dentro de um intervalo especificado double data[]; datetime start_time =m_from_date.SelectedDate(); datetime end_time =m_to_date.SelectedDate(); //--- Número de barras em um intervalo especificado int bars_total=::Bars(symbol,period,start_time,end_time); //--- Número de barras em um intervalo especificado int bars_calculated=::BarsCalculated(handle); if(bars_calculated<bars_total) { while(true) { ::Sleep(100); bars_calculated=::BarsCalculated(handle); if(bars_calculated>=bars_total) break; } } //--- Obtém os dados int copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied<1) { while(true) { ::Sleep(100); copied=::CopyBuffer(handle,buffer_index,start_time,end_time,data); if(copied>=bars_total) break; } } //--- Encerra se nenhum dado for recebido int total=::ArraySize(data); if(total<1) return(result); //--- Conta o número de repetições int counter=0; for(int k=0; k<total; k++) { if(data[k]>(double)m_level.GetValue()) counter++; } //--- Razão percentual result=((double)counter/(double)total)*100; } //--- Libera o indicador ::IndicatorRelease(handle); //--- Retorna o valor return(result); }
Toda vez que uma nova lista de símbolos é formada, a tabela precisa ser reconstruída. Para fazer isso, basta excluir todas as linhas e adicionar o valor necessário.
class CProgram : public CWndCreate { private: //--- Re-constrói a tabela void RebuildingTables(void); }; //+------------------------------------------------------------------+ //| Re-constrói a tabela | //+------------------------------------------------------------------+ void CProgram::RebuildingTables(void) { //--- Remove todas as linhas m_table.DeleteAllRows(); //--- Adiciona os dados int symbols_total=::ArraySize(m_symbols); for(int i=1; i<symbols_total; i++) m_table.AddRow(i); }
O método CProgram::SetData() é usado para preencher as colunas da tabela com os dados. Dois parâmetros (índice da coluna e período de tempo) são passados para ele. Aqui, nós movemos pelas células de uma coluna especificada e preenchemos elas com os valores calculados em um loop. A barra de progresso exibe um símbolo e um período de tempo, cujos dados acabam de ser recebidos, para que os usuários entendam o que está acontecendo no momento.
class CProgram : public CWndCreate { private: //--- Define os valores para uma coluna especificada void SetData(const int column_index,const ENUM_TIMEFRAMES period); //--- Período de tempo para uma string string GetPeriodName(const ENUM_TIMEFRAMES period); }; //+------------------------------------------------------------------+ //| Define os valores para uma coluna especificada | //+------------------------------------------------------------------+ void CProgram::SetData(const int column_index,const ENUM_TIMEFRAMES period) { for(uint r=0; r<(uint)m_table.RowsTotal(); r++) { double value=GetSymbolsData(m_symbols[r],period); m_table.SetValue(column_index,r,string(value),2,true); m_table.Update(); //--- Progresso m_progress_bar.LabelText("Data preparation ["+m_symbols[r]+","+GetPeriodName(period)+"]..."); m_progress_bar.Update(r,m_table.RowsTotal()); } } //+------------------------------------------------------------------+ //| Retorna o valor da string do período | //+------------------------------------------------------------------+ string CProgram::GetPeriodName(const ENUM_TIMEFRAMES period) { return(::StringSubstr(::EnumToString(period),7)); }
O principal método para preencher a tabela com os dados é CProgram::SetDataToTable() A tabela é reconstruída aqui primeiro. Em seguida, nós precisamos definir os cabeçalhos e tipo de dados nele (TYPE_DOUBLE). Definir os símbolos coletados para a primeira coluna. Re-desenhar a tabela para ver as alterações imediatamente.
Agora nós podemos começar a receber os dados do indicador em todos os símbolos e períodos de tempo especificados. Para fazer isso, basta chamar o método CProgram::SetData() passando o índice de coluna e o período de tempo como parâmetros.
class CProgram : public CWndCreate { private: //--- Preenche a tabela com os dados void SetDataToTable(void); }; //+------------------------------------------------------------------+ //| Preenche a tabela com os dados | //+------------------------------------------------------------------+ void CProgram::SetDataToTable(void) { //--- Progresso m_progress_bar.LabelText("Data preparation..."); m_progress_bar.Update(0,1); //--- Re-constrói a tabela RebuildingTable(); //--- Cabeçalhos string headers[]={"Symbols","M5","H1","H8"}; for(uint i=0; i<m_table.ColumnsTotal(); i++) m_table.SetHeaderText(i,headers[i]); for(uint i=1; i<m_table.ColumnsTotal(); i++) m_table.DataType(i,TYPE_DOUBLE); //--- Define os valores para a primeira coluna for(uint r=0; r<(uint)m_table.RowsTotal(); r++) m_table.SetValue(0,r,m_symbols[r],0,true); //--- Mostrar a tabela m_table.Update(true); //--- Preenche as colunas restantes com os dados SetData(1,PERIOD_M5); SetData(2,PERIOD_H1); SetData(3,PERIOD_H8); }
Antes de receber os novos dados usando o CProgram::GetData(), nós devemos tornar a barra de progresso visível com a ajuda do método CProgram::StartProgress(). Após novos dados serem recebidos, oculte a barra de progresso e remova o foco do botão pressionado. Para fazer isso, chame o método CProgram::EndProgress().
class CProgram : public CWndCreate { private: //--- Obtém os dados void GetData(void); //--- Progresso (1) início e (2) fim void StartProgress(void); void EndProgress(void); }; //+------------------------------------------------------------------+ //| Obtém os dados | //+------------------------------------------------------------------+ void CProgram::GetData(void) { //--- Início do progresso StartProgress(); //--- Obtém a lista de símbolos GetSymbols(); //--- Preenche a tabela com os dados SetDataToTable(); //--- Fim do progresso EndProgress(); } //+------------------------------------------------------------------+ //| Início do progresso | //+------------------------------------------------------------------+ void CProgram::StartProgress(void) { m_progress_bar.LabelText("Please wait..."); m_progress_bar.Update(0,1); m_progress_bar.Show(); m_chart.Redraw(); } //+------------------------------------------------------------------+ //| Fim do progresso | //+------------------------------------------------------------------+ void CProgram::EndProgress(void) { //--- Oculta a barra de progresso m_progress_bar.Hide(); //--- Atualiza o botão m_request.MouseFocus(false); m_request.Update(true); m_chart.Redraw(); }
Quando um usuário clica em Request, o evento personalizado ON_CLICK_BUTTON é gerado e nós podemos definir um botão pressionado pelo ID do elemento. Se este é o botão Request, é iniciado o processo de obtenção dos dados.
No método de criação de tabelas, nós incluímos a capacidade de ordenar a tabela clicando nos cabeçalhos. O evento personalizado ON_SORT_DATA é gerado toda vez que nós fazemos isso. Quando o evento é recebido, a tabela deve ser atualizada para exibir as alterações.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Botão pressionando eventos if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { if(lparam==m_request.Id()) { //--- Obtém os dados GetData(); return; } //--- return; } //--- Eventos da tabela ordenada if(id==CHARTEVENT_CUSTOM+ON_SORT_DATA) { if(lparam==m_table.Id()) { m_table.Update(true); return; } //--- return; } }
Agora vamos ver os resultados. Se nós compilarmos o programa e o carregarmos no gráfico, o resultado será como na imagem abaixo. Os seguintes parâmetros são definidos por padrão:
- O campo de entrada Symbols filter está ativado. Ele indica que é necessário obter os dados apenas para os símbolos contendo USD em seus nomes.
- Os dados devem ser obtidos dentro do intervalo de 01.01.2018 - 21.12.2018.
- O valor Level usado como ponto de referência para os cálculos é definido como 30.
- Nesta versão, os períodos de tempo nos quais os cálculos são executados são rigidamente definidos no código: M5, H1 e H8.
Fig. 8. GUI da aplicação em MQL
Pressionando Request inicia a aquisição de dados:
Fig. 9. Recebendo os dados
Depois de receber todos os dados, você pode ordená-los:
Fig. 10. Ordenação dos dados da tabela
Você pode modificar e usar este aplicativo para resolver algumas das suas tarefas. A tabela pode ser preenchida com outros parâmetros.
Abaixo, eu fornecerei outro exemplo demonstrando como melhorar ainda mais a visibilidade dos dados da tabela. Como eu já mencionei no começo desta seção, a versão mais recente da biblioteca EasyAndFast apresenta a capacidade de definir a cor de fundo das células da tabela. Isso permite que você formate a tabela como você vê ajustada da mesma maneira que é feita em vários editores de tabela. A captura de tela abaixo mostra dados de formatação em planilhas do Excel. Cada célula tem sua própria cor de plano de fundo restante no mesmo valor, mesmo ao ordenar os arrays.
Fig. 11. Escalas de cores em Excel
Tal formatação possibilita a rápida execução de análise visual dos dados.
Vamos fazer pequenas alterações e adições ao aplicativo MQL considerado acima. Para definir uma cor exclusiva para cada célula da tabela, desabilite a formatação no estilo zebra. Comente esta linha de código.
// m_table.IsZebraFormatRows(clrWhiteSmoke);
Agora vamos criar o método CProgram::SetColorsToTable() para a formatação da tabela. A classe CColors deve ser usada para trabalhar com cores. Ela já está presente na biblioteca para criar as GUIs, portanto, não há necessidade de incluir o arquivo no projeto. Declare dois arrays para o trabalho: (1) array para obter as cores do gradiente e (2) o array de cores da qual o gradiente deve ser formado. Nós vamos criar o gradiente de três cores. Quanto menor o valor, mais vermelha fica a cor (clrTomato). Quanto maior o valor, mais azul ele se torna (clrCornflowerBlue). Vamos adicionar a cor branca para separar essas duas zonas de cor.
Defina o tamanho dos intervalos de valores do mínimo ao máximo. Este será o tamanho do array de gradientes. O método CColors::Gradient() é usado para definir o tamanho do array e preenchê-lo. As cores das células da tabela são definidas no loop final. Para não sair do alcance do array, o índice é calculado como o valor da célula menos o valor mínimo do intervalo. No final do método, a tabela é atualizada para exibir as alterações implementadas.
class CProgram : public CWndCreate { private: //--- Preenchendo a tabela com cor de fundo para células void SetColorsToTable(void); }; //+------------------------------------------------------------------+ //| Formatando a tabela | //+------------------------------------------------------------------+ void CProgram::SetColorsToTable(void) { //--- Para trabalhar com cores CColors clr; //--- Array para receber o gradiente color out_colors[]; //--- Gradiente de três cores color colors[3]={clrTomato,clrWhite,clrCornflowerBlue}; //--- Encontra os maiores e menores valores da tabela double max =0; double min =100; for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++) { for(uint r=0; r<(uint)m_table.RowsTotal(); r++) { max =::fmax(max,(double)m_table.GetValue(c,r)); min =::fmin(min,(double)m_table.GetValue(c,r)); } } //--- Corrige para o inteiro mais próximo abaixo max =::floor(max); min =::floor(min); //--- Obtém o intervalo int range =int(max-min)+1; //--- Obtém o array do gradiente de cores clr.Gradient(colors,out_colors,range); //--- Define a cor de fundo das células for(uint c=1; c<(uint)m_table.ColumnsTotal(); c++) { for(uint r=0; r<(uint)m_table.RowsTotal(); r++) { int index=(int)m_table.GetValue(c,r)-(int)min; m_table.BackColor(c,r,out_colors[index],true); } } //--- Atualiza a tabela m_table.Update(); }
Abaixo você pode ver como isso fica na GUI. Nesse caso, os resultados mostram que quanto menor o valor, menor o número de tendências na área considerada. Seria sensato definir um intervalo de datas tão amplo quanto possível para obter informações usando o máximo de dados possível.
Fig. 12. Escala de cores para visualizar os dados da tabela
Quanto maior o intervalo de datas, mais dados são usados e, consequentemente, mais tempo será necessário para gerar os dados e calcular os parâmetros. Se não houver dados suficientes, será feito uma tentativa de baixá-los do servidor.
Contagem do número de segmentos por tamanho
Agora, vamos desenvolver um programa para contar o número de segmentos pelo tamanho deles. Copia o EA da seção anterior e faz as alterações necessárias e adições a ele. Haverá duas tabelas aqui. O primeiro é usar apenas uma coluna com a lista de símbolos analisados. O segundo usa duas colunas de dados: (1) o aumento de intervalos em pontos e (2) o número de segmentos por intervalos na primeira coluna. Abaixo, você pode ver como a GUI aparece logo após o upload do aplicativo no gráfico.
Fig. 13. Programa para calcular o número de segmentos por tamanho
O botão Request solicita a lista de símbolos por um filtro especificado. Ao clicar Calculate, os dados do intervalo de tempo especificado são coletados e distribuídos na segunda tabela.
Basicamente, todos os métodos permaneceram os mesmos que no EA anterior, então vamos considerar apenas as coisas relacionadas à segunda tabela. Primeiro, nós precisamos receber os dados do indicador. Isso é feito no método CProgram::GetIndicatorData(). Inicialmente, nós conectamos ao indicador ZigZag e depois obtemos os seus dados no intervalo de tempo especificado. O símbolo, período de tempo e o número de segmentos de indicadores obtidos são exibidos na barra de status.
class CProgram : public CWndCreate { private: //--- Obtém os dados do indicador void GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period); }; //+------------------------------------------------------------------+ //| Recebe os dados do indicador | //+------------------------------------------------------------------+ void CProgram::GetIndicatorData(const string symbol,const ENUM_TIMEFRAMES period) { //--- Obtém o manipulador do indicador string path ="::Indicators\\Custom\\ZigZag\\ExactZZ_Plus.ex5"; int handle =::iCustom(symbol,period,path,0,0); if(handle!=INVALID_HANDLE) { //--- Copia os dados no intervalo especificado datetime start_time =m_from_date.SelectedDate(); datetime end_time =m_to_date.SelectedDate(); m_zz.GetZigZagData(handle,2,3,symbol,period,start_time,end_time); //--- Exibe os dados na barra de status string text="["+symbol+","+(string)GetPeriodName(period)+"] - Segments total: "+(string)m_zz.SegmentsTotal(); m_status_bar.SetValue(0,text); m_status_bar.GetItemPointer(0).Update(true); } //--- Libera o indicador ::IndicatorRelease(handle); }
As faixas de preço com uma etapa especificada devem ser calculadas para a primeira coluna. O método CProgram::GetLevels() é usado para isso. Para definir o número de intervalos, nós devemos primeiro obter o tamanho máximo do segmento no conjunto de dados obtidos. Em seguida, preencha o array com os níveis usando uma etapa especificada em um loop até que o valor máximo seja atingido.
class CProgram : public CWndCreate { private: //--- array de intervalo int m_levels_array[]; //--- private: //--- Obtém os níveis void GetLevels(void); }; //+------------------------------------------------------------------+ //| Obtém os níveis | //+------------------------------------------------------------------+ void CProgram::GetLevels(void) { //--- Libera o array ::ArrayFree(m_levels_array); //--- Obtém o tamanho máximo do segmento int max_value=int(m_zz.LargestSegment()/m_symbol.Point()); //--- Preenche o array com níveis int counter_levels=0; while(true) { int size=::ArraySize(m_levels_array); ::ArrayResize(m_levels_array,size+1); m_levels_array[size]=counter_levels; //--- if(counter_levels>max_value) break; //--- counter_levels+=(int)m_step.GetValue(); } }
O método CProgram::SetDataToTable2() é usado para preencher a segunda tabela com os dados. No início, a verificação é executada se o símbolo é realçado na primeira lista da tabela. Se não estiver, o programa sai do método enviando a mensagem para o log da guia experts Se uma linha na primeira tabela estiver realçada, defina o símbolo e obtenha os dados nele. Depois disso, os métodos descritos acima são chamados para receber os dados do indicador e calcular os níveis. Nós recebemos os dados do indicador com o mesmo período de tempo em que o EA é lançado.
Quando nós sabemos o número de níveis, nós podemos construir uma tabela com o tamanho necessário e preenchê-la com os valores. Primeiro, preencha a primeira coluna com os valores de intervalo. Depois disso, preencha a segunda coluna. Ao mover sequencialmente por todos os intervalos, há o aumento do contador nas células para os segmentos que se enquadram nessa faixa.
class CProgram : public CWndCreate { private: //--- Preenche a tabela 2 com dados void SetDataToTable2(void); }; //+------------------------------------------------------------------+ //| Preenche a tabela 2 com dados | //+------------------------------------------------------------------+ void CProgram::SetDataToTable2(void) { //--- Encerra se a linha não estiver destacada if(m_table1.SelectedItem()==WRONG_VALUE) { ::Print(__FUNCTION__," > Select a symbol in the table on the left!"); return; } //--- Início do progresso StartProgress(); //--- Oculta a mesa m_table2.Hide(); //--- Obtém o símbolo da primeira tabela string symbol=m_table1.GetValue(0,m_table1.SelectedItem()); m_symbol.Name(symbol); //--- Obtém os dados do indicador GetIndicatorData(symbol,_Period); //--- Obtém os níveis GetLevels(); //--- Re-constrói a tabela RebuildingTable2(); //--- Define os intervalos na primeira coluna for(uint r=0; r<(uint)m_table2.RowsTotal(); r++) m_table2.SetValue(0,r,(string)m_levels_array[r],0); //--- Obtém os valores para a segunda coluna int items_total=::ArraySize(m_levels_array); int segments_total=m_zz.SegmentsTotal(); for(int i=0; i<items_total-1; i++) { //--- Progresso m_progress_bar.LabelText("Get data ["+(string)m_levels_array[i]+"]..."); m_progress_bar.Update(i,m_table2.RowsTotal()); //--- for(int s=0; s<segments_total; s++) { int size=int(m_zz.SegmentSize(s)/m_symbol.Point()); if(size>m_levels_array[i] && size<m_levels_array[i+1]) { int value=(int)m_table2.GetValue(1,i)+1; m_table2.SetValue(1,i,(string)value,0); } } } //--- Mostrar a tabela m_table2.Update(true); //--- Fim do progresso EndProgress(); }
Como exemplo, vamos receber os segmentos para o EURUSD desde 2010 até o presente no gráfico M5. Defina os intervalos com o passo de 100 pontos de cinco dígitos. O resultado é mostrado na imagem abaixo.
O número total de segmentos é 302145. Como nós podemos ver, o número máximo de segmentos está dentro do intervalo de zero a 100. Mais adiante, o número de segmentos é reduzido de nível para nível. Dentro do período de tempo especificado, o tamanho máximo do segmento atingido foi de 2400 pontos de cinco dígitos.
Fig. 14. Resultado do cálculo do número de segmentos por tamanho
Contagem do número de segmentos por duração
Também seria bom saber a duração dos segmentos nos grupos formados. Para encontrar padrões, nós precisamos ter todas as estatísticas dos dados analisados. Vamos desenvolver outra versão do EA. Basta copiar o programa da seção anterior e adicionar outra tabela para a GUI. A tabela deve apresentar duas colunas: (1) número de barras e (2) número de segmentos com esse número de barras. Abaixo, você pode ver como a GUI aparece logo após o upload do aplicativo no gráfico.
Fig. 15. Programa para calcular o número de segmentos por duração
A sequência de ações para receber os dados em todas as tabelas é a seguinte:
- Clique em Request para receber a lista de símbolos.
- Selecione um símbolo que destaque uma linha na primeira tabela.
- Clique em Calculate para receber os dados para a segunda tabela.
- Para receber os dados para a terceira, selecione o intervalo necessário, destacando uma linha na segunda tabela.
A lista abaixo fornece o código do método CProgram::SetDataToTable3() para receber os dados e preencher a terceira tabela. A linha destacada aqui é usada para receber o intervalo, dentro do qual o número de segmentos deve ser calculado por sua duração. O número de linhas na tabela é definido pelo segmento mais longo (em barras) do conjunto de dados obtido. Ao preencher a segunda coluna da tabela, percorra todas as linhas e conte os segmentos que se encaixam o intervalo selecionado e o número de barras por tamanho.
class CProgram : public CWndCreate { private: //--- Preenche a tabela 3 com dados void SetDataToTable3(void); }; //+------------------------------------------------------------------+ //| Preenche a tabela 3 com dados | //+------------------------------------------------------------------+ void CProgram::SetDataToTable3(void) { //--- Encerra se a linha não estiver destacada if(m_table2.SelectedItem()==WRONG_VALUE) { ::Print(__FUNCTION__," > Select a range in the table on the left!"); return; } //--- Início do progresso StartProgress(); //--- Oculta a mesa m_table3.Hide(); //--- Obtém a linha destacada int selected_row_index=m_table2.SelectedItem(); //--- Intervalo int selected_range=(int)m_table2.GetValue(0,selected_row_index); //--- Re-constrói a tabela RebuildingTable3(); //--- Define os valores para a primeira coluna for(uint r=0; r<(uint)m_table3.RowsTotal(); r++) m_table3.SetValue(0,r,(string)(r+1),0); //--- Obtém os valores da segunda coluna int segments_total=m_zz.SegmentsTotal(); for(uint r=0; r<(uint)m_table3.RowsTotal(); r++) { //--- Progresso m_progress_bar.LabelText("Get data ["+(string)r+"]..."); m_progress_bar.Update(r,m_table3.RowsTotal()); //--- for(int s=0; s<segments_total; s++) { int size =int(m_zz.SegmentSize(s)/m_symbol.Point()); int bars =m_zz.SegmentBars(s); //--- if(size>selected_range && size<selected_range+(int)m_step.GetValue() && bars==r+1) { int value=(int)m_table3.GetValue(1,r)+1; m_table3.SetValue(1,r,(string)value,0); } } } //--- Exibe a tabela m_table3.Update(true); //--- Fim do progresso EndProgress(); }
Ao destacar as linhas da tabela e da lista, o evento personalizado ON_CLICK_LIST_ITEM é gerado. Neste caso, nós rastreamos a chegada do evento com o ID da segunda tabela.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Eventos de clique nas linhas if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Linha da tabela clicada if(lparam==m_table2.Id()) { //--- Obtém os dados para a terceira tabela SetDataToTable3(); return; } //--- return; } ... }
Ao receber uma nova lista de símbolos ou calcular os dados no novo símbolo realçado na primeira tabela, dados irrelevantes de cálculos anteriores devem ser apagados das tabelas para evitar confusão sobre quais dados são exibidos atualmente.
//+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Evento de clique nos botões if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- Botão de solicitação clicado if(lparam==m_request.Id()) { //--- Obtém os dados para a primeira tabela SetDataToTable1(); //--- Exclui os dados irrelevantes das tabelas m_table2.DeleteAllRows(true); m_table3.DeleteAllRows(true); return; } //--- Calcula o botão clicado if(lparam==m_calculate.Id()) { //--- Obtém os dados para a segunda tabela SetDataToTable2(); //--- Exclui os dados irrelevantes das tabelas m_table3.DeleteAllRows(true); } //--- return; } ... }
Depois de lançar o EA no gráfico, nós obtemos o resultado conforme mostrado abaixo. Neste caso, nós formamos a lista de pares de moedas com USD. Os dados sobre GBPUSD do início de 2018 foram recebidos posteriormente e a lista de intervalos (segunda tabela) foi formada com o passo de 100 e os segmentos calculados para cada um deles. Como exemplo, a linha com o intervalo de 200 e o número de segmentos de 1922 (de 200 a 300) é destacado na segunda tabela. A terceira tabela exibe a duração de todos os segmentos do intervalo realçado na segunda tabela. Por exemplo, nós podemos ver que apenas 11 segmentos com a duração de 10 barras do intervalo especificado estavam presentes no GBPUSD durante este período.
Fig. 16. Resultado do cálculo do número de segmentos por duração
Alguns detalhes de como trabalhar com a interface gráfica
Como um suplemento, eu gostaria de mostrar como lidar adequadamente com o evento de alterar um símbolo do gráfico e um período de tempo quando uma GUI é usada em um programa MQL. Como as GUIs podem conter vários controles, pode levar algum tempo para carregar e inicializar todo o conjunto. Às vezes, esse tempo pode ser salvo, o que é exatamente o caso de alterar um símbolo de gráfico e um período de tempo. Aqui, não há necessidade de remover e criar constantemente uma GUI repetidamente.
Isso pode ser conseguido da seguinte maneira:
Crie um campo para armazenar o último motivo da desinicialização do programa na classe principal do programa:
class CProgram : public CWndCreate { private: //--- Último motivo para a desinicialização int m_last_deinit_reason; }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CProgram::CProgram(void) : m_last_deinit_reason(WRONG_VALUE) { }
Durante a desinicialização, a GUI é removida em todos os casos, exceto aqueles em que a razão é REASON_CHARTCHANGE.
//+------------------------------------------------------------------+ //| Desinicialização | //+------------------------------------------------------------------+ void CProgram::OnDeinitEvent(const int reason) { //--- Lembre-se do último motivo de desinicialização m_last_deinit_reason=reason; //--- Remove a GUI se o motivo não estiver relacionado a alterar um símbolo e um ponto if(reason!=REASON_CHARTCHANGE) { CWndEvents::Destroy(); } }
Como a GUI é criada ao inicializar o programa chamando o método CProgram::CreateGUI(), agora é suficiente para verificar a última causa de desinicialização. Se a razão for que um símbolo ou período de tempo foi alterado, não há necessidade de criar uma GUI. Em vez disso, simplesmente saia do método notificando que tudo está bem.
//+------------------------------------------------------------------+ //| Cria a GUI | //+------------------------------------------------------------------+ bool CProgram::CreateGUI(void) { //--- Encerra se um gráfico ou período de tempo tiver sido alterado if(m_last_deinit_reason==REASON_CHARTCHANGE) return(true); ... return(true); }
Conclusão
A ideia de que o ZigZag não é adequado para gerar sinais de negociação é amplamente difundida em fóruns de negociação. Este é um grande equívoco. De fato, nenhum outro indicador fornece tanta informação para determinar a natureza do comportamento do preço. Agora você tem uma ferramenta que lhe permite obter facilmente todos os dados necessários do indicador ZigZag para uma análise mais detalhada.
Na próxima parte, eu vou mostrar que outros dados podem ser obtidos usando as ferramentas desenvolvidas nesses artigos.
Nome do arquivo | Comentário |
---|---|
MQL5\Indicators\Custom\ZigZag\FrequencyChangeZZ.mq5 | Indicador para calcular a frequência de formação de segmentos do indicador ZigZag de direção oposta |
MQL5\Indicators\Custom\ZigZag\SumSegmentsZZ.mq5 | Indicador para calcular a soma dos segmentos do conjunto obtido e seu valor médio |
MQL5\Indicators\Custom\ZigZag\PercentageSegmentsZZ.mq5 | Indicador da relação percentual das somas do segmento e da diferença entre elas |
MQL5\Indicators\Custom\ZigZag\MultiPercentageSegmentsZZ.mq5 | Indicador para definir a natureza da formação de vários segmentos a partir de um período de tempo maior, usando a diferença entre as taxas percentuais das somas dos segmentos com direção oposta |
MQL5\Experts\ZigZag\TestZZ_05.mq5 | EA para testar o indicador MultiPercentageSegmentsZZ |
MQL5\Experts\ZigZag\ZZ_Scanner_01.mq5 | EA para a coleta de estatísticas sobre o indicador PercentageSegmentsZZ |
MQL5\Experts\ZigZag\ZZ_Scanner_02.mq5 | EA para calcular os segmentos em diferentes faixas de preço |
MQL5\Experts\ZigZag\ZZ_Scanner_03.mq5 | EA para calcular os segmentos localizados em diferentes faixas de preço e com duração diferente |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/5544
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso