![Padrões de reversão: Testando o padrão 'topo/fundo duplo'](https://c.mql5.com/i/articles/overlay.png)
Padrões de reversão: Testando o padrão 'topo/fundo duplo'
Sumário
- Introdução
- 1. Aspectos teóricos da formação de padrões
- 2. Estratégia de negociação usando o padrão
- 3. Criemos o EA
- 4. Teste de estratégia
- Fim do artigo
Introdução
A análise realizada no artigo Quanto dura a tendência? mostra que 60% do tempo o preço permanece em tendência. Isso significa que abrir uma posição no início de uma tendência produz os melhores resultados. A busca por pontos de inversão de tendência gera inúmeros padrões de reversão. Um dos padrões mais famosos e usados é o de 'topo/fundo duplo'.
1. Aspectos teóricos da formação de padrões
O padrão 'topo/fundo duplo' pode ser encontrado com bastante frequência. Sua formação está intimamente ligada à teoria dos níveis de comércio. O padrão é formado no final de uma tendência quando o preço atinge um nível de suporte ou resistência (dependendo do movimento anterior). Após uma correção durante um teste repetido do nível, ele reverte novamente em vez de rompê-lo.
Neste ponto, os traders de contra-tendência entram em jogo negociando com base no recuo em relação ao nível e empurrando o preço para correção. Enquanto o movimento de correção ganha impulso, os traders que seguem a tendência começam a sair do mercado fixando o lucro ou fechando posições deficitárias que visam romper o nível. Isso fortalece ainda mais o movimento, levando ao surgimento de uma nova tendência.
Ao procurar esse padrão, não faz sentido procurar uma correspondência exata de topos/fundos. O desvio dos níveis superior/inferior é considerado normal. O mais importante é que os topos estejam dentro do mesmo nível de suporte/resistência. A confiabilidade do padrão depende da força do nível em que ocorre sua formação.
2. Estratégia de negociação usando o padrão
A popularidade do padrão deu origem a várias estratégias baseadas nele. Na Internet, existem pelo menos três pontos de entrada diferentes para negociar usando esse padrão.
2.1. Cenário 1
O primeiro ponto de entrada é baseado no rompimento da linha do pescoço. Um stop-loss é definido além da linha superior/inferior. Existem diferentes abordagens para identificar o rompimento da linha do pescoço. Os traders podem usar um fechamento de barra sob a linha do pescoço, bem como o rompimento da linha do pescoço a uma distância fixa. Ambas as abordagens têm seus prós e contras. No caso de um movimento brusco, o fechamento da vela pode ocorrer a uma distância suficiente da linha do pescoço, tornando o uso do padrão ineficiente.
A desvantagem dessa abordagem é um nível de stop loss relativamente alto, o que reduz a relação lucro/risco da estratégia utilizada.
2.2. Cenário 2
O segundo ponto de entrada é baseado na teoria dos níveis espelhados, quando a linha do pescoço sendo resistência se transforma em suporte e vice-versa. Aqui a entrada é feita quando o preço, depois de ter sido quebrado, retorna à linha do pescoço. Nesse caso, um stop-loss é definido além do extremo da última correção, reduzindo significativamente o nível de stop-loss. Infelizmente, o preço nem sempre testa a linha do pescoço após ter sido quebrado, reduzindo assim o número de entradas.
2.3. Cenário 3
O terceiro ponto de entrada é baseado na teoria da tendência. É definido pelo rompimento da linha de tendência construída a partir do ponto inicial do movimento até o extremo da linha do pescoço. O stop-loss, como no primeiro caso, é colocado atrás da linha de topos/fundos. A entrada antecipada fornece um nível de stop-loss menor que o primeiro ponto de entrada. Também dá mais sinais em comparação ao segundo cenário. Ao mesmo tempo, tal ponto de entrada dá mais sinais falsos, uma vez que é possível formar um canal entre as linhas extremas e o pescoço, ou pode haver o padrão de flâmula. Ambos os casos indicam a continuação da tendência.
Todas as três estratégias oferecem sair no nível igual à distância entre o extremo e a linha do pescoço.
Além disso, ao determinar o padrão no gráfico, você deve observar que o topo/fundo duplo deve se destacar claramente do movimento do preço. Ao descrever o padrão, frequentemente é adicionada uma restrição: deve haver pelo menos seis barras entre dois topos/fundos.
Além disso, como a formação de padrões é baseada na teoria dos níveis de preços, a negociação usando padrões não deve contradizê-lo. Portanto, com base na finalidade procurada, a linha do pescoço não deve ser inferior ao nível 50 de Fibo do movimento inicial. Além disso, a fim de filtrar os sinais falsos, podemos adicionar o nível mínimo da primeira correção (que forma a linha do pescoço) como um indicador da força do nível de preços.
3. Criemos o EA
3.1. Buscando extremos
A criação de um EA começa com a criação do bloco de busca do padrão. Vamos usar o indicador ZigZag fornecido por padrão com o MetaTrader 5 para procurar extremos. Movemos a parte de cálculo do indicador para a classe, conforme descrito no artigo [1]. O indicador contém dois buffers de indicador contendo valor de preço em pontos de extremos. Os buffers de indicador contêm valores vazios entre os extremos. Para não criar dois buffers de indicador contendo vários valores vazios, eles foram substituídos por uma matriz de estruturas contendo informações sobre o extremo. A estrutura para armazenar informações sobre o extremo é a seguinte.
struct s_Extremum { datetime TimeStartBar; double Price; s_Extremum(void) : TimeStartBar(0), Price(0) { } void Clear(void) { TimeStartBar=0; Price=0; } };
Se você usou o indicador ZigZag pelo menos uma vez, você sabe quantas coisas você precisa fazer ao buscar parâmetros ideais. Parâmetros muito pequenos dividem movimentos grandes em partes pequenas, enquanto parâmetros muito grandes fazem com que movimentos curtos sejam ignorados. O algoritmo para busca de padrões gráficos é muito exigente quanto à qualidade de encontrar extremos. Enquanto tentava encontrar um meio-termo, decidi usar o indicador com pequenos valores de parâmetros e criar uma superestrutura adicional combinando movimentos unidirecionais com curtas correções num único movimento.
Para resolver esse problema, foi criada a classe CTrends. O cabeçalho da classe é apresentado abaixo. Durante a inicialização, para a classe são passados uma referência ao objeto da classe de indicador e o tamanho do movimento mínimo em que um novo movimento será considerado uma continuação da tendência.
class CTrends : public CObject { private: CZigZag *C_ZigZag; // Referência para o objeto do indicador ZigZag s_Extremum Trends[]; // Matriz de extremos int i_total; // Número total de extremos armazenados double d_MinCorrection; // Tamanho mínimo do movimento para continuar a tendência public: CTrends(); ~CTrends(); //--- Método de inicialização de classe virtual bool Create(CZigZag *pointer, double min_correction); //--- Obtendo informações sobre o extremo virtual bool IsHigh(s_Extremum &pointer) const; virtual bool Extremum(s_Extremum &pointer, const int position=0); virtual int ExtremumByTime(datetime time); //--- Obtendo informações gerais virtual int Total(void) { Calculate(); return i_total; } virtual string Symbol(void) const { if(CheckPointer(C_ZigZag)==POINTER_INVALID) return "Not Initilized"; return C_ZigZag.Symbol(); } virtual ENUM_TIMEFRAMES Timeframe(void) const { if(CheckPointer(C_ZigZag)==POINTER_INVALID) return PERIOD_CURRENT; return C_ZigZag.Timeframe(); } protected: virtual bool Calculate(void); virtual bool AddTrendPoint(s_Extremum &pointer); };
Para obter informações sobre o extremo na classe são fornecidos os métodos:
- ExtremumByTime — obtenção do número do extremo no banco de dados segundo o tempo especificado,
- Extremum — retorna o extremo da posição especificada no banco de dados,
- IsHigh — retorna true se o extremo especificado é um topo, e false se é um fundo.
O bloco de informações gerais contém métodos que retornam o número total de extremos armazenados, o símbolo e o período gráfico usados.
A lógica principal da classe é implementada no método Calculate. Vamos dar uma olhada mais de perto.
No início do método, verificamos a referência para o objeto da classe de indicador e a presença de extremos encontrados pelo indicador.
bool CTrends::Calculate(void) { if(CheckPointer(C_ZigZag)==POINTER_INVALID) return false; //--- if(C_ZigZag.Total()==0) return true;
Em seguida, determinamos o número de extremos não processados. Caso todos os extremos sejam processados, saímos do método com o resultado true.
int start=(i_total<=0 ? C_ZigZag.Total() : C_ZigZag.ExtremumByTime(Trends[i_total-1].TimeStartBar)); switch(start) { case 0: return true; break; case -1: start=(i_total<=1 ? C_ZigZag.Total() : C_ZigZag.ExtremumByTime(Trends[i_total-2].TimeStartBar)); if(start<0 || ArrayResize(Trends,i_total-1)<=0) { ArrayFree(Trends); i_total=0; start=C_ZigZag.Total(); } else i_total=ArraySize(Trends); if(start==0) return true; break; }
Depois disso, solicitamos o número necessário de extremos a partir da classe de indicador.
s_Extremum base[]; if(!C_ZigZag.Extremums(base,0,start)) return false; int total=ArraySize(base); if(total<=0) return true;
Se, até o momento, não houver um único extremo no banco de dados, adicionamos o extremo mais antigo ao banco de dados chamando o método AddTrendPoint.
if(i_total==0) if(!AddTrendPoint(base[total-1])) return false;
Em seguida, realizamos um ciclo com iteração de todos os extremos carregados. Extremos antes do último salvo são ignorados.
for(int i=total-1;i>=0;i--) { int trends_pos=i_total-1; if(Trends[trends_pos].TimeStartBar>=base[i].TimeStartBar) continue;
Na próxima etapa, verificamos se os topos são unidirecionais. Se o novo extremo redesenhar o anterior, atualizamos as informações.
if(IsHigh(Trends[trends_pos])) { if(IsHigh(base[i])) { if(Trends[trends_pos].Price<base[i].Price) { Trends[trends_pos].Price=base[i].Price; Trends[trends_pos].TimeStartBar=base[i].TimeStartBar; } continue; }
Para topos multidirecionais, verificamos se o novo movimento é uma continuação da tendência anterior. Se for assim, atualizamos as informações sobre extremos. Se o resultado da verificação for negativo, adicionamos informações sobre o extremo, chamando o método AddTrendPoint;
else { if(trends_pos>1 && Trends[trends_pos-1].Price>base[i].Price && Trends[trends_pos-2].Price>Trends[trends_pos].Price) { double trend=fabs(Trends[trends_pos].Price-Trends[trends_pos-1].Price); double correction=fabs(Trends[trends_pos].Price-base[i].Price); if(fabs(1-correction/trend)>d_MinCorrection) { Trends[trends_pos-1].Price=base[i].Price; Trends[trends_pos-1].TimeStartBar=base[i].TimeStartBar; i_total--; ArrayResize(Trends,i_total); continue; } } AddTrendPoint(base[i]); } }
O código completo das classes pode ser encontrado no anexo.
3.2. Pesquisa de padrões
Depois de determinar o preço extremo, construiremos um bloco para encontrar pontos para posições de abertura. Dividimos esse trabalho em 2 sub-etapas:
- Busca de padrões para possíveis posições de abertura.
- Diretamente pontos de abertura de posição.
Esta funcionalidade será atribuída à classe CPttern. O cabeçalho da classe é apresentado abaixo.
class CPattern : public CObject { private: s_Extremum s_StartTrend; //Ponto de início da tendência s_Extremum s_StartCorrection; //Ponto de início da correção s_Extremum s_EndCorrection; //Ponto de conclusão da correção s_Extremum s_EndTrend; //Ponto de conclusão da tendência double d_MinCorrection; //Correção mínima double d_MaxCorrection; //Correção máxima //--- bool b_found; //Sinalizador "Padrão encontrado" //--- CTrends *C_Trends; public: CPattern(); ~CPattern(); //--- Inicialização da classe virtual bool Create(CTrends *trends, double min_correction, double max_correction); //--- Métodos de busca de padrão e do ponto de entrada virtual bool Search(datetime start_time); virtual bool CheckSignal(int &signal, double &sl, double &tp1, double &tp2); //--- method of comparing the objects virtual int Compare(const CPattern *node,const int mode=0) const; //--- Métodos para obter informações sobre os extremos do padrão s_Extremum StartTrend(void) const { return s_StartTrend; } s_Extremum StartCorrection(void) const { return s_StartCorrection; } s_Extremum EndCorrection(void) const { return s_EndCorrection; } s_Extremum EndTrend(void) const { return s_EndTrend; } virtual datetime EndTrendTime(void) { return s_EndTrend.TimeStartBar; } };
Vamos definir o padrão usando quatro extremos próximos, cujas informações armazenaremos nas quatro estruturas s_StartTrend, s_StartCorrection, s_EndCorrection e s_EndTrend. Para identificar o padrão, também precisamos de níveis de correção mínimo e máximo, que serão armazenados nas variáveis d_MinCorrection e d_MaxCorrection. Vamos obter os extremos numa instância da classe CTrends criada.
Ao inicializar a classe, passaremos para ela um ponteiro para um objeto da classe CTrends e os níveis de limite da correção. Dentro do método, verificamos se o ponteiro passado é válido, salvamos as informações recebidas e limpamos as estruturas dos extremos.
bool CPattern::Create(CTrends *trends,double min_correction,double max_correction) { if(CheckPointer(trends)==POINTER_INVALID) return false; //--- C_Trends=trends; b_found=false; s_StartTrend.Clear(); s_StartCorrection.Clear(); s_EndCorrection.Clear(); s_EndTrend.Clear(); d_MinCorrection=min_correction; d_MaxCorrection=max_correction; //--- return true; }
Vamos buscar padrões potenciais usando o método Search(). Nos parâmetros esse método recebe a data de início da pesquisa de padrões e retorna um valor booleano informando sobre os resultados da pesquisa. Consideremos o algoritmo do método em mais detalhes.
No início do método, verificamos o ponteiro para o objeto da classe CTrends e a presença de extremos salvos. Em caso de resultado negativo, saímos do método com o resultado false.
bool CPattern::Search(datetime start_time) { if(CheckPointer(C_Trends)==POINTER_INVALID || C_Trends.Total()<4) return false;
Em seguida, determinamos o extremo correspondente à data obtida nos parâmetros de entrada. Se o extremo não for encontrado, saímos do método com o resultado false.
int start=C_Trends.ExtremumByTime(start_time); if(start<0) return false;
Em seguida, realizamos um ciclo para percorrer todos os extremos, a partir da data especificada e até a última encontrada. Primeiro nós obtemos 4 extremos consecutivos. Se pelo menos um dos extremos não for recebido, avançamos para o próximo extremo.
b_found=false; for(int i=start;i>=0;i--) { if((i+3)>=C_Trends.Total()) continue; if(!C_Trends.Extremum(s_StartTrend,i+3) || !C_Trends.Extremum(s_StartCorrection,i+2) || !C_Trends.Extremum(s_EndCorrection,i+1) || !C_Trends.Extremum(s_EndTrend,i)) continue;
Na próxima etapa, verificamos os extremos correspondem com o padrão desejado. Se os extremos não satisfizerem o padrão desejado, avançamos para os extremos seguintes. Ao encontrar um padrão, definimos o sinalizador como true e saímos do método com o mesmo resultado.
double trend=s_StartCorrection.Price-s_StartTrend.Price; double correction=s_StartCorrection.Price-s_EndCorrection.Price; double re_trial=s_EndTrend.Price-s_EndCorrection.Price; double koef=correction/trend; if(koef<d_MinCorrection || koef>d_MaxCorrection || (1-fmin(correction,re_trial)/fmax(correction,re_trial))>=d_MaxCorrection) continue; b_found= true; //--- break; } //--- return b_found; }
O próximo passo, depois de determinar o padrão, procuraremos um ponto de entrada. Vamos procurar o ponto de entrada de acordo com o segundo cenário. Mas, para minimizar o risco de o preço não retornar à linha do pescoço, procuraremos uma confirmação do sinal num período gráfico menor.
Para implementar essa funcionalidade, criamos o método CheckSignal(). Esse método, além do próprio sinal, retornará os níveis de negociação de stop-loss e take profit, portanto, nos parâmetros do método, utilizamos ponteiros para variáveis.
No início do método, verificamos o sinalizador de um padrão encontrado anteriormente e, se o padrão não for encontrado, saímos do método com o resultado false.
bool CPattern::CheckSignal(int &signal, double &sl, double &tp1, double &tp2) { if(!b_found) return false;
Logo, determinamos o tempo de fechamento da vela de formação do padrão e carregamos os dados do período gráfico de interesse desde o início da formação do padrão até o momento atual.
string symbol=C_Trends.Symbol(); if(symbol=="Not Initilized") return false; datetime start_time=s_EndTrend.TimeStartBar+PeriodSeconds(C_Trends.Timeframe()); int shift=iBarShift(symbol,e_ConfirmationTF,start_time); if(shift<0) return false; MqlRates rates[]; int total=CopyRates(symbol,e_ConfirmationTF,0,shift+1,rates); if(total<=0) return false;
Depois disso, realizaremos um ciclo no qual a cada barra verificaremos o rompimento da linha do pescoço, a correção e o fechamento da vela atrás da linha do pescoço na direção do movimento esperado.
Repare que aqui adicionei mais algumas restrições:
- Um padrão é considerado inválido se o preço ultrapassar o nível de topos/fundos.
- Um padrão é considerado inválido se o preço atingir o nível esperado de take-profit.
- O sinal para abrir uma negociação é ignorado se mais de 2 velas forem formadas desde o momento do sinal até a abertura de uma posição.
signal=0; sl=tp1=tp2=-1; bool up_trend=C_Trends.IsHigh(s_EndTrend); double extremum=(up_trend ? fmax(s_StartCorrection.Price,s_EndTrend.Price) : fmin(s_StartCorrection.Price,s_EndTrend.Price)); double exit_level=2*s_EndCorrection.Price - extremum; bool break_neck=false; for(int i=0;i<total;i++) { if(up_trend) { if(rates[i].low<=exit_level || rates[i].high>extremum) return false; if(!break_neck) { if(rates[i].close>s_EndCorrection.Price) continue; break_neck=true; continue; } if(rates[i].high>s_EndCorrection.Price) { if(sl==-1) sl=rates[i].high; else sl=fmax(sl,rates[i].high); } if(rates[i].close<s_EndCorrection.Price || sl==-1) continue; if((total-i)>2) return false;
Depois de detectar um sinal para abrir uma posição, indicamos o tipo de sinal ("-1" - Venda, "1" - Compra) e os níveis de negociação. Definimos o stop-loss na profundidade máxima de correção em relação à linha do pescoço após ter sido quebrada. Para o take-profit, definimos 2 níveis:
1. Em um nível igual a 90% da distância entre o extremo e o pescoço na direção da abertura de posição.
2. No nível igual a 90% do movimento de tendência anterior.
Ao mesmo tempo, adicionamos a restrição de que o nível do primeiro take-profit não pode exceder o nível do segundo take-profit.
signal=-1; double top=fmax(s_StartCorrection.Price,s_EndTrend.Price); tp1=s_EndCorrection.Price-(top-s_EndCorrection.Price)*0.9; tp2=top-(top-s_StartTrend.Price)*0.9; tp1=fmax(tp1,tp2); break; }
O código completo das classes pode ser encontrado no anexo.
3.3. Montando o EA
Após preparar tudo, reuniremos todos os blocos num único EA. Declaramos as variáveis externas que dividimos em três blocos:
- Parâmetros do indicador zig-zag;
- Parâmetros para busca de padrões e pontos de entrada;
- Parâmetros para negociação.
sinput string s1 = "---- ZigZag Settings ----"; //--- input int i_Depth = 12; // Depth input int i_Deviation = 100; // Deviation input int i_Backstep = 3; // Backstep input int i_MaxHistory = 1000; // Max history, bars input ENUM_TIMEFRAMES e_TimeFrame = PERIOD_M30; // Work Timeframe sinput string s2 = "---- Pattern Settings ----"; //--- input double d_MinCorrection= 0.118; // Minimal Correction input double d_MaxCorrection= 0.5; // Maximal Correction input ENUM_TIMEFRAMES e_ConfirmationTF= PERIOD_M5; // Timeframe for confirmation sinput string s3 = "---- Trade Settings ----"; //--- input double d_Lot = 0.1; // Trade Lot input ulong l_Slippage = 10; // Slippage input uint i_SL = 350; // Stop Loss Backstep, points
No bloco de variáveis globais, declararemos uma matriz para armazenar ponteiros para objetos padrão, uma instância da classe de operações de negociação, uma instância de uma classe para busca de padrões que armazenará um ponteiro para a instância que processa a classe e uma variável para armazenar a hora inicial da busca do próximo padrão.
CArrayObj *ar_Objects;
CTrade *Trade;
CPattern *Pattern;
datetime start_search;
Para definir simultaneamente dois take-profit para a posição, usaremos o método proposto no artigo [2]
Na função OnInit(), inicializaremos todos os objetos necessários. Além disso, como não declaramos globalmente instâncias das classes CZigZag e CTrends, simplesmente as inicializamos e adicionamos ponteiros esses objetos em nossa matriz. No caso de um erro de inicialização em qualquer uma das etapas, saímos da função com o resultado INIT_FAILED.
int OnInit() { //--- Inicialização da matriz de objetos ar_Objects=new CArrayObj(); if(CheckPointer(ar_Objects)==POINTER_INVALID) return INIT_FAILED; //--- Inicialização da classe do indicador Zig-Zag CZigZag *zig_zag=new CZigZag(); if(CheckPointer(zig_zag)==POINTER_INVALID) return INIT_FAILED; if(!ar_Objects.Add(zig_zag)) { delete zig_zag; return INIT_FAILED; } zig_zag.Create(_Symbol,i_Depth,i_Deviation,i_Backstep,e_TimeFrame); zig_zag.MaxHistory(i_MaxHistory); //--- Inicialização da classe de busca de tendências CTrends *trends=new CTrends(); if(CheckPointer(trends)==POINTER_INVALID) return INIT_FAILED; if(!ar_Objects.Add(trends)) { delete trends; return INIT_FAILED; } if(!trends.Create(zig_zag,d_MinCorrection)) return INIT_FAILED; //--- Inicialização da classe de operações de negociação Trade=new CTrade(); if(CheckPointer(Trade)==POINTER_INVALID) return INIT_FAILED; Trade.SetAsyncMode(false); Trade.SetDeviationInPoints(l_Slippage); Trade.SetTypeFillingBySymbol(_Symbol); //--- Inicialização de variáveis auxiliares start_search=0; CLimitTakeProfit::OnlyOneSymbol(true); //--- return(INIT_SUCCEEDED); }
Na função OnDeinit(), vamos limpar as instâncias dos objetos usados.
void OnDeinit(const int reason) { //--- if(CheckPointer(ar_Objects)!=POINTER_INVALID) { for(int i=ar_Objects.Total()-1;i>=0;i--) delete ar_Objects.At(i); delete ar_Objects; } if(CheckPointer(Trade)!=POINTER_INVALID) delete Trade; if(CheckPointer(Pattern)!=POINTER_INVALID) delete Pattern; }
Como sempre, a funcionalidade principal é realizada na função OnTick. A funcionalidade dessa função pode ser dividida em dois blocos:
1. Verificação de sinais para abrir posições nos padrões encontrados anteriormente. É iniciado em cada abertura de uma nova vela num timeframe pequeno para procurar por confirmação de sinal.
2. Busca de novos padrões. É iniciado em cada abertura de uma nova vela no timeframe de trabalho (especificado para o indicador).
No início da função, verificaremos o início da nova barra no timeframe de confirmação do ponto de entrada. Se a barra não estiver formada, saímos da função antes do próximo tick. Repare que esta abordagem só funcionará corretamente se o timeframe para confirmar o ponto de entrada não for maior do que o timeframe de trabalho. Caso contrário, em vez de sair da função, será preciso ir para o bloco de busca de padrões.
void OnTick() { //--- static datetime Last_CfTF=0; datetime series=(datetime)SeriesInfoInteger(_Symbol,e_ConfirmationTF,SERIES_LASTBAR_DATE); if(Last_CfTF>=series) return; Last_CfTF=series;
No caso de uma nova barra surgir, realizaremos um ciclo para verificar se em todos os padrões previamente salvos há um sinal para abrir uma posição. Observe aqui que não verificaremos que nos dois primeiros objetos da matriz existem sinais, pois nessas células armazenamos ponteiros para instâncias das classes de busca de extremos. Nos casos em que o ponteiro armazenado é inválido ou a função de teste de sinal retorna um valor false, o ponteiro será removido da matriz. A verificação dos sinais padrão será realizada diretamente na função CheckPattern(), cujo algoritmo será discutido abaixo.
int total=ar_Objects.Total(); for(int i=2;i<total;i++) { if(CheckPointer(ar_Objects.At(i))==POINTER_INVALID) if(ar_Objects.Delete(i)) { i--; total--; continue; } //--- if(!CheckPattern(ar_Objects.At(i))) { if(ar_Objects.Delete(i)) { i--; total--; continue; } } }
Após verificar os padrões encontrados anteriormente, avançamos para o segundo bloco — a busca por novos padrões. Para fazer isso, verificamos se há uma nova barra no timeframe de trabalho. Se uma nova barra não for formada, sairemos da função antecipando um novo tick.
static datetime Last_WT=0; series=(datetime)SeriesInfoInteger(_Symbol,e_TimeFrame,SERIES_LASTBAR_DATE); if(Last_WT>=series) return;
Quando uma nova barra aparecer, determinaremos a data inicial da busca de padrões (levando em conta a profundidade do histórico analisado nos parâmetros). Em seguida, verificaremos se o ponteiro para um objeto da classe CPattern é relevante e, no caso de um ponteiro inválido, criaremos uma nova instância da classe.
start_search=iTime(_Symbol,e_TimeFrame,fmin(i_MaxHistory,Bars(_Symbol,e_TimeFrame))); if(CheckPointer(Pattern)==POINTER_INVALID) { Pattern=new CPattern(); if(CheckPointer(Pattern)==POINTER_INVALID) return; if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection)) { delete Pattern; return; } } Last_WT=series;
Depois disso, no ciclo, chamamos o método de busca de potenciais padrões. No caso de uma pesquisa bem-sucedida, deslocaremos a data de início da pesquisa de um novo padrão e verificaremos se o padrão encontrado existe na matriz de padrões anteriormente encontrados. Se o padrão já estiver na matriz, avançamos para uma nova pesquisa.
while(!IsStopped() && Pattern.Search(start_search)) { start_search=fmax(start_search,Pattern.EndTrendTime()+PeriodSeconds(e_TimeFrame)); bool found=false; for(int i=2;i<ar_Objects.Total();i++) if(Pattern.Compare(ar_Objects.At(i),0)==0) { found=true; break; } if(found) continue;
Se um novo padrão for encontrado, verificamos se o sinal pode abrir uma posição, chamando a função CheckPattern(). Depois disso, se necessário, salvamos o padrão em nossa matriz e inicializamos uma nova instância da classe para a próxima pesquisa. O ciclo continua até que, durante a próxima pesquisa, o método Search() retorne o valor false.
if(!CheckPattern(Pattern)) continue; if(!ar_Objects.Add(Pattern)) continue; Pattern=new CPattern(); if(CheckPointer(Pattern)==POINTER_INVALID) break; if(!Pattern.Create(ar_Objects.At(1),d_MinCorrection,d_MaxCorrection)) { delete Pattern; break; } } //--- return; }
Para complementar, proponho considerar as funções do algoritmo CheckPattern(). Nos parâmetros, esse método recebe um ponteiro para uma instância da classe CPatern e retorna o valor lógico do resultado das operações. Lembre-se que ao obter o resultado false a partir da função em questão, o padrão analisado é removido da matriz de objetos armazenados.
No início da função, chamaremos o método de busca de um sinal para abrir uma posição da classe CPattern. Se a verificação for mal-sucedida, saímos da função com o resultado false.
bool CheckPattern(CPattern *pattern) { int signal=0; double sl=-1, tp1=-1, tp2=-1; if(!pattern.CheckSignal(signal,sl,tp1,tp2)) return false;
Se o sinal para abrir a posição for encontrado com sucesso, definimos níveis de negociação e enviamos uma ordem para abrir uma posição de acordo com o sinal recebido.
double price=0; double to_close=100; //--- switch(signal) { case 1: price=SymbolInfoDouble(_Symbol,SYMBOL_ASK); CLimitTakeProfit::Clear(); if((tp1-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point) if(CLimitTakeProfit::AddTakeProfit((uint)((tp1-price)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100))) to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100); if(to_close>0 && (tp2-price)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point) if(!CLimitTakeProfit::AddTakeProfit((uint)((tp2-price)/_Point),to_close)) return false; if(Trade.Buy(d_Lot,_Symbol,price,sl-i_SL*_Point,0,NULL)) return false; break; case -1: price=SymbolInfoDouble(_Symbol,SYMBOL_BID); CLimitTakeProfit::Clear(); if((price-tp1)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point) if(CLimitTakeProfit::AddTakeProfit((uint)((price-tp1)/_Point),(fabs(tp1-tp2)>=_Point ? 50 : 100))) to_close-=(fabs(tp1-tp2)>=_Point ? 50 : 100); if(to_close>0 && (price-tp2)>SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)*_Point) if(!CLimitTakeProfit::AddTakeProfit((uint)((price-tp2)/_Point),to_close)) return false; if(Trade.Sell(d_Lot,_Symbol,price,sl+i_SL*_Point,0,NULL)) return false; break; } //--- return true; }
Repare que se uma posição for aberta com sucesso, saímos da função com o resultado false. Isso não se deve a um erro, mas, sim, à necessidade de remover o padrão trabalhado da matriz. Este passo permite evitar a reabertura de uma posição com o mesmo padrão.
O código completo de todos os métodos e funções pode ser encontrado no anexo.
4. Teste de estratégia
Após a criação do nosso EA, é hora de verificar seu trabalho usando dados históricos. Vamos realizar testes num período de 9 meses de 2018 no par EURUSD. Vamos buscar padrões no timeframe M30, e vamos procurar os pontos de abertura de posição no timeframe M5.
Os resultados dos testes mostraram a capacidade do EA de gerar lucros. Durante o período de testes, o EA concluiu 90 trades, dos quais 70 foram lucrativos. O EA apresentou um fator de lucro de 2,02 e um fator de recuperação de 4,77, indicando que o EA se pode usar em contas reais. Os resultados completos dos testes estão listados abaixo.
Fim do artigo
Neste artigo, criamos um Expert Advisor trabalhando no padrão de reversão de tendência "Topo/fundo duplo". O teste do EA usando dados históricos mostrou resultados aceitáveis e a capacidade do EA de gerar lucros. Esse trabalho confirma a possibilidade de usar o padrão "Topo/fundo duplo" para buscar pontos de abertura de posição como um bom sinal de reversão da tendência.
Links
- Como transferir a parte de cálculo de qualquer indicador para o código do EA
- Implementando Take Profit na forma de ordens limitadas sem alterar o código original do EA
Programas utilizados no artigo:
# | Nome | Tipo | Descrição |
---|---|---|---|
1 | ZigZag.mqh | Biblioteca de classe | Classe do indicador Zig Zag |
2 | Trends.mqh | Biblioteca de classe | Classe de busca de tendências |
3 | Pattern.mqh | Biblioteca de classe | Classe de trabalho com padrões |
4 | LimitTakeProfit.mqh | Biblioteca de classe | Classe para substituir take-profit por ordens limitadas |
5 | Header.mqh | Biblioteca | Arquivo de cabeçalho do EA |
6 | DoubleTop.mq5 | Expert Advisor | EA da estratégia "Topo/fundo duplo". |
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/5319
![WebRequest multi-threaded assíncrono em MQL5](https://c.mql5.com/2/34/Multi_WebRequest_MQL5.png)
![Reversão: reduzindo o rebaixamento máximo e testando outros mercados](https://c.mql5.com/2/34/Graal.png)
![Usando OpenCL para testar padrões de candles](https://c.mql5.com/2/34/OpenCL_for_candle_patterns.png)
![Gap - estratégia rentável ou 50/50?](https://c.mql5.com/2/34/GapDown.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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
Boa tarde.
Eu opero o Mini Indice e gostei muito do artigo.
Porém, no momento em que tento testá-lo sobre o indice no MT5, esse gera resultado nulo, sem conseguir executar nenhum trader.
Imagina que esteja colocando os parâmetros errados para a execução.
Gostaria de ver qual resultado teria ao rodar no Wing19.
Vc pode me ajudar?
O mercado que ele esta testando é forex, para usar em bmf você pode usar o código como base e altera-lo.
Caso não souber editar o código, use a área de freelance.
https://www.mql5.com/pt/job
Boa noite .
Alguém sabe dizer se o código já foi corrigido?
Além disso, existe uma versão dele para operar Bovespa?
Grato