English Русский 中文 Español Deutsch 日本語
Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência

Aprimorando o Testador de Estratégia para Otimizar Indicadores Exclusivamente nos Exemplos dos Mercados Lateral e de Tendência

MetaTrader 4Exemplos | 27 setembro 2016, 10:52
2 656 0
Carl Schreiber
Carl Schreiber

Problema

Existem muitos parâmetros para otimizar.

Um EA de negociação com muitos indicadores que possuem diversos parâmetros precisará de um bom tempo para sua otimização, pois existem muitas combinações a serem testadas. E se pudermos reduzir essa quantidade de combinações antes de começar a otimização da negociação do EA? Em outras palavras, antes de codificar a negociação do EA, começamos com um pseudo EA que questiona apenas perguntas específicas do mercado. Podemos dividir um grande problema em partes menores e resolvê-lo separadamente. Este pseudo-EA não negocia! Como exemplo, vamos escolher o ADX e verificar se este indicador pode distinguir entre os mercados lateral e de tendência, talvez assim possamos ganhar algumas informações adicionais.

Imagine uma ideia de negociação a curto prazo para um EA que depende do conhecimento se o mercado é lateral para um 'swing-trade' (negociação na mudança de direção da média móvel) ou se possui uma estratégia de seguir a tendência (negociando no caminho da média móvel). Para esta distinção, nosso EA de negociação deve usar (apenas) o ADX num timeframe maior - neste caso, barras de 1 hora. Junto ao ADX, o EA de negociação pode ter 5 indicadores (para gerenciamento de negociação a curto prazo). Cada indicador tem 4 parâmetros para configurar, sendo que cada um deles tem 2000 valores diferentes devido aos seus pequenos passos. Daria 2000*5*4 = 40.000 valores diferentes no total. Agora vamos adicionar o ADX. Para cada combinação de parâmetros do ADX, em teoria, temos que passar por 40.000 cálculos adicionais.

Neste exemplo, será definido o período ADX (PER), o seu preço (PRC) e o limite (LIM) para que possamos propor uma tendência inícial pelo ADX (MODE_MAIN), acima do LIM, assim como delimitamos um mercado lateral menor que o LIM. Para o período PER, podemos tentar entre 2,..,90 (Step1=> 89 valores diferentes), para o preço podemos escolher entre 0,..,6 (=Close,.., Weighted, Step1 => 7) e para o LIM nós tentamos entre 4,..,90 (Step2 => 87). No total teremos 89*7*87 = 54,201 combinações para testar. Aqui está a configuração do Testador de Estratégia:

Fig. 01 Parâmetro StratTester-Setup

Fig. 02 Opção do EA StratTester-Setup

Fig. 03 Opção StratTester-Setup

Fig. 04 Otimizações do StratTester-Setup

Se você repetir essa otimização, não se esqueça de excluir o arquivo em cache na pasta \tester\cache\. Senão, você verá nos "Resultados de Otimização" e no "Gráfico de otimização" do Testador de Estratégia, mas não no arquivo CSV, já que a OnTester() não é executado em tal caso.

Claro que, normalmente usaremos essas variações para serem otimizadas pelo Testador de Estratégia, porém devemos primeiro encontrar resultados sem sentido para ver se podemos detectá-los e excluí-los, em segundo lugar, existem razões educacionais para expandir as variaçõe. Devido ao fato de que o nosso pseudo EA não negocia (não há necessidade de usar cada tick!) e de termos apenas um indicador com 54,201 combinações para testar, podemos desligar o modo genético e deixar o Testador calcular todas as combinações.

Se não fosse feito a pré-otimização-Adx ou caso não realizássemos a redução no número de combinações do ADX, teríamos que multiplicar 40.000 combinações com outras 54.201 combinações do ADX e nós teríamos 2.168.040,000 combinações para a otimizar - um pouco demais certo? Nessas situações usamos a otimização genética.

No final, nós não poderíamos somente reduzir o alcance dos parâmetros AdX drásticamente - que é OK, isto já é esperado! Nós obteremos uma melhor compreensão do ADX, quando ele puder realmente distinguir entre os mercados laterais e de tendência - mesmo se a detecção do mercado lateral seja um pouco mais atrasada do que o mercado de tendência (espaço para melhorias?)! E, além disso, poderemos obter algumas ideias para determinar as perdas no Stop Loss e os alvos da negociação do EA, devido aos intervalos encontrados dos mercados laterais e de tendência. Os períodos do ADX são testados a partir do PER: 11..20 (Step 1=> 10), PRC: 0,6 (Step 6=>2) e LIM: 17,..,23 (Step 1=> 7) - no total são apenas 140 combinações. Isso significa que em vez de ter 2,168,040,000 combinações para testar, temos apenas 4,680,000 combinações para o EA, que é cerca de 460 vezes mais rápido no modo não genético ou mais ou menos 460 vezes melhor do que o modo genético. No modo genético, o testador executa somente ~10.000 passes, entretanto mais valores de outros parâmetros da negociação do EA são testados!

Preste atenção caso resolva usar o algoritmo genético: os seus resultados variam muito de acordo com a relação da combinação totalmente disponível e dos passes efetivamente realizados. Quanto mais resultados ruins forem encontrados durante a otimização, menor será a quantidade de bons resultados a serem selecionados nas próximas configurações.


A Idéia

Nós construímos um pseudo-EA que não negocia, possui apenas três funções importantes. A função OnTick(), é onde verificamos o indicador e determinamos o estado do mercado; a OnTester(), é onde escrevemos os resultados finais do nosso arquivo CSV e a calcOptVal(), onde calculamos o valor OptVal, que retorna ao Testador de Estratégia pela OnTester(), para o ordenamento e o Algoritmo Genético. A função OnTester() é chamada e no final de um passe de otimização, retorna um valor específico, acrescentando uma nova linha para um arquivo CSV, deste modo uma análise é realizada após toda a otimização ser concluída.


O Pseudo-EA, Primeira Aproximação


Agora precisamos determinar os critérios para calcular o valor de retorno: OptVal. Nós escolhemos a variação de mercados lateral e de tendência, que é a diferença entre a maior Máxima e a menor Mínima de preço do mercado atual, dividindo "TrndRange" por "FlatRange" para que o otimizador possa maximizar o resultado a seguir:
double   TrndRangHL,       // soma da maior Máxima de preço - menor Mínima de preço dos mercados de tendências
         TrndNum,          // número de tendência do mercado
         FlatRangHL,       // soma da maior Máxima de preço - menor Mínima de preço dos mercados laterais
         FlatNum,          // número do mercado lateral
         RangesRaw,        // variação do mercado de tendência dividido pelo número de mercados laterais (quanto maior, melhor)
         // ...            see below

double calcOptVal() // primeira aproximação!!
   {
      FlatRange    = FlatRangHL / FlatNum;
      TrndRange    = TrndRangHL / TrndNum;
      RangesRaw    = FlatRange>0 ? TrndRange/FlatRange : 0.0; 
      return(RangesRaw);
   }
...
double OnTester() 
   {
      OptVal = calcOptVal();
      return( OptVal );
   }

Se executarmos uma otimização com as configurações acima mencionadas e se OptVal = RangesRaw, o resultado no "Gráfico de Otimização" vai aparecer como:

Fig. 05 Teste Gráfico Bruto

E se olharmos para os melhores valores nos Resultados das Otimizações, ordenados pelos Resultados da OnTester de cima para baixo, veremos:

Fig. 06 Melhores Valores do Teste Bruto

Relações altamente ridículas! Se olharmos para o arquivo cvs, vemos que o tempo médio de duração dos mercados laterais é de 1 barra e a quantidade do número de mercados laterais + número de mercados de tendência também é muito pequeno para um uso significativo. (Os números estranhos para PRC=1994719249 em vez de 0,.., 6 não devem nos incomodar, já que o número correto para o preço do ADX está escrito no arquivo cvs!).

Este resultado insatisfatório significa que temos de adicionar mais alguns critérios para excluir essas situações ridículas.


Pseudo-EA, Aprimoramento

Primeiramente, basta adicionar um comprimento mínimo ou um mínimo de barras dos mercados lateral ou de tendência:

      FlatBarsAvg  = FlatBars/FlatNum; // soma de todos as 'barras lateral'  / número de mercados lateral
      TrndBarsAvg  = TrndBars/TrndNum; // soma de todos as 'barras de tendência' / número de mercados de tendência
      BrRaw        = fmin(FlatBarsAvg,TrndBarsAvg);

Em segundo lugar, nós especificamos um mínimo de "Switches" entre os mercados lateral e o de tendência:

      SwitchesRaw  = TrndNum+FlatNum; // número de mercados de tendência e de lateralidade

Agora vamos enfrentar o próximo problema! Variações do RangesRaw de 0 a 100,000.0, BrRaw de 0 a 0,5 e SwitchesRaw de 0 a ~8000 (=Bars()) - teoricamente, se temos um "switch" em cada nova barra.

Precisamos equalizar os três critérios! Para todos eles, usamos a mesma função necessária: Arco tangente - ou no mq4 - atan(..)! Outros como, por exemplo, o sqrt() ou o log(), não possuem qualquer problema com 0 ou valores negativos. O atan() nunca excede um limite de modo que, por exemplo, com o RangesRaw, a diferença entre o atan(100,000) e o atan(20) é praticamente 0 e eles possuem quase o mesmo peso, de modo que os resultados de outros fatores sofrem maior influência. Além disso, o atan() fornece um aumento suave do limite, enquanto um limite rígido como if(x>limit) pesa todos os valores dos maiores limites por igual e novamente encontrará os melhores valores próximos do nosso limite, porém ainda não será o que estamos procurando. Você verá mais a frente!

Vamos ver como atan() funciona (para os gráficos atan(), eu uso isto):

Fig. 07 Função Atan

A versão azul é (apenas) limitada entre +1 e -1 (pela divisão do pi/2).
A linha vermelha (e sua função) mostra como podemos mover a interceptação do eixo x, de x=0 para x=4.
A linha verde mostra como podemos mudar a inclinação. Nós controlamos o quão rápido o atan() se aproxima do limite e quão rápido as diferenças gradualmente se tornam menores.

O que não precisamos aqui é mudar o limite de aproximação das versões do nosso atan(). Mas, se você, por exemplo, mudar o primeiro 1*atan(..) para 2*atan(..), o limite se move para +2 e -2.

O que não precisamos aqui é trocar o limite superior e inferior, transformando 1*atan() em -1*atan(). Agora nossa função está aproximando de -1 para um x maior.

Neste momento, nós temos tudo para nosso pseudo-EA. Vamos começar a colocar as coisas em conjunto.


O Pseudo-EA, Versão Final

Nosso pseudo-EA não negocia! Ele apenas chama o iADX(..) se uma nova barra for aberta. Isto significa que não precisamos do "Cada Tick" ou dos "Pontos de Controle"! Podemos usar o modelo mais rápido, "Apenas Abertura de Preço", para calcularmos o estado do mercado pelas 2 barras anteriores do ADX:

extern int                 PER   = 22;             // Período do Adx
extern ENUM_APPLIED_PRICE  PRC   = PRICE_TYPICAL;  // Preço do Adx
extern double              LIM   = 14.0;           // Limite para a linha principal do Adx
extern string            fName   = "";             // nome do arquivo em \tester\files, "" => não é arquivo csv!


//+------------------------------------------------------------------+
//| Definição da Variável Global                                     |
//+------------------------------------------------------------------+
double   OptVal,           // este valor é devolvido pela OnTester() e seu valor pode ser encontrado na aba OnTerster() do Testador de Estratégia
         TrndHi,           // maior Máxima de preço do mercado de tendência atual
         TrndLo,           // menor Mínima de preço do mercado de tendência atual
         TrndBeg,          // preço no início de uma tendência do mercado
         TrndRangHL,       // soma da maior Máxima de preço - menor Mínima de preço dos mercados de tendência
         TrndRangCl,       // último fechamento - primeiro fechamento do mercado de tendência (da esquerda, mas não utilizado)
         TrndNum,          // número do mercado de tendência
         TrndBars=0.0,     // número de barras do mercado de tendência
         TrndBarsAvg=0.0,  // média das barras no mercado de tendêcia
         FlatBarsAvg=0.0,  // média das barras no mercado lateral
         FlatHi,           // maior Máxima de preço do mercado lateral corrente
         FlatLo,           // menor Mínima de preço do mercado lateral corrente
         FlatBeg,          // preço no início do mercado lateral
         FlatRangHL,       // soma da maior Máxima de preço - menor Mínima de preço dos mercados lateral
         FlatRangCl,       // último fechamento - primeiro fechamento de um mercado lateral (da esquerda, mas não utilizados)
         FlatNum,          // número do mercado lateral
         FlatBars=0.0,     // número de barras do mercado lateral
         FlatRange,        // tmp FlatRangHL / FlatNum
         TrndRange,        // tmp TrndRangHL / TrndNum
         SwitchesRaw,      // número de "switches"
         SwitchesAtan,     // "Atan" do número de "switches"
         BrRaw,            // Mínimo de horas do mercado lateral ou de tendência (maior é melhor)
         BrAtan,           // "Atan" do "BrRaw"
         RangesRaw,        // Variação do mercado de tendência dividido pelo número de mercados lateral (quanto maior, melhor)
         RangesAtan;       // "Atan" do "TrndRange/FlatRange"

enum __Mkt // 3 estados dos mercados 
 {
   UNDEF,  
   FLAT,
   TREND
 };
__Mkt MARKET = UNDEF;      // início o estado do mercado.
string iName;              // nome do indicador
double main1,main2;        // valores da linha principal do Adx


//+------------------------------------------------------------------+
//| OnTick calcula o Indi, determinando o estado do mercado          |
//+------------------------------------------------------------------+
void OnTick() 
 {
 //---
   static datetime tNewBar=0;
   if ( tNewBar < Time[0] ) 
    {
      tNewBar = Time[0];
      main1 = iADX(_Symbol,_Period,PER,PRC,  MODE_MAIN, 1); // ADX
      main2 = iADX(_Symbol,_Period,PER,PRC,  MODE_MAIN, 2); // ADX)
      iName = "ADX";

      // definição da configuração da variável apropriada do estado do mercado 
      if ( MARKET == UNDEF ) 
       { 
         if      ( main1 < LIM ) main2 = LIM+10.0*_Point; // MERCADO se torna LATERAL
         else if ( main1 > LIM ) main2 = LIM-10.0*_Point; // MERCADO se torna TENDÊNCIA
         FlatHi  = High[0];
         FlatLo  = Low[0];
         FlatBeg = Close[2];//
         TrndHi  = High[0];
         TrndLo  = Low[0];
         TrndBeg = Close[2];//
       }
      
      // vamos entrar em um mercado lateral?
      if ( MARKET != FLAT && main2>LIM && main1<LIM)  // ADX
       {
         //finaliza o mercado de tendência
         TrndRangCl += fabs(Close[2] - TrndBeg)/_Point;
         TrndRangHL += fabs(TrndHi - TrndLo)/_Point;
                  
         // atualiza os valores relevantes
         OptVal = calcOptVal();

         //define o novo mercado lateral
         MARKET  = FLAT;
         FlatHi  = High[0];
         FlatLo  = Low[0];
         FlatBeg = Close[1];//
         ++FlatNum;
         if ( IsVisualMode() )
          {
            if (!drawArrow("Flat "+TimeToStr(Time[0]), Time[0], Open[0]-(High[1]-Low[1]), 243, clrDarkBlue) ) // 39: mercado dorme
               Print("Error drawError ",__LINE__," ",_LastError);
          }
       } 
      else if ( MARKET == TREND )   // atualiza o mercado de tendência corrente
       {
         TrndHi = fmax(TrndHi,High[0]); 
         TrndLo = fmin(TrndLo,Low[0]); 
         TrndBars++;
       }
      
      // vamos entrar em um mercado de tendência 
      if ( MARKET != TREND && main2<LIM && main1>LIM) 
       { 
         // finaliza o mercado lateral
         FlatRangCl += fabs(Close[2] - FlatBeg)/_Point;
         FlatRangHL += fabs(FlatHi - FlatLo)/_Point;
         
         // atualiza os valores relevantes
         OptVal = calcOptVal();

         // define o novo mercado de tendência
         MARKET  = TREND;
         TrndHi  = High[0];
         TrndLo  = Low[0];
         TrndBeg = Close[1];//
         ++TrndNum;
         TrndBars++;
         if ( IsVisualMode() )
          {
            if(!drawArrow("Trend "+TimeToStr(Time[0]), Time[0], Open[0]-(High[1]-Low[1]), 244, clrRed)) // 119:kl Diamond
               Print("Error drawError ",__LINE__," ",_LastError);
          }
       } 
      else if ( MARKET == FLAT  ) // atualiza o mercado lateral corrente
       {
         FlatHi = fmax(FlatHi,High[0]);
         FlatLo = fmin(FlatLo,Low[0]); 
         FlatBars++; 
       }
      
    }
   if ( IsVisualMode() )  // mostrar a real situação no Modo Visual
    {
      string lne = StringFormat("%s  PER: %i    PRC: %s    LIM: %.2f\nMarket  #   BarsAvg  RangeAvg"+
                                "\nFlat:    %03.f    %06.2f         %.1f\nTrend: %03.f    %06.2f         %.1f   =>  %.2f",
                                 iName,PER,EnumToString(PRC),LIM,FlatNum,FlatBarsAvg,FlatRange,
                                 TrndNum,TrndBarsAvg,TrndRange,(FlatRange>Point?TrndRange/FlatRange:0.0)
      );
      Comment(TimeToString(tNewBar),"  ",EnumToString(MARKET),"  Adx: ",DoubleToString(main1,3),
              "  Adx-Lim:",DoubleToString(main1-LIM,3),"\n",lne);
    }
 }

Se o ADX cruza o LIM , nós finalizamos o estado anterior do mercado e preparamos um novo. O pseudo-EA calcula todas as suas diferenças de cotação em pontos!

Vejamos agora o que queremos alcançar, o que devemos determinar, o que é necessário e para que. Precisamos de um número para a OnTester() retornar. O otimizador do Testador de Estratégia calcula o maior e melhor. O valor de retorno pela OnTester() (OptVal), portanto devemos aumentar se a distinção entre mercado lateral e o de tendência fica melhor para as nossas necessidades!

Nós determinamos três variáveis para calcular o OptVal. Podemos facilmente definir um mínimo razoável para dois deles:

  1. RangesRaw = TrndRage/FlatRange deve ser maior do que 1! O mercado de tendência deve ter uma variação maior do que o mercado lateral. TrndRage e FlatRange são definidos como a maior Máxima de Preço - menor Mínima de Preço do mercado corrente. Vamos definir a intersecção do eixo-x em x=1.
  2. BrRaw deve ser maior do que 3 barras (= 3 horas). BrRaw = fmin(FlatBarsAvg,TrndBarsAvg).  FlatBarsAvg e TrndBarsAvg são os números médios de barras de cada mercado. Precisamos disso para evitar os valores da manhã nas fronteiras. Vamos definir o eixo x para interceptar em x=3.
  3. SwitchesRaw. Vamos otimizar mais de 8.000 barras. Um resultado de apenas 20 "switches" (10 mercados lateral e de tendência), por exemplo, não faria qualquer sentido.  Isso significaria, em média, 400 horas ou 16 dias por mercado?

O problema é encontrar um bom limite para o SwitchesRaw, uma vez que depende muito do prazo e do número total de barras. Exceto para 1) e 2), onde podemos definir os limites devido as considerações de plausibilidade, temos que dar uma olhada nos primeiros resultados (a aba:Opti ADX ALL do arquivo cvs anexado) para derivar o limite:

Fig. 08 Gráficos Switches Opti ADX ALL

Em vez de lidar com ~2500 switches diferentes, nós utilizamos somente sqrt(2500) = 50 classes, que é muito mais fácil de lidar. Para cada classe, a média é calculada e plotada. Vemos que há um mínimo local de 172. Vamos usar 100 para ver como o nosso pseudo-EA lida com esse limite. Nós utilizamos um pequeno coeficiente de 0,01 para garantir um aumento lento, a partir deste limite, até 100. Normalmente o certo seria usar um limite maior, talvez 200 - mas devido a razões educacionais ...

A fim de obter outros coeficientes, olhamos para a plotagem da função. Nós os adaptamos para que a curva não seja muito plano, onde assumimos resultados interessantes. Azul é a função para SwitchesRaw():

Fig. 09 Atan para Switches (azul)

Vamos olhar agora para as outras duas funções de avaliação.
O vermelho é a função para BrRaw: O mínimo aceito para qualquer duração de mercado é de 3 barras e um coeficiente de 0.5, garantindo que até 8 barras (horas) vão fazer a diferença.
Verde para RangesRaw: O mínimo aceito aqui é 1 e, como não podemos esperar um milagre, mais de 8 provavelmente não seria um resultado ideal.

Fig. 10 Barras Atan (vermelho), Variações (verde) e Switches (azul)

Agora nós podemos construir a função que calcula OptVal , o qual a OnTester() irá retornar.

  1. Como se aplica a todas as três variáveis (quanto maior, melhor), podemos multiplicá-los!
  2. Nós temos três variáveis e para todas elas, o parâmetro atan(..) pode tornar-se negativo, por isso temos que avaliar: fmax(0.0,atan(..)). Caso contrário, dois resultados negativos da nossa função atan() irão resultar em um valor positivo errado para OptVal .
//+------------------------------------------------------------------+
//| calcOptVal calcula OptVal para ser devolvido ao dispositivo do   |
//| Testador de Estratégia e os seus coeficientes de avaliação       |
//+------------------------------------------------------------------+
// Coeff. para SwitchesAtan, número de switches:
double SwHigh = 1.0, SwCoeff=0.01, SwMin = 100;
// Coeff. para BrAtan, num. de barras:
double BrHigh = 1.0, BrCoeff=0.5,  BrMin = 3.0;
// Coeff. para RangesAtan, TrendRange/FlatRange:
double RgHigh = 1.0, RgCoeff=0.7,  RgMin = 1.0;

double calcOptVal() {
   if ( FlatNum*TrndNum>0 ) {
      SwitchesRaw  = TrndNum+FlatNum;
      SwitchesAtan = SwHigh*atan( SwCoeff*(SwitchesRaw-SwMin))/M_PI_2;

      FlatBarsAvg  = FlatBars/FlatNum;
      TrndBarsAvg  = TrndBars/TrndNum;
      BrRaw        = fmin(FlatBarsAvg,TrndBarsAvg);
      BrAtan       = BrHigh*atan( BrCoeff*(BrRaw-BrMin))/M_PI_2;

      FlatRange    = FlatRangHL / FlatNum;
      TrndRange    = TrndRangHL / TrndNum;
      RangesRaw    = FlatRange>0 ? TrndRange/FlatRange : 0.0; 
      RangesAtan   = FlatRange>0 ? RgHigh*atan( RgCoeff*(RangesRaw-RgMin))/M_PI_2 : 0.0;
      return(fmax(0.0,SwitchesAtan) * fmax(0.0,BrAtan) * fmax(0.0,RangesAtan));  
   }
   return(0.0);
}



As outras partes do pseudo-EA estão na OnInit() para gravar os cabeçalhos da coluna do arquivo csv:

//+------------------------------------------------------------------+
//| Função de inicialização do expert                                |
//+------------------------------------------------------------------+
int OnInit() 
  {
//---
   // escreve a linha de cabeçalho do cálculo -Sheet
   if ( StringLen(fName)>0 ) {
      if ( StringFind(fName,".csv", StringLen(fName)-5) < 0 ) fName = fName+".csv";    //  verifica o nome do arquivo
      if ( !FileIsExist(fName) ) {                                                     // escrever os cabeçalhos da coluna de um novo arquivo
         int fH = FileOpen(fName,FILE_WRITE);
         if ( fH == INVALID_HANDLE ) Print("ERROR open ",fName,": ",_LastError); 
         string hdr = StringFormat("Name;OptVal;RangesRaw;PER;PRC;LIM;FlatNum;FlatBars;FlatBarsAvg;FlatRgHL;FlatRgCls;FlatRange;"+
                      "TrendNum;TrendBars;TrendBarsAvg;TrendRgHL;TrendRgCl;TrendRange;"+
                      "SwitchesRaw;SwitchesAtan;BrRaw;BrAtan;RangesRaw;RangesAtan;FlatHoursAvg;TrendHoursAvg;Bars;"+
                      "Switches: %.1f %.1f %.f, Hours: %.1f %.1f %.1f, Range: %.1f %.1f %.1f\n",
                      SwHigh,SwCoeff,SwMin,BrHigh,BrCoeff,BrMin,RgHigh,RgCoeff,RgMin);
         FileWriteString(fH, hdr, StringLen(hdr));
         FileClose(fH);
      }   
   }
//---
   return(INIT_SUCCEEDED);
  }

A função OnTester() finaliza o estado do mercado aberto e escreve o resultado da otimização no final do arquivo csv:

double OnTester() 
 {
   // verifica o limite de nocaute: pelo menos um switche
   if ( FlatNum*TrndNum<=1 ) return(0.0);  // se um deles é 0 => pule este resultados sem sentido
   
   // agora finaliza o último mercado: lateral
   if ( MARKET == FLAT ) 
    {
      TrndRangCl += fabs(Close[2] - TrndBeg)/_Point;
      TrndRangHL += fabs(TrndHi - TrndLo)/_Point;

      // atualiza os valores relevantes
      OptVal = calcOptVal();

    } 
   else if ( MARKET == TREND ) // .. e de tendência
    {
      FlatRangCl += fabs(Close[2] - FlatBeg)/_Point;
      FlatRangHL += fabs(FlatHi - FlatLo)/_Point;

      // atualização OptVal
      OptVal = calcOptVal();
    }
   
   // escreve o valor no arquivo csv
   if ( StringLen(fName)>0 ) 
    {
      string row = StringFormat("%s;%.5f;%.3f;%i;%i;%.2f;%.0f;%.0f;%.1f;%.0f;%.0f;%.2f;%.2f;%.0f;%.0f;%.1f;%.0f;%.0f;%.2f;%.2f;%.0f;%.5f;%.6f;%.5f;%.6f;%.5f;%.2f;%.2f;%.0f\n",
                  iName,OptVal,RangesRaw,PER,PRC,LIM,
                  FlatNum,FlatBars,FlatBarsAvg,FlatRangHL,FlatRangCl,FlatRange,
                  TrndNum,TrndBars,TrndBarsAvg,TrndRangHL,TrndRangCl,TrndRange,
                  SwitchesRaw,SwitchesAtan,BrRaw,BrAtan,RangesRaw,RangesAtan,
                  FlatBarsAvg*_Period/60.0,TrndBarsAvg*_Period/60.0,
                  (FlatBars+TrndBars)
             );
             
      int fH = FileOpen(fName,FILE_READ|FILE_WRITE);
      if ( fH == INVALID_HANDLE ) Print("ERROR open ",fName,": ",_LastError);
      FileSeek(fH,0,SEEK_END); 
      FileWriteString(fH, row, StringLen(row) );
      FileClose(fH);
    }
   // retorna 0.0 ao invés de valores Negativos! Eles atrapalham a Otimização de Gráfico no nosso caso.
   return( fmax(0.0,OptVal) );
 }


Agora o nosso pseudo-EA está pronto e preparamos o Testador de Estratégia para a otimização:

  1. Nós desativamos o "Algoritmo Genético" para testar todas as combinações.
  2. Defina o "Parâmetro Otimizado" para Customização. Isto nos mostra imagens mais interessantes no Gráfico Otimizado.
  3. Certifique-se de que o arquivo em cache na pasta ..\tester\caches foi deletado.
  4. Para um arquivo csv se certificar de que o fName não está vazio, um arquivo csv existente no \tester\files deve ser deletado.
  5. Se você deixar o nome de um arquivo para .csv, o otimizador irá adicionar linha por linha, tornando-se maior até você estar em apuros devido ao seu tamanho!
  6. Nós escolhemos o símbolo EURUSD.
  7. O período é definido como H1 (aqui a partir de 08. 13. 2015 até 20.11.2015).
  8. O modelo é definido como "Apenas Abertura de Preço".
  9. Não se esqueça de habilitar a "Otimização".

Após 25 minutos no meu laptop de 2007, o Testador de Estratégia concluiu a otimização e encontramos o resultado do arquivo csv em ..\tester\files\.

Nos Gráficos de Otimização podemos ver, por exemplo (bottom =LIM, right=PER):

Fig. 11 TesterGraph SwLim 100

Isso parece muito melhor do que a otimização inicial. Vemos um campo limpo com uma maior densidade de 34>PER>10 e 25>LIM>13, que é muito melhor do que 2,.., 90 e 4,..,90!

Vamos apenas verificar se os resultados para os diferentes mínimos de switches (=resultados estáveis) parecem semelhantes ou não:

SwMin = 50:

Fig. 11 TesterGraph SwLim 050

SwMin = 150

Fig. 13 TesterGraph SwLim 150

SwMin = 200:

Fig. 14 TesterGraph SwLim 200

Para toda as otimizações, são aplicados estes limites: 34>PER>10 e 25>LIM>13, que é um bom sinal para a robustez desta abordagem!

Lembre-se:
  • Tivemos de usar as funções atan(..) para fazer OptVal igualmente sensível às nossas três variáveis.
  • A utilização da função atan com os seus diferentes coeficientes é um pouco arbitrária! Foi testado até que eu obtivesse alguns resultados satisfatórios. Não poderia haver uma solução melhor, tente você mesmo!
  • Você pode pensar que eu o modifiquei até conseguir o que queremos ver - como sobre adaptar um EA. Correto, é por isso que nós temos que verificar cuidadosamente os resultados!
  • Este pseudo-EA não tem o intuito de encontrar a melhor solução única, porém temos em vista apenas descobrir limites razoáveis menores para cada parâmetro! No final o sucesso só é determinado pela negociação do EA!


Analisando os resultados no EXCEL para a Verificação de Plausibilidade

Cada passagem durante a otimização acrescenta uma nova linha para o arquivo csv, com muito mais informações do que as ofertas do Testador de Estratégia, ignorando as categorias que não precisamos, como Lucro, Negociações, Fator de Lucro, ... Carregamos este arquivo no Excel (no meu caso, o LibreOffice).

Temos de resolver tudo, em primeiro lugar, de acordo com nosso OptVal, segundo, de acordo com o RangesRaw, posteriormente nós obtemos este (aba: "Optimizing ADX SwLim 100 raw"):

Fig. 15 Optimizing ADX SwLim 100 raw

Nós procuramos os "melhores" 50, de acordo com o OptVal. Os diversos parâmetros PER, PRC e LIM são coloridos para facilitar a visualização.

  1. RangesRaw variam de 2.9 até 4.5. Isto significa que a tendência do mercado varia de 3 a 4,5 vezes mais do que o mercado lateral.
  2. O mercado lateral tem a duração de 6 a 9 barras (horas).
  3. O mercado lateral varia de 357 a 220 pontos - espaço suficiente para uma variação de negociação.
  4. A tendência dura entre 30 e 53 horas.
  5. O mercado de tendência varia de 1,250 a 882 pontos.
  6. Se você olhar além dos top 50, mas também para os top 200, as variações são quase as mesmas, RangesRaw: 2.5 para 5.4, o mercado lateral varia de 221-372, o de tendência varia: 1,276-783.
  7. PER dos top 200: 14 a 20 e LIM: 14 a 20, mas temos que ficar de olho nisso.
  8. Se olharmos para o OptVal quando ele torna-se 0.0, vemos valores muito elevados para RangesRaw, entretanto outros valores nos dizem que eles não são bons ao negociar (aba: "skipped OptVal=0"):

Fig. 16 skipped OptVal 0

O RangesRaw é ridiculamente alto, todavia o FlatBarsAvg é muito curto para o negociar e/ou TrndBarsAvg é muito alto, com mais de 1000 horas.

Agora vamos verificar o RangeRaw da parte que contém o OptVal>0 e se vai de acordo com o RangesRaw (aba: "OptVal>0 sort RangesRaw"):

Fig. 17 OptVal gt 0 sort RangesRaw

Os 50 maiores valores da variação do RangesRaw de 20 a 11. Porém, basta olhar para o TrendBarsAvg: Em média, cerca de 100, dura mais que 4 dias.

No total, podemos dizer que o OptVal desvalorizou muito bem todos os resultados ADX que seriam difíceis de negociar. Por outro lado, o RangesRaw mais alto dos top 200 (5.4) ou top 500 (7.1) parecem muito promissores.



Verificação de parâmetros

Após uma verificação de plausibilidade necessária, olhamos para os nossos parâmetros do ADX PER e PRC e seu limite LIM.

Devido a muitas linhas (= 29,106), precisamos apenas das linhas com OptVal maior que 0. Na tabela bruta, estas são as primeiras 4085 linhas (se o acc. foi classificado ao OptVal!). Nós o copiamos em uma nova aba. Acrescentamos três colunas ao lado do PER e os adicionamos de acordo com as imagens. Todas as fórmulas poderão ser visto no arquivo anexo.

A partir de linha 5 das Colunas D, E, F entre com: AVERAGE(D$2:D5), STDEV(D$2:D5), SKEW(D$2:D5). As células na fileira 2 mostram apenas os valores contidos na última linha, que são os resultados estatísticos de toda a coluna RangesRaw. Por que? Como a tabela é ordenada do melhor para o pior, vamos ver a média na coluna n, o desvio padrão e a assimetria da melhor n. A comparação dos melhores valores de n com todos os resultados, pode nos dizer onde provavelmente podemos encontrar o que estamos procurando (aba: "OptVal>0 Check PER, PRC, LIM"):

Fig. 18 OptVal gt 0 Check PER, PRC, LIM

O que podemos aprender com isso? Na segunda linha (abaixo da coluna E "last") vemos que a média (Avg) de todos PER éigual a 33,55 o desvio padrão (StdDev) é 21.60. Se PER é distribuído de acordo com uma distribuição de Gauss, encontramos 68% de todos os valores de PER dentro da média +/- StdDev e 95% no +/-2*StdDev. Aqui está entre 33.55 - 21.60 = 11.95 e 33.55 + 21.60 = 55,15. Agora vamos olhar para as melhores linhas. A média começa em 19 na linha 5 e aumenta lentamente para 20. As mudanças stdDev vão de 2,0 a 2,6. Agora, os 68% abrangem de 18 a 23. Finalmente, olhamos para a  assimetria. É de 0.61 na linha 2 para todos os PER. Isso significa que o lado esquerdo (menor) tem mais valores do que o lado direito, mesmo que ainda seja uma distribuição de Gauss. Se a assimetria excede +/- 1.96, não podemos assumir uma distribuição de Gauss e temos que ter muito cuidado para usar a média e o std.dev, pois um lado é fortemente 'sobrecarregado', enquanto o outro lado é mais ou menos 'vazio'. A assimetria maior que 0, significa que o lado direito (>média) tem menor valor do que o lado direito. Então PER é uma distribuição de Gauss, podemos assim, usar a média e o StDev. Se compararmos o desenvolvimento dos principais resultados (de acordo com OptVal), vemos que a média aumenta lentamente de 19 para 20(row 487!). O StdDev, entretanto, aumenta de ~2.0 para 5.36 (row 487). A assimetria nunca excede 0,4 se ignorar os 10 primeiros resultados é bastante positivo, o que significa que devemos adicionar um (ou dois) valores do lado esquerdo da média.

Os resultados do PRC têm de ser tratados de formas diferentes! Além dePER e LIM , os valores de PRC definem uma escala nominal, qualquer cálculo entre eles é sem sentido. Então, nós contaremos somente quantas vezes eles aparecem e calcularemos a média do RangesRaw para cada PRC 0,..,6. Lembrando que queríamos verificar até mesmo os conjuntos ridículos. Normalmente não usamos PRC=Open (1), PRC=High (2) ou o PRC=Low (3). Todavia, temos de perceber que "Open" é o valor mais frequente entre os top 50. Isto é provavelmente motivado pelo fato de que usamos apenas barras inteiras, o ADX usa a máxima e a mínima do preço da barra, que juntamente com o fechamento são "publicamente conhecidas" - um tipo de vantagem imoral como o ADX usa o conjunto. O sucesso da Máxima e da Mínima do preço? Difícil de explicar! O fato do preço EURUSD cai para 1,33 em Agosto de 2014 a 1.08 em Dezembro de 2015 pode explicar o sucesso da mínima, porém não da máxima de preço. Talvez seja um resultado das dinâmicas dos mercados mais fortes. De qualquer maneira, nós ignoramos. Se compararmos PER = Close, Typical, Median e Weighted, percebemos que não há grande diferença entre eles quando se olha para as colunas Q, R e S. entre os top 100 PRC=Typical(4), seria a melhor escolha, melhor até que PRC=High(2). Entretanto entre os top 500 PRC=Close tornou-se o melhor.

Para LIM , usamos as mesmas fórmulas do PER. Interessante observar que a "última" assimetria (de todos) é muito acima de +1.96, porém não para o top 100 (=0,38) ou para o top 500(=0.46). Então, vamos usar o 500 melhores. A média dos top 500 é de 16,65 e a StdDev é de 3,03.  Naturalmente, este LIM depende fortemente do PER: quanto menor for o PER , maior será o LIM e vice versa. É por isso que a variação do LIM corresponde com a do PER.

Então, nós escolhemos o resultado das 500 melhores de nossas três variáveis PER, PRC, e LIM , :

  • PER Avg=20.18 +/- StdDev=5.51 Skew=0.35 (-2) => (20.18-5.41-2=) 14,..,(20.18+5.52=) 26 (Step 1 => 13).
  • PRC de acordo com a linha 500, podemos decidir por apenas um fechamento (Step 0 => 1).
  • LIM Avg=16.64 +/- StdDev=3.03 Skew=0.46 (-2) => (16.64-3.03-2=) 12,..,(16.64+3.03=) 20 (Step 1 => 9)

No total, temos agora apenas 13*1*9 = 117 combinações para a negociação do EA e sua otimização.

Podemos observar mais de perto os resultados (aba: "OPT Top 500 Best PER's Average"):

Fig. 19 OPT Top 500 Best PER's Average

Podemos notar que o PER=18 é mais frequente nos top 500 e PER=22 possui a maior média. Ambos são cobertos pela nossa seleção e seu LIM também.


Modo Visual

Vamos, finalmente, verificar o PER com a melhor média dos top 500: PER=22. Ao desativar o PRC=Open,Low,High, encontramos esta configuração com uma relação de variação na ordem de 4.48 na linha 38, o fundo amarelo na imagem anterior da guia.

Nós rodamos o pseudo-EA no Modo Visual com esta configuração e aplicamos o ADX com a mesma configuração.

(Unicamente) No Modo Visual, nosso pseudo-EA coloca uma seta azul na direita-esquerda da próxima barra, onde foi detectado o mercado lateral e uma seta de cima para baixo vermelha no caso de um barra de tendência: (A partir de 30.07.2015 05:00 para Terça-feira. 04.08.2015 12:00):

Fig. 20 VisualMode Per 22

Nota-se claramente dois problemas do ADX que pode incentivá-lo a melhorar esta ideia!

  1. O ADX está atrasando, especialmente na detecção do mercado lateral, quando alguns movimentos maiores jogam fora o ADX, Ele precisa de muito tempo para se 'acalmar' novamente. Seria interessante se o mercado lateral detectasse em torno das 00:00 de 03.08.2015 e não 09:00 de 03.08.2015.
  2. Se o ADX está perto do LIM, percebemos uma espécie de efeito "whipsaw". Por exemplo, teria sido melhor se nós não pudéssemos detectar o mercado de tendência às 14:00 do dia 03.08.2015.
  3. Se as variações das máximas e mínims das barras é cada vez menor, até mesmo um par de barras "pequenas" no mesmo sentido é reconhecido como uma nova tendência. Em vez de uma nova tendência às 20:00 em 03.08.2015, seria melhor se a tendência fosse detectada mais tarde, talvez em torno das 07:00 em 04.08. 2015.08.
  4. O pseudo-EA não faz distinção entre tendência de alta ou baixa. Caberá ao usuário querer utilizar, por exemplo, DI+ e DI- do ADX ou outros indicadores.
  5. Talvez o comprimento médio dos mercados de tendência (46,76) que dura cerca de 4 dias(!) seja muito tempo. Neste caso, escolher a SwMin maior (em vez de 100) ou um SwCoeff menor (em vez de 0,01), ou ambos, lhe darão resultados que melhor atendam às suas ideias.
Estes são cinco pontos de partida para você encontrar ou para codificar seu próprio indicador ou conjunto de indicadores para uma melhor descoberta. Você pode usar o ADX como uma referência. O pseudo-EA anexo pode ser facilmente alterado quando sabe-se sua definição de um mercado de tendência ou lateral!



Conclusão

O EA de negociação que usa o ADX teria que testar 54,201 combinações apenas para otimizar esse único indicador - na esperança de que o ADX faça o que ele tem de fazer! Se EA de negociação não for tão bem sucedido quanto o esperado, seria difícil resolver o problema para começar a aprimorá-lo. Após estas otimizações, que precisam unicamente de um par de minutos para todas as suas 54,201 combinações do ADX, nós descobrimos que:

  1. O ADX é capaz de diferenciar entre os mercados latera e os de tendência, assim
  2. podemos reduzir 54.201 para 117 combinações (= 13 (PER) * 1 (PRC) * 9 (LIM)).
  3. O alcance do mercado lateral varia de 372 a 220 pontos (top 100).
  4. O alcance do mercado de tendência varia de 1.277 e 782 pontos.

Portanto, podemos reduzir as 2,168,040,000 de combinações da negociação do EA para (117*40,000=) 4,680,000. Isso é apenas 0,21%, sendo 99,7% mais rápido ou muito melhor do que a otimização genética, porque mais variações de outros parâmetros sem que sejam os do ADX serão verificados. Estas foram as configurações reduzidas para o nosso pseudo-EA:

Fig. 21 Opções do EA StratTester-Setup reduzidos

Fig. 22 Otimizações reduzidas StratTester-Setup

Além disso, conseguimos algumas informações valiosas (que depende das ideias de negociações, é claro) para os critérios de entrada em uma negociação, para sair de uma negociação e para definir ou mover stops e alvos.

O pseudo-EA e o arquivo Excel estão anexados. Explicamos o processo de forma detalhada, por que armadilhas podem aparecer. Tudo o que você precisa para começar a tentar encontrar seus próprios indicadores otimizados, antes de usá-los no seu EA de negociação. Se você está prestes a desenvolver a sua própria maneira de detectar mercados laterais e de tendência, pode-se usar isso para comparar seus resultados com o ADX, para ver se suas combinações de indicadores são melhores!

Se você quiser usar isso para encontrar um melhor indicador ou um conjunto de indicadores para os mercados lateral e de tendência, muito provavelmente, terá que ajustar os coeficientes do calcOptVal(). Por exemplo, se você quiser usar um longo período de tempo, você tem que pelo menos aumentar o SwMin. Tenha em mente que um bom OptVal permitirá que o Modo Genético encontre a melhor configuração para você fora das combinações múltiplas!! Mas você pode usar essa ideia, assim como pode fazer otimizações de indicadores totalmente diferentes. Neste caso, você pode ser forçado a reescrever completamente a função-calcOptVal().

Se você deseja trabalhar com este EA, não se esqueça:

  1. Certifique-se de que o arquivo cache em ..\tester\caches foi deletado.
  2. Se você precisa o arquivo csv no ..\tester\files\, digite um nome de arquivo para fName e delete um arquivo csv com este nome.
  3. Se você não quer um arquivo csv, deixe criado o fName do pseudo-EAs, vazio.
  4. Se você deixar o nome de um arquivo para .csv, o otimizador irá adicionar linha por linha, tornando-se maior até você estar em apuros devido ao seu tamanho!
  5. Configure o "Optimized parameter"  em "Custom" na aba "Tester" .
  6. O modo mais fácil de influenciar o OptVal e os resultados do algoritmo genético é variar os mínimos dos três coeficientes: SwMin, BrMin, RgMin.
  7. Configure o "Modelo" em "Apenas abertura de preço" , será mais rápida a otimização.
  8. Se você estiver usando datas diferentes ("Usa Data": De..Para) você tem que ajustar os coeficientes acima da função calcOptVal() dentro do pseudo-EA.
  9. Após a otimização estar concluída, escolha uma configuração na aba Resultado de Otimização" e comece de novo no "Modo Visual" para ver se a otimização cumpriu as ideias.
  10. A seta azul para direita-esquerda do início de um mercado lateral e uma seta vermelha para baixo em um mercado de tendência.
  11. Se você quer desenvolver uma alternativa melhor para o ADX, pode não precisar do arquivo csv: Apenas otimize, observe o melhor resultado no "Modo Visual", mude alguma coisa, otimize,...
  12. Por diferentes questões do mercado, seja lateral ou de tendência, você provavelmente precisará usar os arquivos csv e talvez, também precisará calcular o OptVal de maneira diferente.

Entretanto, tenha em mente que não existe nenhuma garantia para um sucesso rápido ou a obtenção de sucesso em tudo.


Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/2118

Arquivos anexados |
otimIndi_Publ.mq4 (13.4 KB)
Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1) Interfaces Gráficas VI: Os Controles Caixa de Seleção, Campo de Edição e seus Tipos Combinados (Capítulo 1)
Este artigo é o começo da sexta parte da série dedicada ao desenvolvimento da biblioteca para a criação de interfaces gráficas nos terminais MetaTrader. No primeiro capítulo, nós vamos discutir o controle caixa de seleção, o controle campo de edição e seus tipos combinados.
Usando arquivos de texto para armazenar parâmetros de entrada dos Expert Advisors, indicadores e scripts Usando arquivos de texto para armazenar parâmetros de entrada dos Expert Advisors, indicadores e scripts
O artigo descreve a aplicação de arquivos de texto para armazenar objetos dinâmicos, arrays e outras variáveis, as quais são ​​utilizadas como propriedades dos Expert Advisors, indicadores e scripts. Os arquivos servem como um complemento útil para a funcionalidade das ferramentas padrão oferecidas pelas linguagens MQL.
Expert Advisor multiplataforma: Introdução Expert Advisor multiplataforma: Introdução
Este artigo descreve um método que permite desenvolver rápida e facilmente um Expert Advisor multiplataforma. O método proposto combina as características, comuns para ambas as versões, numa classe e desenvolve a implementação para funções incompatíveis nas classes herdadas.
LifeHack para traders: um back-test bem, e quatro melhor LifeHack para traders: um back-test bem, e quatro melhor
Antes do primeiro teste único, na mente de cada trader surge a mesma pergunta: "Qual dos quatro modos devo usar?" Cada um dos modos oferecidos tem suas vantagens e características, por isso tornamos o trabalho mais fácil, que dizer, executamos todos os modos usando apenas um botão! Este artigo mostra como ver simultaneamente todos os quatro gráficos de teste com ajuda da Win API e um magic pequeno.