
Receitas MQL5 - sinais de negociação de pivô
Introdução
Este artigo continua uma série de artigos que descreve indicadores e configurações que geram sinais de negociação. Desta vez consideraremos os pivôs, isto é, pontos de reversão (pontos). Mais uma vez estará envolvida a Biblioteca padrão. Primeiro trabalharemos com o indicador de níveis de reversão e, em seguida, criaremos com base nele uma estratégia base que nós modificaremos.
É desejável que o leitor conheça a classe base para criar geradores de sinais de negociação CExpertSignal.
1. Indicador de pivôs ou níveis de reversão
Para a estratégia será utilizado um indicador que plota níveis de potencial reversão. Ao fazer isto, a plotagem ocorre só através de construção gráfica, sem o uso de objetos gráficos. A principal vantagem dessa abordagem consiste na possibilidade de acessar o indicador durante o modo de otimização. As desvantagens, por sua vez, abrangem o fato das construções gráficas não poderem ficar fora dos limites dos buffers de indicador, o que acarreta que mais tarde nas barras não haverá linhas.
Os níveis podem ser contados de várias maneiras diferentes. É possível aprofundar sobre isto no artigo Estratégia de trading com base na análise de pontos de pivô.
Por enquanto, consideraremos a abordagem clássica, onde os níveis são definidos de acordo com estas formulas:
RES é o nível i de resistência, enquanto SUP é o nível i de suporte. No total haverá: 1 nível base de reversão (PP), 6 níveis de resistência (RES) e 6 níveis de suporte (SUP).
Assim, o indicador aparece como um conjunto de níveis horizontais construídos segundo diferentes preços. A primeira execução do indicador no gráfico plotará os níveis apenas para o dia atual (Fig.1).
Fig. 1 Indicador de pivôs: plotagem para o dia atual
Examinemos bloco por bloco o código do indicador, comecemos com a parte prevista.
Assim, quando se inicia um novo dia, é preciso contar novamente todos os níveis de reversão.
//--- se houver um novo dia if(gNewDay.isNewBar(today)) { PrintFormat("Novo dia: %s",TimeToString(today)); //--- normalização dos preços double d_high=NormalizeDouble(daily_rates[0].high,_Digits); double d_low=NormalizeDouble(daily_rates[0].low,_Digits); double d_close=NormalizeDouble(daily_rates[0].close,_Digits); //--- memorizamos os preços gYesterdayHigh=d_high; gYesterdayLow=d_low; gYesterdayClose=d_close; //--- 1) pivô: PP = (HIGH + LOW + CLOSE) / 3 gPivotVal=NormalizeDouble((gYesterdayHigh+gYesterdayLow+gYesterdayClose)/3.,_Digits); //--- 4) RES1.0 = 2*PP - LOW gResVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayLow,_Digits); //--- 5) SUP1.0 = 2*PP – HIGH gSupVal_1_0=NormalizeDouble(2.*gPivotVal-gYesterdayHigh,_Digits); //--- 8) RES2.0 = PP + (HIGH -LOW) gResVal_2_0=NormalizeDouble(gPivotVal+(gYesterdayHigh-gYesterdayLow),_Digits); //--- 9) SUP2.0 = PP - (HIGH – LOW) gSupVal_2_0=NormalizeDouble(gPivotVal-(gYesterdayHigh-gYesterdayLow),_Digits); //--- 12) RES3.0 = 2*PP + (HIGH – 2*LOW) gResVal_3_0=NormalizeDouble(2.*gPivotVal+(gYesterdayHigh-2.*gYesterdayLow),_Digits); //--- 13) SUP3.0 = 2*PP - (2*HIGH – LOW) gSupVal_3_0=NormalizeDouble(2.*gPivotVal-(2.*gYesterdayHigh-gYesterdayLow),_Digits); //--- 2) RES0.5 = (PP + RES1.0) / 2 gResVal_0_5=NormalizeDouble((gPivotVal+gResVal_1_0)/2.,_Digits); //--- 3) SUP0.5 = (PP + SUP1.0) / 2 gSupVal_0_5=NormalizeDouble((gPivotVal+gSupVal_1_0)/2.,_Digits); //--- 6) RES1.5 = (RES1.0 + RES2.0) / 2 gResVal_1_5=NormalizeDouble((gResVal_1_0+gResVal_2_0)/2.,_Digits); //--- 7) SUP1.5 = (SUP1.0 + SUP2.0) / 2 gSupVal_1_5=NormalizeDouble((gSupVal_1_0+gSupVal_2_0)/2.,_Digits); //--- 10) RES2.5 = (RES2.0 + RES3.0) / 2 gResVal_2_5=NormalizeDouble((gResVal_2_0+gResVal_3_0)/2.,_Digits); //--- 11) SUP2.5 = (SUP2.0 + SUP3.0) / 2 gSupVal_2_5=NormalizeDouble((gSupVal_2_0+gSupVal_3_0)/2.,_Digits); //--- barra do início do dia atual gDayStart=today; //--- encontrar a barra inicial no timeframe ativo //--- como uma série temporal for(int bar=0;bar<rates_total;bar++) { //--- tempo da barra selecionada datetime curr_bar_time=time[bar]; user_date.DateTime(curr_bar_time); //--- dia da barra selecionada datetime curr_bar_time_of_day=user_date.DateOfDay(); //--- se a barra atual tiver acontecido de dia anteriormente if(curr_bar_time_of_day<gDayStart) { //--- fixar a barra inicial gBarStart=bar-1; break; } } //--- zerar o contador local prev_calc=0; }
Eu destaquei a vermelho as cadeias de caracteres onde os níveis são recalculados. Em seguida, para o timeframe atual, é preciso encontrar o número da barra que será o início para plotar os níveis. A variável gBarStart está encarregada pelo seu valor. Durante a pesquisa a estrutura personalizada SUserDateTime é ativada para trabalhar com as datas e o tempo, isto é, com o descendente da estrutura CDateTime.
Agora algumas palavras sobre o bloco em que são preenchidos os valores de buffer para as barras do timeframe atual.
//--- se houver uma barra no timeframe ativo if(gNewMinute.isNewBar(time[0])) { //--- até qual barra contar int bar_limit=gBarStart; //--- se não for a primeira execução if(prev_calc>0) bar_limit=rates_total-prev_calc; //--- contagem de buffers for(int bar=0;bar<=bar_limit;bar++) { //--- 1) pivô gBuffers[0].data[bar]=gPivotVal; //--- 2) RES0.5 if(gToPlotBuffer[1]) gBuffers[1].data[bar]=gResVal_0_5; //--- 3) SUP0.5 if(gToPlotBuffer[2]) gBuffers[2].data[bar]=gSupVal_0_5; //--- 4) RES1.0 if(gToPlotBuffer[3]) gBuffers[3].data[bar]=gResVal_1_0; //--- 5) SUP1.0 if(gToPlotBuffer[4]) gBuffers[4].data[bar]=gSupVal_1_0; //--- 6) RES1.5 if(gToPlotBuffer[5]) gBuffers[5].data[bar]=gResVal_1_5; //--- 7) SUP1.5 if(gToPlotBuffer[6]) gBuffers[6].data[bar]=gSupVal_1_5; //--- 8) RES2.0 if(gToPlotBuffer[7]) gBuffers[7].data[bar]=gResVal_2_0; //--- 9) SUP2.0 if(gToPlotBuffer[8]) gBuffers[8].data[bar]=gSupVal_2_0; //--- 10) RES2.5 if(gToPlotBuffer[9]) gBuffers[9].data[bar]=gResVal_2_5; //--- 11) SUP2.5 if(gToPlotBuffer[10]) gBuffers[10].data[bar]=gSupVal_2_5; //--- 12) RES3.0 if(gToPlotBuffer[11]) gBuffers[11].data[bar]=gResVal_3_0; //--- 13) SUP3.0 if(gToPlotBuffer[12]) gBuffers[12].data[bar]=gSupVal_3_0; } }
A contagem de buffers começa após atualizar a nova barra no gráfico onde foi executado o indicador. A amarelo selecionada a definição do número da barra sobre o qual contaremos os buffers. Para esses fins, é utilizado o contador local de barras contadas. Nós precisávamos dele porque ao iniciar o novo dia não é zerado o valor da constante prev_calculated, enquanto a reinicialização é necessária.
O código completo do indicador de pivôs está no arquivo Pivots.mq5.
2. Estratégia básica
Com base no indicador descrito tentaremos criar uma estratégia básica simples. Vamos supor que o sinal de abertura depende da localização do preço de abertura do dia em relação ao pivô central. Quando o preço atinge o nível de pivô, o sinal é confirmado.
Assim, no gráfico EURUSD, M15 (Fig.2) podemos ver que o dia 14 de janeiro de 2015 é aberto abaixo do pivô central e, logo depois, o preço intradia atinge seu nível de baixo para cima. Assim, surge um sinal de venda. Se não se ativar nem o Stop-Loss nem o Take-Profit, ao começar o novo dia fecharemos.
Fig. 2 Estratégia básica: sinal de venda
Ligaremos os níveis de stop e lucro aos níveis de reversão do indicador de pivô. O nível intermediário de resistência Res0.5 localizado em $1.18153 será o stop para venda. O nível base de suporte Sup1.0 em $1.17301 será o stop para lucro. Mais tarde retornaremos para o dia 14 de janeiro. Entretanto, falaremos sobre o código que constituirá a base algorítmica de uma estratégia básica.
2.1 Classe de sinal CSignalPivots
Criaremos a classe de sinal que gerará os sinais a partir de vários modelos formados com base na dinâmica de preço e no indicador de níveis de reversão.
//+------------------------------------------------------------------+ //| Class CSignalPivots | //| Purpose: Classe de sinais de negociação com base em pivôs. | //| Descendente da classe CExpertSignal. | //+------------------------------------------------------------------+ class CSignalPivots : public CExpertSignal { //--- === Data members === --- protected: CiCustom m_pivots; // objeto-indicador "Pivots" //--- parâmetros personalizados bool m_to_plot_minor; // plotagem de níveis secundários double m_pnt_near; // tolerância //--- de cálculo double m_pivot_val; // valor de pivô double m_daily_open_pr; // preço de abertura do dia atual CisNewBar m_day_new_bar; // nova barra do timeframe diário //--- modelos de mercado //--- 1) Modelo 0 "primeiro atingimento do nível PP" (de cima - buy, de baixo - sell) int m_pattern_0; // peso bool m_pattern_0_done; // sinal de modelo modificado //--- === Methods === --- public: //--- construtor/destrutor void CSignalPivots(void); void ~CSignalPivots(void){}; //--- métodos de instalação de parâmetros personalizados void ToPlotMinor(const bool _to_plot) {m_to_plot_minor=_to_plot;} void PointsNear(const uint _near_pips); //--- métodos de configuração de "pesos" de modelos de mercado void Pattern_0(int _val) {m_pattern_0=_val;m_pattern_0_done=false;} //--- método de verificação de configurações virtual bool ValidationSettings(void); //--- método de criação de indicador e de TimeSeries virtual bool InitIndicators(CIndicators *indicators); //--- método para verificar se o modelo de mercado foi formado virtual int LongCondition(void); virtual int ShortCondition(void); virtual double Direction(void); //--- método de definição de níveis de entrada no mercado virtual bool OpenLongParams(double &price,double &sl,double &tp,datetime &expiration); virtual bool OpenShortParams(double &price,double &sl,double &tp,datetime &expiration); //--- protected: //--- método de inicialização do indicador bool InitCustomIndicator(CIndicators *indicators); //--- obtenção do valor do nível de pivô double Pivot(void) {return(m_pivots.GetData(0,0));} //--- obtenção de valor do nível básico de resistência double MajorResistance(uint _ind); //--- obtenção do valor do nível secundário de resistência double MinorResistance(uint _ind); //--- obtenção do valor do nível básico de suporte double MajorSupport(uint _ind); //--- obtenção do valor do nível secundário de suporte double MinorSupport(uint _ind); }; //+------------------------------------------------------------------+
No artigo "Guia Prático MQL5 - sinais de negociação de canais móveis", já usei uma abordagem em que se o preço ficar nos arredores da linha, seu toque feito pelo preço será fixado. O membro de dados m_pnt_near definirá a tolerância para o nível de reversão.
Talvez seja o modelo de sinal - atendido pela classe - que deve realizar o papel mais importante na classe. Na classe base, haverá um modelo. Além do peso (m_pattern_0), ele terá ainda um sinal de processamento durante o dia de negociação (m_pattern_0_done).
A classe de sinal base CExpertSignal é rica em métodos virtuais. E esta riqueza permite implementar uma configuração sutil da classe derivada.
Geralmente, para calcular os níveis de negociação, eu tenho redefinido 2 métodos, nomeadamente, OpenLongParams() e OpenShortParams().
Examinemos o código do primeiro método, isto é, a determinação dos valores, para os níveis de negociação, durante a compra.
//+------------------------------------------------------------------+ //| Determinação de níveis de negociação para compra | //+------------------------------------------------------------------+ bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { bool params_set=false; sl=tp=WRONG_VALUE; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) { //--- preço de abertura - segundo o mercado double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); //--- preço Stop-Loss - nível Sup0.5 sl=this.MinorSupport(0); if(sl==DBL_MAX) return false; //--- se definido preço Stop-Loss sl=m_symbol.NormalizePrice(sl); //--- preço Take-Profit - nível Res1.0 tp=this.MajorResistance(0); if(tp==DBL_MAX) return false; //--- se definido o preço Take-Profit tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); //--- se definido o preço params_set=true; //--- modelo modificado m_pattern_0_done=true; } //--- return params_set; } //+------------------------------------------------------------------+
O preço Stop-Loss é calculado como o valor do primeiro nível de suporte secundário usando o método MinorSupport(). Definimos o lucro de acordo com o preço do primeiro nível base de resistência com ajuda do método MajorResistance(). Para venda, estes métodos são colocados, respetivamente, em MinorResistance() e MajorSupport().
Para que os métodos de definição de níveis de negociação na classe derivada "joguem", é necessário tornar o sinal personalizado o principal. O método de definição de níveis de negociação da classe principal tem a seguinte aparência:
//+------------------------------------------------------------------+ //| Detecting the levels for buying | //+------------------------------------------------------------------+ bool CExpertSignal::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { CExpertSignal *general=(m_general!=-1) ? m_filters.At(m_general) : NULL; //--- if(general==NULL) { //--- if a base price is not specified explicitly, take the current market price double base_price=(m_base_price==0.0) ? m_symbol.Ask() : m_base_price; price =m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); sl =(m_stop_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price-m_stop_level*PriceLevelUnit()); tp =(m_take_level==0.0) ? 0.0 : m_symbol.NormalizePrice(price+m_take_level*PriceLevelUnit()); expiration+=m_expiration*PeriodSeconds(m_period); return(true); } //--- return(general.OpenLongParams(price,sl,tp,expiration)); } //+------------------------------------------------------------------+
Se não for especificado o índice do sinal principal, os níveis terão os valores por padrão. Para evitar esta situação, no código do EA, ao inicializar o sinal, é possível escrever o seguinte:
//--- filtro CSignalPivots CSignalPivots *filter0=new CSignalPivots; if(filter0==NULL) { //--- erro PrintFormat(__FUNCTION__+": error creating filter0"); return INIT_FAILED; } signal.AddFilter(filter0); signal.General(0);
O método de verificação de condições de compra é apresentado como segue:
//+------------------------------------------------------------------+ //| Verificação de condição de compra | //+------------------------------------------------------------------+ int CSignalPivots::LongCondition(void) { int result=0; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) //--- se o dia abir acima do pivô if(m_daily_open_pr>m_pivot_val) { //--- preço mínimo na barra atual double last_low=m_low.GetData(1); //--- se preço obtido if((last_low>WRONG_VALUE) && (last_low<DBL_MAX)) //--- se o atingimento for de cima (tendo em conta a tolerância) if(last_low<=(m_pivot_val+m_pnt_near)) { result=m_pattern_0; //--- no Diário Print("\n---== O preço atingiu de cima o nível de pivô ==---"); PrintFormat("Preço: %0."+IntegerToString(m_symbol.Digits())+"f",last_low); PrintFormat("Pivô: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("Tolerância: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } //--- return result; } //+------------------------------------------------------------------+
É fácil perceber que houve atingimento de cima é verificado tendo em conta a tolerância last_low<=(m_pivot_val+m_pnt_near).
Método de definição da direção "ponderada" Direction(), entre outras coisas, verifica o processamento do modelo base.
//+------------------------------------------------------------------+ //| Definição da direção "ponderada" | //+------------------------------------------------------------------+ double CSignalPivots::Direction(void) { double result=0.; //--- obter dados históricos do diário MqlRates daily_rates[]; if(CopyRates(_Symbol,PERIOD_D1,0,1,daily_rates)<0) return 0.; //--- se o Modelo 0 é modificado if(m_pattern_0_done) { //--- verificar a existência de um novo dia if(m_day_new_bar.isNewBar(daily_rates[0].time)) { //--- zerar o sinalizador de processamento de modelo m_pattern_0_done=false; return 0.; } } //--- se Modelo 0 não é modificado else { //--- preço de abertura do dia if(m_daily_open_pr!=daily_rates[0].open) m_daily_open_pr=daily_rates[0].open; //--- pivô double curr_pivot_val=this.Pivot(); if(curr_pivot_val<DBL_MAX) if(m_pivot_val!=curr_pivot_val) m_pivot_val=curr_pivot_val; } //--- resultado result=m_weight*(this.LongCondition()-this.ShortCondition()); //--- return result; } //+------------------------------------------------------------------+
No que diz respeito aos sinais para fechamento substituímos os métodos da classe principal CloseLongParams() e CloseShortParams(). Exemplo de código para o bloco de compra:
//+------------------------------------------------------------------+ //| Determinação do nível de negociação para compra | //+------------------------------------------------------------------+ bool CSignalPivots::CloseLongParams(double &price) { price=0.; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) { price=m_symbol.Bid(); //--- no Diário Print("\n---== Sinal para fechamento da compra ==---"); PrintFormat("Preço de mercado: %0."+IntegerToString(m_symbol.Digits())+"f",price); return true; } //--- return the result return false; } //+------------------------------------------------------------------+
O limiar do sinal para fechamento deve ser zerado no código do próprio Expert Advisor.
Assim, na classe principal não haverá verificação de acordo com direção.
//+------------------------------------------------------------------+ //| Generating a signal for closing of a long position | //+------------------------------------------------------------------+ bool CExpertSignal::CheckCloseLong(double &price) { bool result =false; //--- the "prohibition" signal if(m_direction==EMPTY_VALUE) return(false); //--- check of exceeding the threshold value if(-m_direction>=m_threshold_close) { //--- there's a signal result=true; //--- try to get the level of closing if(!CloseLongParams(price)) result=false; } //--- zeroize the base price m_base_price=0.0; //--- return the result return(result); } //+------------------------------------------------------------------+
Surge a pergunta: como é que neste caso será verificado se o sinal será fechado? Ele será verificado, em primeiro lugar, graças à presença da posição (no método Processing() ), e, em segundo lugar, graças à caraterística m_pattern_0_done (nos métodos substituídos CloseLongParams() e CloseShortParams() ). Uma vez que o Expert Advisor detecta a presença da posição - ao mesmo tempo que o Modelo 0 não está modificado -, ele imediatamente tentará fechar a posição. Isto acontece no início do dia de negociação.
Nós examinamos as noções básicas da classe de sinal personalizada CSignalPivots, agora falaremos sobe a classe da estratégia.
2.2 Classe de estratégia de negociação CPivotsExpert
A classe derivada de estratégia é semelhante com o análogo para os canais móveis. A diferença é que, em primeiro lugar, não é utilizado o modo baseado em ticks, mas sim um com base em minutos. Isto permite testar rápida e profundamente o histórico para a estratégia. Em segundo lugar, há uma verificação para o fechamento da posição. Acima foi determinada a razão pela qual o EA pode fechar a posição.
O principal método-manipulador tem a seguinte aparência:
//+------------------------------------------------------------------+ //| Módulo principal | //+------------------------------------------------------------------+ bool CPivotsExpert::Processing(void) { //--- nova barra em minutos if(!m_minute_new_bar.isNewBar()) return false; //--- cálculo de direção m_signal.SetDirection(); //--- se não há posição if(!this.SelectPosition()) { //--- módulo de abertura de posição if(this.CheckOpen()) return true; } //--- se há posição else { //--- módulo de fechamento de posição if(this.CheckClose()) return true; } //--- se não há operações de negociação return false; } //+------------------------------------------------------------------+
Isso é tudo. Agora podemos executar a estratégia básica. Seu código está disponível no arquivo BasePivotsTrader.mq5.
Fig. 3 Estratégia básica: venda
Voltamos para o exemplo do dia 14 de janeiro de 2015. Neste caso, o modelo foi idealmente elaborado. Abrimos para baixo, sobre pivô, e fechamos no nível de suporte base Sup1.0.
Foi executado no Testador de Estratégias de 07.01.2013 a 07.01.2017, no timeframe de 15 minutos, EURUSD, com os seguintes parâmetros:
- Limiar do sinal para abertura, [0...100] = 10;
- Peso, [0...1.0] = 1,0;
- Volume fixado = 0,1;
- Tolerância, pp = 15.
Afinal, a estratégia opera com resultados sustentáveis. No entanto, é um resultado negativo (Fig.4).
Fig. 4 EURUSD: resultados da primeira estratégia básica para 2013-2016.
A jugar pelo gráfico de desempenho, fizemos tudo errado. Tínhamos de comprar imediatamente após chegar o sinal para venda, e vender imediatamente depois de surgir o sinal de compra. É não é? Tentemos verificar. Para fazer isso, criamos uma segunda estratégia básica.
Alteraremos a parte de sinais. Então, por exemplo, a condição de compra será como segue:
//+------------------------------------------------------------------+ //| Verificação de condição de venda | //+------------------------------------------------------------------+ int CSignalPivots::LongCondition(void) { int result=0; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) //--- se o dia aberto abaixo do pivô if(m_daily_open_pr<m_pivot_val) { //--- preço máximo na barra atual double last_high=m_high.GetData(1); //--- se preço obtido if((last_high>WRONG_VALUE) && (last_high<DBL_MAX)) //--- se o atingimento for de baixo (tendo em conta a tolerância) if(last_high>=(m_pivot_val-m_pnt_near)) { result=m_pattern_0; //--- no Diário Print("\n---== O preço atingiu de baixo o nível de pivô ==---"); PrintFormat("Preço: %0."+IntegerToString(m_symbol.Digits())+"f",last_high); PrintFormat("Pivô: %0."+IntegerToString(m_symbol.Digits())+"f",m_pivot_val); PrintFormat("Tolerância: %0."+IntegerToString(m_symbol.Digits())+"f",m_pnt_near); } } //--- return result; } //+------------------------------------------------------------------+
Novamente executamos a estratégia no Testador e obtemos o seguinte resultado:
Fig. 5 EURUSD: resultado da segunda estratégia básica para 2013-2016.
É evidente que não houve um "espelhamento" da primeira versão. Isso é atribuído, obviamente, ao tamanho do Stop-Loss e Take-Profit. Além disso, imediatamente após iniciar o novo dia, foram fechadas as posições que, durante o dia de negociação, não ativaram nem o Profit nem o Stop.
Tentemos novamente modificar a estratégia básica, nomeadamente, sua segunda versão. Faremos com que, para compra, o nível Stop-Loss seja colocado mais adiante, isto é, antes do nível de suporte de Sup1.0, enquanto, o tamanho do Take-Profit seja limitado pelo nível de resistência de Res0.5. Para venda, colocaremos o Stop-Loss no nível de Res1.0, enquanto o Take-Profit, em Sup0.5.
Dessa maneira, por exemplo, os níveis de negociação para a compra serão determinados como segue:
//+------------------------------------------------------------------+ //| Definição dos níveis de negociação para compra | //+------------------------------------------------------------------+ bool CSignalPivots::OpenLongParams(double &price,double &sl,double &tp,datetime &expiration) { bool params_set=false; sl=tp=WRONG_VALUE; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) { //--- preço de abertura — segundo o mercado double base_price=m_symbol.Ask(); price=m_symbol.NormalizePrice(base_price-m_price_level*PriceLevelUnit()); //--- preço Stop-Loss - nível Sup1.0 sl=this.MajorSupport(0); if(sl==DBL_MAX) return false; //--- se definido preço Stop-Loss sl=m_symbol.NormalizePrice(sl); //--- preço Take-Profit - nível Res0.5 tp=this.MinorResistance(0); if(tp==DBL_MAX) return false; //--- se definido o preço Take-Profit tp=m_symbol.NormalizePrice(tp); expiration+=m_expiration*PeriodSeconds(m_period); //--- se definido o preço params_set=true; //--- modelo modificado m_pattern_0_done=true; } //--- return params_set; } //+------------------------------------------------------------------+
No Testador, obtemos este resultado para a terceira versão:
Fig. 6 EURUSD: resultados da terceira estratégia básica para 2013-2016
A imagem ficou mais o menos parecida com a imagem "espelhada" para a primeira versão. À primeira vista parece que foi encontrada a estratégia de negociação sem perdas. Mas há armadilhas, que serão discutidas na próxima seção.
3. Sobre a questão da eficiência
Se olharmos atentamente para a Fig.6, podemos reparar facilmente em que a curva de balanço cresceu de forma desigual. Houve áreas onde o balanço acumulou lucros de maneira constante. Houve áreas de abaixamento, bem como áreas onde a curva de balanço mudava para a direita.
Em geral podemos dizer que a estratégia carece de eficiência. Será que é possível aumentá-la? Tentemos.
3.1 Indicador de tendência
Na minha opinião, as regras de negociação descritas acima funcionam melhor quando, no mercado, uma tendência, isto é, um movimento dirigido. Assim, a estratégia mostrou o melhor resultado em EURUSD, entre 2014 e o início de 2015, quando o par estava a diminuir progressivamente.
Em certo sentido, então, é necessário algum tipo de filtro que permita evitar um movimento lateral. Quanto ao tema sobre a definição de uma tendência estável há muitos materiais, inclusive na seção "Artigos" no mql5.com. Pessoalmente, gostei muito do artigo "Diversas maneiras de encontrar uma tendência no MQL5". Nele, o autor oferece uma maneira confortável e, mais importante, universal para procurar a tendência.
Eu criei um indicador semelhante MaTrendCatcher. Nele, são usadas duas MA, quer dizer, a rápida e a lenta. Se a diferença entre os valores é positivo, julgaremos que a tendência é de alta. Em seguida, no indicador, as colunas do histograma serão iguais a 1. Se essa diferença for negativa, no mercado prevalecerá a tendência de baixa. As colunas serão igual a menos 1 (Fig.7.).
Fig. 7 Indicador de tendência MaTrendCatcher
Além disso, se, em relação à barra anterior, a diferença entre as MA aumentar (se tendência se fortalecer), a coluna ficará verde, se cair, vermelha.
Existe mais um recurso adicionado ao indicador: onde a diferença entre a MA seja pequena, não serão exibidas colunas. É o parâmetro personalizado do indicador "Corte, pp" (Fig.8) que define qual a diferença necessária para ocultar sua visualização.
Fig. 8 Indicador de tendência MaTrendCatcher, corte de pequenas diferenças
Assim, para efeitos de filtragem, usaremos o indicador MaTrendCatcher.
Para ativar o indicador, é necessário fazer algumas alterações no código dos arquivos do projeto. Observem que a última versão do Expert Advisor será armazenada na pasta Model.
Para efeitos da estratégia, era necessário obter a dimensão calculada da direção "ponderada". Então foi necessário criar uma classe descendente personalizada a partir da classe de sinal base.
Em seguida, na classe de sinal atualizada de níveis de reversão, apareceu um novo modelo — Modelo 1 "tendência-fase de correção-contra-tendência".
Na verdade, ela complementa o Modelo 0. Portanto, ela pode ser chamada de sub-modelo. Um pouco mais tarde, no código, citaremos este ponto.
O método de verificação de condição de compra é apresentado como segue:
//+------------------------------------------------------------------+ //| Verificação de condição de compra | //+------------------------------------------------------------------+ int CSignalPivots::LongCondition(void) { int result=0; //--- se Modelo 0 é levado em conta if(IS_PATTERN_USAGE(0)) //--- se Modelo 0 não é modificado if(!m_pattern_0_done) { m_is_signal=false; //--- se o dia aberto abaixo do pivô if(m_daily_open_pr<m_pivot_val) { //--- preço máximo na barra anterior double last_high=m_high.GetData(1); //--- se preço obtido if(last_high>WRONG_VALUE && last_high<DBL_MAX) //--- se o atingimento for de baixo (tendo em conta a tolerância) if(last_high>=(m_pivot_val-m_pnt_near)) { result=m_pattern_0; m_is_signal=true; //--- no Diário this.Print(last_high,ORDER_TYPE_BUY); } } //--- se o Modelo1 é levado em conta if(IS_PATTERN_USAGE(1)) { //--- se na barra anterior houve tendência de alta if(m_trend_val>0. && m_trend_val!=EMPTY_VALUE) { //--- se houver aceleração if(m_trend_color==0. && m_trend_color!=EMPTY_VALUE) result+=(m_pattern_1+m_speedup_allowance); //--- se não houver aceleração else result+=(m_pattern_1-m_speedup_allowance); } } } //--- return result; }
Destacado em verde o bloco onde entra em jogo o sub-modelo.
A finalidade do cálculo é a seguinte: se entrada no mercado for realizada excluindo o sub-modelo, o resultado do sinal será igual ao peso do Modelo 0. Se o sub-modelo for levado em conta, serão possíveis as seguintes opções:
- entrada segundo a tendência com aceleração (prêmio pela tendência e prêmio pela aceleração);
- entrada segundo a tendência sem aceleração (prêmio pela tendência e multa pela aceleração);
- entrada contra a tendência com aceleração (penalidade pela contra-tendência e a penalidade pela aceleração);
- entrada contra a tendência sem aceleração (penalidade pela contra-tendência e prêmio pela aceleração).
Tal abordagem permite não reagir a um sinal fraco. Se o peso do sinal superar o valor limite, isso afetará o tamanho do volume de negociação. Na classe do EA, quanto a pivôs, exite o método CPivotsExpert::LotCoefficient():
//+------------------------------------------------------------------+ //| Coeficiente do lote | //+------------------------------------------------------------------+ double CPivotsExpert::LotCoefficient(void) { double lot_coeff=1.; //--- sinal comum CExpertUserSignal *ptr_signal=this.Signal(); if(CheckPointer(ptr_signal)==POINTER_DYNAMIC) { double dir_val=ptr_signal.GetDirection(); lot_coeff=NormalizeDouble(MathAbs(dir_val/100.),2); } //--- return lot_coeff; } //+------------------------------------------------------------------+
Assim, se o sinal reunir 120 pontos, o volume inicial será corrigido para 1,2, e se 70 — para 0,7.
Para aplicar o coeficiente, é necessário substituir os métodos OpenLong() e OpenShort(). Em particular, o método para compra é apresentado como segue:
//+------------------------------------------------------------------+ //| Long position open or limit/stop order set | //+------------------------------------------------------------------+ bool CPivotsExpert::OpenLong(double price,double sl,double tp) { if(price==EMPTY_VALUE) return(false); //--- get lot for open double lot_coeff=this.LotCoefficient(); double lot=LotOpenLong(price,sl); lot=this.NormalLot(lot_coeff*lot); //--- check lot for open lot=LotCheck(lot,price,ORDER_TYPE_BUY); if(lot==0.0) return(false); //--- return(m_trade.Buy(lot,price,sl,tp)); } //+------------------------------------------------------------------+
A ideia da formação dinâmica do tamanho do lote é bastante simples: quanto mais forte o sinal, maior o risco.
3.2 Tamanho do intervalo
Também é fácil reparar em que quando os níveis de reversão (pivôs) estão próximos uns dos outros há uma baixa volatilidade no mercado. Para se abster de operar nesses dias, temos o parâmetro "Limite de largura, pp". O Modelo 0 (e com ele o sub-modelo) será considerado aperfeiçoado, se o limiar do limite não for atingido. Isto é verificado no corpo do método Direction(). Este é um trecho de código:
//--- se defiido o limite if(m_wid_limit>0.) { //--- limite superior de cálculo double norm_upper_limit=m_symbol.NormalizePrice(m_wid_limit+m_pivot_val); //--- limite superior real double res1_val=this.MajorResistance(0); if(res1_val>WRONG_VALUE && res1_val<DBL_MAX) { //--- se o limite não for atingido if(res1_val<norm_upper_limit) { //--- Modelo 0 aperfeiçoado m_pattern_0_done=true; //--- no Diário Print("\n---== Não foi atingido o limite superior ==---"); PrintFormat("De cálculo: %0."+IntegerToString(m_symbol.Digits())+"f",norm_upper_limit); PrintFormat("Real: %0."+IntegerToString(m_symbol.Digits())+"f",res1_val); //--- return 0.; } } //--- limite inferior de cálculo double norm_lower_limit=m_symbol.NormalizePrice(m_pivot_val-m_wid_limit); //--- limite inferior real double sup1_val=this.MajorSupport(0); if(sup1_val>WRONG_VALUE && sup1_val<DBL_MAX) { //--- se o limite não for atingido if(norm_lower_limit<sup1_val) { //--- Modelo 0 aperfeiçoado m_pattern_0_done=true; //--- no Diário Print("\n---== Não foi atingido o limite inferior ==---"); PrintFormat("De cálculo: %0."+IntegerToString(m_symbol.Digits())+"f",norm_lower_limit); PrintFormat("Real: %0."+IntegerToString(m_symbol.Digits())+"f",sup1_val); //--- return 0.; } } }
Se a verificação de largura de intervalo não for aprovada, no diário, por exemplo, aparecerá esta entrada:
2015.08.19 00:01:00 ---== Limite superior não atingido ==--- 2015.08.19 00:01:00 De cálculo: 1.10745 2015.08.19 00:01:00 Real: 1.10719Ou seja, neste caso, para ficar completo, ao sinal lhe faltou 26 pp.
Executamos estratégia no Testador no modo de otimização. Eu usei os seguintes parâmetros de otimização:
- "Limite de largura, pp";
- "Tolerância, pp";
- "MA rápida";
- "MA lenta";
- "Corte, pp".
Em termos de rentabilidade, a execução melhor sucedida tem a seguinte aparência:
Fig. 9EURUSD: resultados de estrategia com uso de filtros para 2013-2016.
De um modo geral, como esperado, foram selecionados alguns sinais. A curva do balanço tornou-se mais suave.
Não vale a pena falar do que não foi possível. A estratégia, como pode ser visto no gráfico, desde 2015, gera áreas em que a curva de balanço flutua numa faixa estreita sem um crescimento explícito do lucro. Os resultados de otimização encontram-se no arquivo EURUSD_model.xml.
Vejamos os resultados noutros instrumentos.
Assim, no par USDJPY, a melhor otimização é exibida na Fig.10.
Fig. 10USDJPY: resultados de estrategia com uso de filtros de 2013 a 2016.
Agora olhemos para o ouro "spot". O melhor resultado é mostrado na Fig.11.
Fig. 11XAUUSD: resultados de estratégia com uso de filtros de 2013 a 2016.
Neste período, o metal precioso foi negociado numa faixa estreita, de modo que a estratégia não teve um resultado positivo.
Quanto à libra esterlina, o melhor dos resultados é apresentado na Fig.12.
Fig. 12GBPUSD: resultados de estrategia com uso de filtros para 2013-2016.
A libra foi negociada bem de acordo com a tendência. Mas a correção no início de 2015 estragou um pouco o resultado final.
Em geral, a estratégia trabalha melhor quando existe uma tendência de mercado.
Conclusão
Em conclusão, gostaria de salientar que a criação de uma estratégia de negociação consiste em várias etapas. Na fase inicial, é formulada a ideia de negociação. Normalmente, esta é uma hipótese que deve ser formalizada como código e, em seguida, verificada no Testador. Muitas vezes é preciso corrigir ou refinar algo durante o processo de teste. Nisso consiste o trabalho do desenvolvedor. Neste caso, para correção da estratégia de pivô, foi usada esta abordagem. Ao fazer isto, na minha opinião, o uso da POO facilita muito a tarefa do desenvolvedor.
Observe que todo o teste foi realizado no modo de otimização na MQL5 Cloud Network. Usando tecnologias baseadas em nuvem foi possível avaliar de maneira rápida e barata a eficiência das estratégias.
Localização de arquivos
O mais conveniente é colocar os arquivos das estratégias na pasta Pivots. É preciso colocar os arquivos dos indicadores (Pivots.ex5 e MaTrendCatcher.ex5) na pasta \MQL5\Indicators\.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2853





- 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