Guia Prático MQL5 - Sinais de negociação de canais móveis
Introdução
O artigo anterior «Guia Prático MQL5 - Programando os canais móveis» descreveu um método para traçar canais equidistantes, frequentemente chamado de canais móveis. Com o intuito de solucionar a tarefa, foram usados a ferramenta «Equidistant Channel» e o recurso OOP.
Este artigo dará ênfase nos sinais, que poderão ser identificados por meio destes canais. Vamos tentar criar uma estratégia de negociação fundamentada nesses sinais.
Existem diversos artigos publicados no MQL5, nos quais descrevem a geração de sinais de negociação usando chamadas para os módulos prontos da Biblioteca Padrão. Espera-se que este artigo complemente os materiais e amplie aos usuários a variação das classes padrão.
Para aqueles que começaram a se familiarizar com esta estratégia, são bem vindos a estudar este material, do simples ao mais complexo. Primeiro, cria-se uma estratégia básica, depois a tornamos mais complexa e incluimos quando possível.
1. O indicador do canal equidistante
No artigo anterior sobre canais móveis, o próprio expert advisor plota os canais, criando os objetos gráficos. Por um lado, esta abordagem facilita certas tarefas para o programador, já pelo outro, ela torna determinadas coisas impossíveis. Por exemplo, se o EA trabalha em modo de otimização, então ele pode não detectar quaisquer objetos gráficos na tabela, bem como não teria nenhum gráfico. Descrição das limitações durante o teste:
Objetos Gráficos em Testes
Durante os testes/otimizações, os objetos gráficos não são plotados. Então, ao se referir às propriedades de um objeto criado durante os testes ou a otimização, um Expert Advisor receberá valores igual a zero.
Esta limitação não se aplica aos testes no modo visual. |
Por isso, será utilizada uma abordagem diferente, criando um indicador que reflete tanto os fratais, quanto o canal atual.
Este indicador é chamado de EquidistantChannels. Ele consiste essencialmente de dois blocos. O primeiro cálcula os buffers do fractal, e o segundo — os buffers dos canais.
Aqui está o código do handler de evento Calculate.
---------//+---------------------------------------------------------+ //| Função de interação do indicador personalizado | ---------//+---------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- se não houver código de barras na chamada anterior if(prev_calculated==0) { //--- zerar os buffers ArrayInitialize(gUpFractalsBuffer,0.); ArrayInitialize(gDnFractalsBuffer,0.); ArrayInitialize(gUpperBuffer,0.); ArrayInitialize(gLowerBuffer,0.); ArrayInitialize(gNewChannelBuffer,0.); } //--- Cálculo para fractais [Inicio] int startBar,lastBar; //--- if(rates_total<gMinRequiredBars) { Print("Not enough data for calculation"); return 0; } //--- if(prev_calculated<gMinRequiredBars) startBar=gLeftSide; else startBar=rates_total-gMinRequiredBars; //--- lastBar=rates_total-gRightSide; for(int bar_idx=startBar; bar_idx<lastBar && !IsStopped(); bar_idx++) { //--- if(isUpFractal(bar_idx,gMaxSide,high)) gUpFractalsBuffer[bar_idx]=high[bar_idx]; else gUpFractalsBuffer[bar_idx]=0.0; //--- if(isDnFractal(bar_idx,gMaxSide,low)) gDnFractalsBuffer[bar_idx]=low[bar_idx]; else gDnFractalsBuffer[bar_idx]=0.0; } //--- Cálculo para os fractais [final] //--- Cálculo para as extremidades do canal [Iniciar] if(prev_calculated>0) { //--- se o conjunto não foi inicializado if(!gFracSet.IsInit()) if(!gFracSet.Init( InpPrevFracNum, InpBarsBeside, InpBarsBetween, InpRelevantPoint, InpLineWidth, InpToLog )) { Print("Fractal set initialization error!"); return 0; } //--- cálculo gFracSet.Calculate(gUpFractalsBuffer,gDnFractalsBuffer,time, gUpperBuffer,gLowerBuffer, gNewChannelBuffer ); } //--- Cálculo para as extremidades do canal [final] //--- valor de retorno do prev_calculated para a próxima chamada return rates_total; }
O bloco com o cálculo dos valores do buffer do fractal é destacado em amarelo e o bloco com o cálculo dos buffers dos canais — em verde. É fácil perceber que o segundo bloco será ativado somente na segunda escala do handler. Esta implementação do segundo bloco permite obter os buffers completos dos fractais.
Agora, algumas palavras sobre o conjunto de pontos fractais — o objeto CFractalSet. Devido à alteração do método de exibição do canal, foi necessário modificar a classe CFractalSet. O método fundamental foi o CFractalSet::Calculate, que calcula o buffer do canal do indicador. O código é fornecido no arquivo CFractalPoint.mqh.
Agora existe uma base — um provedor de sinais a partir do canal equidistante. A operação do indicador é apresentada no vídeo.
2. Estratégia básica
Começaremos com algo simples, o qual poderá ser melhorado e revisto com a ajuda do OOP (Objeto Orientado a Programação). Temos de ter alguma estratégia básica.
Esta estratégia irá considerar algumas regras bem simples de negociação. Entradas no mercado serão feitas pelas extremidades do canal. Quando o preço atingir a extremidade inferior, uma posição de compra será aberta, ao tocar a extremidade "superior" - será aberta a posição de venda. A Fig 1 mostra que o preço atingiu a extremidade inferior, então o robô compra um certo volume. Os níveis de negociação (stop loss e take profit) têm um tamanho fixo, também foram posicionados automaticamente. Se existe posição aberta, os sinais de entrada repetidos serão ignorados.
Fig.1Sinal de Entrada
Também vale a pena mencionar que a Biblioteca padrão tem crescido bastante. A mesma já contém muitas classes prontas que podem ser utilizadas. Primeiramente, tentaremos «conectar» a classe do sinal CExpertSignal. De acordo com a documentação, esta é a classe base para a criação de geradores de sinais de negociação.
Essa classe foi nomeada com muita precisão. Esta não é a CTradeSignal ou CSignal, mas sim, é a classe de sinais que são concebidas para uso no código do EA — CExpertSignal.
Não prolongaremos este conteúdo. O artigo «Assistente do MQL5: Como Criar um Módulo de Sinais de Negociação» contém uma descrição detalhada dos métodos da classe de sinal.
2.1 O sinal da classe CSignalEquidChannel
Desta maneira, as classes derivadas do sinal são as seguintes:
---------//+---------------------------------------------------------+ //| Classe CSignalEquidChannel | //| Objetivo: Classe de sinais de negociação baseada no canal | //| equidistante. | //| Derivada da classe CExpertSignal. | ---------//+---------------------------------------------------------+ class CSignalEquidChannel : public CExpertSignal { protected: CiCustom m_equi_chs; // objeto do indicador "EquidistantChannels" //--- parâmetros ajustáveis int m_prev_frac_num; // fractais anteriores bool m_to_plot_fracs; // fractais exibidos? int m_bars_beside; // barras à esquerda/direita do fractal int m_bars_between; // barras intermediárias ENUM_RELEVANT_EXTREMUM m_relevant_pnt; // ponto relevante int m_line_width; // largura da linha bool m_to_log; // manter o log? double m_pnt_in; // tolerância interna, pips double m_pnt_out; // tolerância externa, pips bool m_on_start; // inicio da flag de sinalização //--- calculado double m_base_low_price; // base da mínima do preço double m_base_high_price; // base da máxima do preço double m_upper_zone[2]; // zona superior: [0]-tolerância interna, [1]-externa double m_lower_zone[2]; // zona inferior datetime m_last_ch_time; // tempo de ocorrência do último canal //--- "pesos" dos modelos do mercado (0-100) int m_pattern_0; // "Atingindo o limite inferior do canal - comprar, o superior - vender" //--- === Métodos === --- público: //--- Construtor/destrutor void CSignalEquidChannel(void); void ~CSignalEquidChannel(void){}; //--- métodos para definir parâmetros ajustáveis void PrevFracNum(int _prev_frac_num) {m_prev_frac_num=_prev_frac_num;} void ToPlotFracs(bool _to_plot) {m_to_plot_fracs=_to_plot;} void BarsBeside(int _bars_beside) {m_bars_beside=_bars_beside;} void BarsBetween(int _bars_between) {m_bars_between=_bars_between;} void RelevantPoint(ENUM_RELEVANT_EXTREMUM _pnt) {m_relevant_pnt=_pnt;} void LineWidth(int _line_wid) {m_line_width=_line_wid;} void ToLog(bool _to_log) {m_to_log=_to_log;} void PointsOutside(double _out_pnt) {m_pnt_out=_out_pnt;} void PointsInside(double _in_pnt) {m_pnt_in=_in_pnt;} void SignalOnStart(bool _on_start) {m_on_start=_on_start;} //--- métodos de ajuste dos "pesos" dos modelos de mercado void Pattern_0(int _val) {m_pattern_0=_val;} //--- método de verificação das configurações virtual bool ValidationSettings(void); //--- método de criação do indicador e das timeseries. virtual bool InitIndicators(CIndicators *indicators); //--- métodos de verificação, caso os modelos de mercado sejam elaborados virtual int LongCondition(void); virtual int ShortCondition(void); virtual double Direction(void); //--- protected: //--- método de inicialização do indicador bool InitCustomIndicator(CIndicators *indicators); //- obter o valor do limite superior do canal double Upper(int ind) {return(m_equi_chs.GetData(2,ind));} //- obter o valor da limite inferior do canal double Lower(int ind) {return(m_equi_chs.GetData(3,ind));} //- obter a flag do surgimento do canal double NewChannel(int ind) {return(m_equi_chs.GetData(4,ind));} }; ---------//+---------------------------------------------------------+
Algumas nuances a serem observadas.
O principal gerador de sinal nesta classe é a configuração do canal equidistante, é o único na versão atual e por enquanto, não haverá nenhum outro. Por sua vez, contém a classe para trabalhar com indicador técnico do tipo personalizado — CiCustom.
O modelo básico é usado como o modelo de sinal: "ao tocar na margem inferior do canal — comprar; na parte superior — vender". Desde que tocar com extrema precisão, por assim dizer, não seja o evento mais provável, então se usa um certo buffer com extremidades ajustáveis. O parâmetro de tolerância externa m_pnt_out determina o quão longe o preço tem a permissão de ir além das extremidades do canal; o parâmetro de tolerância interno m_pnt_in — estabelece qual medida o preço está autorizado a permanecer longe da extremidade. A lógica é bastante simples. Suponha que o preço atingiu os limites do canal, que se aproximou dele ou até o ultrapassou levemente. Fig.2 Apresentação esquemática de um buffer. O preço e a extremidade acionam o modelo.
Fig.2 Acionando o modelo básico do sinal
O array do parâmetro m_upper_zone[2] delineia as extremidades do buffer superior; o m_lower_zone[2] — o inferior.
O nível de preço $1.11552 no exemplo serve como a extremidade superior do canal (linha vermelha). O nível de $1,11452 é responsável pelo limite inferior do buffer e o $1,11702 — pelo superior. Desta forma, a flexibilidade da tolerância externa é 150 pontos e a interna é de 100 pontos. O preço é exibido pela curva azul.
O parâmetro m_on_start permite ignorar os sinais do primeiro canal ao executar o robô no gráfico, pois neste caso, o canal já havia sido desenhado. Se a flag é reiniciada, o robô irá funcionar apenas no próximo canal e não irá processar sinais de negociação na atual.
Os parâmetros m_base_low_price e m_base_high_price armazenam os valores das mínimas dos preço e das máximas nas barras atuais. A barra é considerada como zero, se a negociação for realizada em cada tick, ou na barra anterior se a negociação for permitida apenas com o aparecimento de uma nova barra.
Agora, algumas palavras sobre os métodos. Aqui devemos notar que o desenvolvedor proporciona um nível suficientemente amplo em relação a liberdade de ação, pois cerca de metade dos métodos são virtuais. Isto significa que o comportamento das classes descendentes podem ser implementadas conforme seja necessário.
Vamos começar com o método Direction(), que quantitativamente estima o potencial da direção de negociação:
---------//+---------------------------------------------------------+ //| Determinar a direção "pesada" | ---------//+---------------------------------------------------------+ double CSignalEquidChannel::Direction(void) { double result=0.; //--- aparecimento de um novo canal datetime last_bar_time=this.Time(0); bool is_new_channel=(this.NewChannel(0)>0.); //--- Se os sinais do primeiro canal são ignorados if(!m_on_start) //--- Se o primeiro canal é normalmente exibido durante a inicialização if(m_prev_frac_num==3) { static datetime last_ch_time=0; //--- se um novo canal apareceu if(is_new_channel) { last_ch_time=last_bar_time; //--- se este é o primeiro lançamento if(m_last_ch_time==0) //--- armazenar a hora da barra, onde o primeiro canal apareceu m_last_ch_time=last_ch_time; } //---se os horários combinam if(m_last_ch_time==last_ch_time) return 0.; else //--- limpar a flag m_on_start=true; } //--- indicador de barras atuais int actual_bar_idx=this.StartIndex(); //--- definir as extremidades double upper_vals[2],lower_vals[2]; // [0]-a barra antecende a atual, [1]-barra atual ArrayInitialize(upper_vals,0.); ArrayInitialize(lower_vals,0.); for(int idx=ArraySize(upper_vals)-1,jdx=0;idx>=0;idx--,jdx++) { upper_vals[jdx]=this.Upper(actual_bar_idx+idx); lower_vals[jdx]=this.Lower(actual_bar_idx+idx); if((upper_vals[jdx]==0.) || (lower_vals[jdx]==0.)) return 0.; } //--- obter os preços double curr_high_pr,curr_low_pr; curr_high_pr=this.High(actual_bar_idx); curr_low_pr=this.Low(actual_bar_idx); //--- Se os preços são obtidos if(curr_high_pr!=EMPTY_VALUE) if(curr_low_pr!=EMPTY_VALUE) { //--- armazenar os preços m_base_low_price=curr_low_pr; m_base_high_price=curr_high_pr; //---Definir preços para as zonas dos buffer //--- zona superior: [0]-tolerância interna, [1]-externa this.m_upper_zone[0]=upper_vals[1]-m_pnt_in; this.m_upper_zone[1]=upper_vals[1]+m_pnt_out; //--- zona inferior: [0]-tolerância interna, [1]-externa this.m_lower_zone[0]=lower_vals[1]+m_pnt_in; this.m_lower_zone[1]=lower_vals[1]-m_pnt_out; //---normalização for(int jdx=0;jdx<ArraySize(m_lower_zone);jdx++) { this.m_lower_zone[jdx]=m_symbol.NormalizePrice(m_lower_zone[jdx]); this.m_upper_zone[jdx]=m_symbol.NormalizePrice(m_upper_zone[jdx]); } //---verificar se há zonas de convergência if(this.m_upper_zone[0]<=this.m_lower_zone[0]) return 0.; //--- Resultado result=m_weight*(this.LongCondition()-this.ShortCondition()); } //--- return result; } ---------//+---------------------------------------------------------+
O primeiro bloco na estrutura do método, verifica se é necessário ignorar o primeiro canal no gráfico, caso o mesmo esteja presente.
O segundo bloco obtém os preços atuais e determina as zonas do buffer. Esta é a verificação de convergência das zonas. Se o canal for demasiadamente estreito, ou se as zonas buffer são muito largas, existe a possibilidade puramente matemática de que o preço possa entrar em ambas as zonas. Portanto, tal situação deve ser examinada.
O alvo é destacado na linha azul. Aqui, conseguimos uma estimativa quantitativa da direção de negociação, caso haja alguma.
Agora, vamos considerar o método LongCondition().
---------//+---------------------------------------------------------+ //| Verificar a condição para a compra | ---------//+---------------------------------------------------------+ int CSignalEquidChannel::LongCondition(void) { int result=0; //--- se a mínima de preço é definida if(m_base_low_price>0.) //--- se a mínima de preço está no nível da extremidade inferior if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1])) { if(IS_PATTERN_USAGE(0)) result=m_pattern_0; } //--- return result; } ---------//+---------------------------------------------------------+
Para comprar, verifique se o preço entrou na zona do buffer inferior. Feito isso, verifique a permissão para ativar o modelo de mercado. Mais detalhes sobre as estruturas do tipo "IS_PATTERN_USAGE(k)" podem ser encontradas no artigo «Gerador de Sinal de Negócios baseado em um Indicador Personalizado».
O método ShortCondition() funciona de modo similar ao mencionado acima. O foco está somente na zona do buffer superior.
---------//+---------------------------------------------------------+ //| Verificar a condição para venda | ---------//+---------------------------------------------------------+ int CSignalEquidistantChannel::ShortCondition(void) { int result=0; //--- se a máxima de preço é definida if(m_base_high_price>0.) //--- se a máxima de preço está no nível da extremidade superior if((m_base_high_price>=m_upper_zone[0]) && (m_base_high_price<=m_upper_zone[1])) { if(IS_PATTERN_USAGE(0)) result=m_pattern_0; } //--- return result; } ---------//+---------------------------------------------------------+
A classe inicializa um indicador personalizado usando o método InitCustomIndicator():
---------//+---------------------------------------------------------+ //| Inicialização de indicadores personalizados | ---------//+---------------------------------------------------------+ bool CSignalEquidChannel::InitCustomIndicator(CIndicators *indicators) { //--- adicionar um objeto para a coleção if(!indicators.Add(GetPointer(m_equi_chs))) { PrintFormat(__FUNCTION__+": error adding object"); return false; } //--- especificar os parâmetros do indicador MqlParam parameters[8]; parameters[0].type=TYPE_STRING; parameters[0].string_value="EquidistantChannels.ex5"; parameters[1].type=TYPE_INT; parameters[1].integer_value=m_prev_frac_num; // 1) fractais anteriores parameters[2].type=TYPE_BOOL; parameters[2].integer_value=m_to_plot_fracs; // 2) visualizar fractais? parameters[3].type=TYPE_INT; parameters[3].integer_value=m_bars_beside; // 3) barras à esquerda/direita do fratal parameters[4].type=TYPE_INT; parameters[4].integer_value=m_bars_between; // 4) barra intermediária parameters[5].type=TYPE_INT; parameters[5].integer_value=m_relevant_pnt; // 5) ponto relevante parameters[6].type=TYPE_INT; parameters[6].integer_value=m_line_width; // 6) largura da linha parameters[7].type=TYPE_BOOL; parameters[7].integer_value=m_to_log; // 7) manter o log? //--- inicialização de objeto if(!m_equi_chs.Create(m_symbol.Name(),_Period,IND_CUSTOM,8,parameters)) { PrintFormat(__FUNCTION__+": error initializing object"); return false; } //--- número de buffers if(!m_equi_chs.NumBuffers(5)) return false; //--- ok return true; } ---------//+---------------------------------------------------------+
O primeiro valor no array do parâmetro deve ser o nome do indicador, assim como uma string.
A classe também contém um método virtual ValidationSettings(). Este método é semelhante ao anterior e verifica se os parâmetros do indicador do canal são definidos corretamente. Existem também métodos de serviço que recebem os valores correspondentes aos buffers do indicador personalizado.
No momento, todos estão relacionados com a classe de sinal derivada.
2.2 Classe de negociação de estratégia CEquidChannelExpert
A implementação básica da ideia vai exigir a escrita de uma classe derivada da classe padrão CExpert. Na etapa atual, o código será o mais compacto possível, porque é de fato necessário alterar apenas o comportamento do handler principal — o método Processing(). Ele é virtual, então temos a oportunidade de escrever todas as estratégias.
---------//+---------------------------------------------------------+ //| Classe CEquidChannelExpert. | //| Objetivo: Classe para EA de negociação de canal equidistante. | //| Derivado da classe CExper. | ---------//+---------------------------------------------------------+ class CEquidChannelExpert : public CExpert { //--- === Membros de dados === --- privado: //--- === Métodos === --- público: //--- construtor/destrutor void CEquidChannelExpert(void){}; void ~CEquidChannelExpert(void){}; protected: virtual bool Processing(void); }; ---------//+---------------------------------------------------------+
Aqui está o método:
---------//+---------------------------------------------------------+ //| Módulo principal | ---------//+---------------------------------------------------------+ bool CEquidChannelExpert::Processing(void) { //--- cálculo da direção m_signal.SetDirection(); //--- checar se existe posições abertas if(!this.SelectPosition()) { //--- módulo de abertura da posição if(this.CheckOpen()) return true; } //--- se não houver operações de negociação return false; }
Tudo é muito simples. Primeiramente, o sinal de objeto calcula a direção da possível negociação, após a presença de uma posição aberta que é verificada. Se não houver posição, ele procura por uma oportunidade para poder abrir. Se houver uma posição, então não faz nada.
O código da estratégia básica é implementado no arquivo BaseChannelsTrader.mq5.
Tal como no exemplo do funcionamento apresentado no vídeo.
Fig.3 Resultados da estratégia básica para 2013-2015
A execução foi feita no testador de estratégia, sendo averiguada de hora em hora para o símbolo EURUSD. Pode-se notar no balanço do gráfico que em certos intervalos a estratégia básica trabalhou pelo "princípio de serra": um negócio sem lucro, seguido por um rentável. Os valores dos parâmetros personalizados utilizados nos ensaios estão disponíveis no arquivo base_signal.set. Ele também contém os parâmetros do canal, além dos valores que permaneceram inalterados em todas as versões da estratégia.
Foi utilizado o modo de teste "Cada tick baseado em ticks reais".
Essencialmente, existem duas formas de melhorar o desempenho da negociação na estratégia. A primeira é a otimização, no qual consiste em selecionar uma combinação de valores dos parâmetros para maximizar o lucro, e etc. A segunda maneira, concerne na preocupação em encontrar os fatores que afetam o desempenho do EA. Se o primeiro método não está associado com a mudança da lógica da estratégia de negociação, o segundo não pode ser feito sem ele.
Na próxima seção, a estratégia básica será editada e os fatores de desempenho serão procurados.
3. Fatores de desempenho
Algumas palavras sobre a disposição. Por conveniência colocamos todos os arquivos da estratégia numa única pasta do projeto. Por conseguinte, a implementação da estratégia básica está localizada na subpasta Base (Fig.4) e assim por diante.
Fig.4 Exemplo da hierarquia das pastas do projeto para a estratégia de canais
Além disso, assumimos que cada novo fator é uma nova etapa para realizar as alterações nos arquivos de origem que compõem o código do EA.
3.1 Usando o trailing
Antes de começar, é sugerido o trailing padrão para a estratégia. Com o objeto da classe CTrailingFixedPips, o que permite manter posições abertas numa "distância" fixa (em pontos). Isto moverá tanto o preço do stop loss quanto o preço do take profit. Para desativar o trailing do take profit, é definido um valor zero para o parâmetro correspondente (InpProfitLevelPips).
Fizemos as seguintes alterações no código:
Adicionamose um grupo de parâmetros personalizados para o arquivo ChannelsTrader1.mq5 do expert:
//--- sinput string Info_trailing="+===-- Trailing --====+"; // +===-- Trailing --====+ input int InpStopLevelPips=30; // nível para StopLoss, em pips input int InpProfitLevelPips=50; // nível para TakeProfit, em pips
No bloco de inicialização, escrevemos um objeto do tipo CTrailingFixedPips para ser criado, incluído e definido nos parâmetros da estratégia.
//--- Objeto do trailing CTrailingFixedPips *trailing=new CTrailingFixedPips; if(trailing==NULL) { //--- erro printf(__FUNCTION__+": error creating trailing"); myChannelExpert.Deinit(); return(INIT_FAILED); } //--- Adicionando o objeto de trailing if(!myChannelExpert.InitTrailing(trailing)) { //--- erro PrintFormat(__FUNCTION__+": error initializing trailing"); myChannelExpert.Deinit(); return INIT_FAILED; } //--- parâmetros do trailing trailing.StopLevel(InpStopLevelPips); trailing.ProfitLevel(InpProfitLevelPips);
Uma vez que o trailing será utilizado, é necessário modificar o método principal CEquidChannelExpert::Processing(), no arquivo EquidistantChannelExpert1.mqh.
---------//+---------------------------------------------------------+ //| Módulo principal | ---------//+---------------------------------------------------------+ bool CEquidChannelExpert::Processing(void) { //--- cálculo da direção m_signal.SetDirection(); //--- Se não existe uma posição if(!this.SelectPosition()) { //--- módulo de abertura da posição if(this.CheckOpen()) return true; } //---se existe uma posição else { //--- verificar se a modificação da posição é viável if(this.CheckTrailingStop()) return true; } //--- se não houver operações de negociação return false; }
É isso aí. O trailing foi adicionado. Os arquivos atualizados da estratégia estão localizados na subpasta separada ChannelsTrader1.
Vamos nos certificar se a inovação possui qualquer impacto sobre a eficácia.
Assim, foram feitas várias execuções da estratégi ano modo de otimização, com os mesmos intervalos do histórico e parâmetros, assim como para a estratégia básica. Os parâmetros do stop loss e take profit foram ajustados:
Variável | Começo | Passo | Finalização |
---|---|---|---|
Nível do StopLoss, em pips | 0 | 10 | 100 |
Nível do TakeProfit, em pips | 0 | 10 | 150 |
Os resultados da otimização podem ser encontrados no arquivo ReportOptimizer-signal1.xml. A melhor execução é apresentada na Figura 5, onde o nível de StopLoss = 0 e TakeProfit = 150.
Fig.5 Resultados da estratégia com o uso do trailing para 2013-2015.
É fácil perceber a semelhança entre a última figura e a Fig 3. Deste modo, pode-se dizer que o uso do trailing, neste intervalo de valores, não aprimora o resultado.
3.2 Tipo de canal
Observamos que o tipo de canal afeta os resultados de desempenho. A ideia geral é esta: é melhor vender no canal descendente e comprar no ascendente. Se o canal é lateral (não inclinado), então é possível negociar com base em ambas as margens.
A enumeração ENUM_CHANNEL_TYPE define o tipo de canal:
---------//+---------------------------------------------------------+ //| Tipo de canal | ---------//+---------------------------------------------------------+ enum ENUM_CHANNEL_TYPE { CHANNEL_TYPE_ASCENDING=0, // ascendente CHANNEL_TYPE_DESCENDING=1, // descendente CHANNEL_TYPE_FLAT=2, // lateral }; ---------//+---------------------------------------------------------+
Definindo o parâmetro de tolerância de pesquisa para o tipo de canal no bloco de inicialização do arquivo de origem ChannelsTrader2.mq5 do EA.
//--- parâmetros do filtro filter0.PointsInside(_Point*InpPipsInside); filter0.PointsOutside(_Point*InpPipsOutside); filter0.TypeTolerance(_Point*InpTypePips); filter0.PrevFracNum(InpPrevFracNum); ...
Este parâmetro controla a velocidade da mudança de preço em pontos. Vamos assumir que ele é igual a 7 pips. Então se o canal "cresce" 6 pips em cada barra, ele não é o suficiente para ser considerado ascendente. Logo ele simplesmente será considerado lateral (não inclinado).
Adicionamos a identificação do tipo de canal no método Direction() da fonte do sinal SignalEquidChannel2.mqh para o sinal final.
//--- se o canal for novo if(is_new_channel) { m_ch_type=CHANNEL_TYPE_FLAT; // lateralidade (não inclinado) do canal //--- se foi configurado a tolerância para o tipo if(m_ch_type_tol!=EMPTY_VALUE) { //--- Tipo de canal //--- velocidade da mudança double pr_speed_pnt=m_symbol.NormalizePrice(upper_vals[1]-upper_vals[0]); //---Se a velocidade é suficiente if(MathAbs(pr_speed_pnt)>m_ch_type_tol) { if(pr_speed_pnt>0.) m_ch_type=CHANNEL_TYPE_ASCENDING; // canal ascendente else m_ch_type=CHANNEL_TYPE_DESCENDING; // canal descendente } } }
Inicialmente, o canal é considerado lateral - nem ascendente e nem descendente. Se o valor do parâmetro de tolerância para a identificação do tipo de canal não foi definido, então ele não retorna para determinar a velocidade de mudança.
A condição para a compra irá incluir uma verificação para saber se o canal não é descendente.
---------//+---------------------------------------------------------+ //| Verificar a condição para a compra | ---------//+---------------------------------------------------------+ int CSignalEquidChannel::LongCondition(void) { int result=0; //--- se a mínima de preço é definida if(m_base_low_price>0.) //--- Se o canal não é descendente if(m_ch_type!=CHANNEL_TYPE_DESCENDING) //--- se a mínima de preço está no nível da extremidade inferior if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1])) { if(IS_PATTERN_USAGE(0)) result=m_pattern_0; } //--- return result; } ---------//+---------------------------------------------------------+
Uma averiguação deste tipo é realizada na condição de venda para ver se o canal não é ascendente.
O metodo principal CEquidChannelExpert::Processing() do arquivo EquidistantChannelExpert2.mqh será o mesmo da versão básica, uma vez que o trailing foi excluído.
Comprovando a eficácia deste fator, apenas um parâmetro foi otimizado.
Variável | Começo | Passo | Finalização |
---|---|---|---|
Tolerância para o tipo, em pips | 0 | 5 | 150 |
Os resultados da otimização podem ser encontrados no arquivo ReportOptimizer-signal2.xml. A melhor execução é apresentada na Fig.6.
Fig.6 Resultados da estratégia com o uso do tipo de canalpara 2013-2015.
É fácil perceber que os resultados dos testes foram ligeiramente melhores do que os resultados da estratégia básica. Acontece que, a um determinado valor base de parâmetros, um filtro como o tipo de canal influencia no resultado final.
3.3 Largura do canal
Observamos que a largura do canal pode influenciar o tipo de estratégia em si. Se o canal se tornou estreito, então quando a extremidade for rompida, será possível negociar na direção do rompimento e não contra ela. Isso resulta numa estratégia de rompimento. Se o canal se tornando largo, é possível negociar com base nas suas extremidades. Esta é a estratégia de recuperação. Na estratégia atual a negociação é realizada com base nos limites do canal.
Obviamente, aqui é necessário um critério para determinar se o canal é estreito ou largo. Para não executar ordens n extremidades é sugerido adicionar algo entre os dois, de modo que não considere o canal analisado estreito ou largo. Como resultado, 2 critérios são necessários:
- Largura suficiente de um canal estreito;
- Largura suficiente de um canal largo.
Se o canal não for nenhuma destas duas situações, então pode ser sensato em abster-se da entrada no mercado.
Fig.7 Largura do canal, diagrama
Ocorre um problema geométrico com a determinação da largura do canal, tal como nos eixos gráficos, eles são medidos em valores diferentes. Fica fácil medir o comprimento dos segmentos AB e CD, mas existe um problema com o cálculo do segmento CE (Fig.7).
Escolhemos o método mais simples para a normatização, ainda que polêmico e menos preciso. A fórmula é a seguinte:
comprimento CE ≃ comprimento CD / (1.0 + velocidade de canal)
A largura do canal é medida usando enumeração ENUM_CHANNEL_WIDTH_TYPE:
---------//+---------------------------------------------------------+ //| Largura do canal | ---------//+---------------------------------------------------------+ enum ENUM_CHANNEL_WIDTH_TYPE { CHANNEL_WIDTH_NARROW=0, // estreito CHANNEL_WIDTH_MID=1, // médio CHANNEL_WIDTH_BROAD=2, // largo };
Adicionado os critérios de largura do canal ao grupo dos parâmetros personalizados "Channels" no arquivo de origem ChannelsTrader3.mq5 do expert.
//--- sinput string Info_channels="+===-- Channels --====+"; // +===-- Channels --====+ input int InpPipsInside=100; // tolerância interna, pips input int InpPipsOutside=150; // tolerância externa, pips input int InpNarrowPips=250; // Limite do canal, pips input int InpBroadPips=1200; // Largura de canal, pips ...
Se o critério do canal estreito tem um valor maior do que a largura do canal, acontecerá um erro de inicialização.
//--- parâmetros do filtro filter0.PointsInside(_Point*InpPipsInside); filter0.PointsOutside(_Point*InpPipsOutside); if(InpNarrowPips>=InpBroadPips) { PrintFormat(__FUNCTION__+": error specifying narrow and broad values"); return INIT_FAILED; } filter0.NarrowTolerance(_Point*InpNarrowPips); filter0.BroadTolerance(_Point*InpBroadPips);
O momento de determinar o grau da largura do canal é apresentado no corpo do método Direction().
//--- Largura do canal m_ch_width=CHANNEL_WIDTH_MID; // média double ch_width_pnt=((upper_vals[1]-lower_vals[1])/(1.0+pr_speed_pnt)); //--- Se o critério do canal estreito é especificado if(m_ch_narrow_tol!=EMPTY_VALUE) if(ch_width_pnt<=m_ch_narrow_tol) m_ch_width=CHANNEL_WIDTH_NARROW; // estreito //--- Se o critério do canal largo é especificado if(m_ch_narrow_tol!=EMPTY_VALUE) if(ch_width_pnt>=m_ch_broad_tol) m_ch_width=CHANNEL_WIDTH_BROAD; // largo
Inicialmente, o canal é considerado como médio. Em seguida, é verificado se é estreito ou largo.
É também necessário mudar tanto os métodos de determinação, bem como a negociação pela direção. Portanto, a condição para a compra ficará da seguinte forma:
---------//+---------------------------------------------------------+ //| Verificar a condição para a compra | ---------//+---------------------------------------------------------+ int CSignalEquidChannel::LongCondition(void) { int result=0; //--- se o canal é estreito - negocie no rompimento da extremidade superior if(m_ch_width==CHANNEL_WIDTH_NARROW) { //--- se a máxima de preço é definida if(m_base_high_price>0.) //--- se a máxima de preço está no nível da extremidade superior if(m_base_high_price>=m_upper_zone[1]) { if(IS_PATTERN_USAGE(0)) result=m_pattern_0; } } //--- ou se o canal é largo - negocie na recuperação a partir da margem inferior else if(m_ch_width==CHANNEL_WIDTH_BROAD) { //--- se a mínima de preço é definida if(m_base_low_price>0.) //--- se a mínima de preço está no nível da extremidade inferior if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1])) { if(IS_PATTERN_USAGE(0)) result=m_pattern_0; } } //--- return result; } ---------//+---------------------------------------------------------+
O método consiste em duas partes. A primeiraanalisa a oportunidade de negociar no rompimento do canal estreito. Nota-se que na variante atual, o rompimento é considerado como o preço atingindo na parte superior da zona do buffer superior. A segunda parte verifica se o preço já tenha entrou na zona do buffer inferior para que a estratégia de recuperação entre em jogo.
O método para verificar a possibilidade de vender — ShortCondition() — é criado por analogia.
A princípio, o método CEquidChannelExpert::Processing() no arquivo continua inalterado EquidistantChannelExpert3.mqh.
Existem 2 parâmetros a serem otimizados.
Variável | Começo | Passo | Finalização |
---|---|---|---|
Canal estreito, em pips | 100 | 20 | 250 |
Canal largo, em pips | 350 | 50 | 1250 |
Os resultados da otimização podem ser encontrados no arquivo ReportOptimizer-signal3.xml. A melhor execução é apresentada na Fig.8.
Fig.8 Resultados da estratégia considerando a largura do canal para2013-2015.
Talvez, este seja o fator de maior impacto entre todas as descrições acima. A curva de equilíbrio tem agora uma direção mais pronunciada.
3.4 Limite do Stop Loss e do Take Profit
Se as metas de negociação estão originalmente presente na forma do Stop Loss e do Take Profit, então deve haver a possibilidade de ajustar estes níveis às condições da estratégia atual. Em suma, se existe um canal que faz o seu caminho através da dinâmica sobre o gráfico em determinado ângulo, os níveis do Stop Loss e do Take Profit devem ser movidos em conjunto com as extremidades do canal.
Um conjunto de modelos foram adicionados por conveniência. Agora assemelham-se a isto:
//--- "pesos" dos modelos do mercado (0-100) int m_pattern_0; // Modelo de "Recuperaçao da margem do canal" int m_pattern_1; // Modelo de "Rompimento da margem de canal" int m_pattern_2; // Modelo do "Novo canal"
As versões anteriores tinham apenas um, o preço tocando qualquer extremidade do canal. Agora, o modelo de recuperação e de rompimento serão diferenciados. Agora há também o terceiro modelo — novo modelo do canal. É necessário nos casos onde existe um novo canal e também uma posição aberta no canal anterior. Se o modelo for desencadeado, a posição será fechada.
A condição para a compra aparece da seguinte maneira:
---------//+---------------------------------------------------------+ //| Verificar a condição para a compra | ---------//+---------------------------------------------------------+ int CSignalEquidChannel::LongCondition(void) { int result=0; bool is_position=PositionSelect(m_symbol.Name()); //--- se o canal é estreito - negocie no rompimento da extremidade superior if(m_ch_width_type==CHANNEL_WIDTH_NARROW) { //--- se a máxima de preço é definida if(m_base_high_price>0.) //--- se a máxima de preço está no nível da extremidade superior if(m_base_high_price>=m_upper_zone[1]) { if(IS_PATTERN_USAGE(1)) { result=m_pattern_1; //--- Se não existe uma posição if(!is_position) //--- no Diário do terminal if(m_to_log) { Print("\nTriggered the \"Breakout of channel border\" model for buying."); PrintFormat("High price: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_high_price); PrintFormat("Trigger price: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]); } } } } //--- ou se o canal é largo ou médio - negociar a recuperação da margem inferior else { //--- se a mínima de preço é definida if(m_base_low_price>0.) //--- se a mínima de preço está no nível da extremidade inferior if((m_base_low_price<=m_lower_zone[0]) && (m_base_low_price>=m_lower_zone[1])) { if(IS_PATTERN_USAGE(0)) { result=m_pattern_0; //--- Se não existe uma posição if(!is_position) //--- no Diário do terminal if(m_to_log) { ); PrintFormat("Low price: %0."+IntegerToString(m_symbol.Digits())+"f",m_base_low_price); PrintFormat("Zone up: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[0]); PrintFormat("Zone down: %0."+IntegerToString(m_symbol.Digits())+"f",m_upper_zone[1]); } } } } //--- return result; } ---------//+---------------------------------------------------------+
Além disso, há agora uma verificação da condição de venda:
---------//+---------------------------------------------------------+ //| Verifique a condição para fechar uma compra | ---------//+---------------------------------------------------------+ bool CSignalEquidChannel::CheckCloseLong(double &price) const { bool to_close_long=true; int result=0; if(IS_PATTERN_USAGE(2)) result=m_pattern_2; if(result>=m_threshold_close) { if(m_is_new_channel) //--- Se uma compra está para ser fechada if(to_close_long) { price=NormalizeDouble(m_symbol.Bid(),m_symbol.Digits()); //--- no Diário do terminal if(m_to_log) { Print("\nTriggered the \"New channel\" model for closing buy."); PrintFormat("Close price: %0."+IntegerToString(m_symbol.Digits())+"f",price); } } } //--- return to_close_long; } ---------//+---------------------------------------------------------+Para uma posição de venda, a condição para o fechamento será idêntica.
Agora, algumas palavras sobre o Trailing. Uma classe separada CTrailingEquidChannel foi escrita por isso, com a classe CExpertTrailing como parente.
---------//+---------------------------------------------------------+ //| Classe CTrailingEquidChannel. | //| Objetivo: Classe do Trailing Stops baseado no Canal Equidistante.| //| Deriva da classe CExpertTrailing. | ---------//+---------------------------------------------------------+ class CTrailingEquidChannel : public CExpertTrailing { protected: double m_sl_distance; // distância do stop loss double m_tp_distance; // distância do take profit double m_upper_val; // limite superior double m_lower_val; // limite inferior ENUM_CHANNEL_WIDTH_TYPE m_ch_wid_type; // tipo de canal pela largura //--- público: void CTrailingEquidChannel(void); void ~CTrailingEquidChannel(void){}; //--- métodos de inicialização dos dados protegidos void SetTradeLevels(double _sl_distance,double _tp_distance); //--- virtual bool CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp); virtual bool CheckTrailingStopShort(CPositionInfo *position,double &sl,double &tp); //--- bool RefreshData(const CSignalEquidChannel *_ptr_ch_signal); }; ---------//+---------------------------------------------------------+
O método para obter a informação do sinal do canal é destacado em vermelho.
Os métodos de controle possibilitam o Trailing para as posições compradas e vendidas, no qual os antecessores foram redefinidos usando polimorfismo - o princípio básico do OOP.
Para a classe de Trailing receber os alvos de tempo e preço do canal atual, foi necessário criar uma ligação com a classe de sinais CSignalEquidChannel. Ele foi implementado no ponteiro constante dentro da classe CEquidChannelExpert. Esta abordagem permite a obtenção de toda a informação necessária a partir do sinal, sem o perigo de alterar os parâmetros do sinal em si.
---------//+---------------------------------------------------------+ //| Classe CEquidChannelExpert. | //| Objetivo: Classe para EA de negociação de canal equidistante. | //| Derivado da classe CExper. | ---------//+---------------------------------------------------------+ class CEquidChannelExpert : public CExpert { //--- === Membros de dados === --- privado: const CSignalEquidChannel *m_ptr_ch_signal; //--- === Métodos === --- público: //--- construtor/destrutor void CEquidChannelExpert(void); void ~CEquidChannelExpert(void); //--- ponteiro para o canal do objeto de sinal void EquidChannelSignal(const CSignalEquidChannel *_ptr_ch_signal){m_ptr_ch_signal=_ptr_ch_signal;}; const CSignalEquidChannel *EquidChannelSignal(void) const {return m_ptr_ch_signal;}; protected: virtual bool Processing(void); //--- verificar fechamento das posições de negociação virtual bool CheckClose(void); virtual bool CheckCloseLong(void); virtual bool CheckCloseShort(void); //--- verificação do trailing stop virtual bool CheckTrailingStop(void); virtual bool CheckTrailingStopLong(void); virtual bool CheckTrailingStopShort(void); }; ---------//+---------------------------------------------------------+
Os métodos responsáveis pelo fechamento e o trailing também foram redefinidos na classe expert.
A princípio, o método CEquidChannelExpert::Processing() no EquidistantChannelExpert4.mqh tem a seguinte aparência:
---------//+---------------------------------------------------------+ //| Módulo principal | ---------//+---------------------------------------------------------+ bool CEquidChannelExpert::Processing(void) { //--- cálculo da direção m_signal.SetDirection(); //--- Se não existe uma posição if(!this.SelectPosition()) { //--- módulo de abertura da posição if(this.CheckOpen()) return true; } //---se existe uma posição else { if(!this.CheckClose()) { //--- verificar se a modificação da posição é viável if(this.CheckTrailingStop()) return true; //--- return false; } else { return true; } } //--- se não houver operações de negociação return false; } ---------//+---------------------------------------------------------+Estes parâmetros serão otimizados:
Variável | Começo | Passo | Finalização |
---|---|---|---|
Stop Loss, em pontos | 25 | 5 | 75 |
Take Profit, em pontos | 50 | 5 | 200 |
Os resultados da otimização podem ser encontrado no arquivo ReportOptimizer-signal4.xml. A melhor execução é apresentada na Fig.9.
Fig.9 Resultados da estratégia no período de 2013-2015 2013-2015.
Claramente que este fator - os limites dos níveis de preços - não melhorou o desempenho.
Conclusão
O artigo apresentou o processo de desenvolvimento e implementação de uma classe para envio de sinais com base nos canais móveis. Cada versão do sinal foi seguida de uma estratégia de negociaçao com os resultados dos testes.
Deve-se ressaltar que os valores fixos para as definições do canal equidistante foram utilizados ao longo do artigo. Portanto, as conclusões sobre eficiência de um ou outro fator são verdadeiras somente para os valores determinados.
Ainda existem outras maneiras de melhorar os resultados de desempenho. Este artigo abrange parte do trabalho em encontrar tais possibilidades.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1863
- 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