Expert Advisors baseado em sistemas de trading populares e alquimia da otimização de robô de trading

Nikolay Kositsin | 4 fevereiro, 2016

A maioria dos livros sobre negociações Forex geralmente oferece sistemas de negociação mais simples como material educacional. Mas hoje em dia tais sistemas existem somente como instruções gerais sem uma implementação adequada de tais estratégias de negociação na forma de Expert Advisors prontos para o uso. Então agora é impossível estimar se tais exemplos contém qualquer valor prático. Se olharmos por vários fóruns dedicados a escrever EAs, podemos concluir que quase todo escritor iniciante de EAs terá que reinventar a roda e desenvolver seu primeiro Expert Advisor com base nos sistemas de negociação mais simples, totalmente do início.

Considero o valor desse trabalho um tanto duvidoso e acho que é necessário disponibilizar a qualquer investidor iniciante EAs corretamente desenvolvidos com base nestes sistemas de negociação, assim abandonando a necessidade de começar tudo do zero. Nesse artigo gostaria de oferecer minha própria variação da solução do problema. Para construir o EA usarei os indicadores da minha própria biblioteca, que já usei no artigo Algoritmos de Média Eficiente com Lag Mínimo: Uso em Indicadores.



Esquema geral de estrutura do EA


Antes de começar a descrição principal, gostaria de chamar a sua atenção para o fato de que todos os EAs contidos no artigo foram construídos de acordo com um único esquema:



Sistemas de negociação baseados em mudança de direção de movimento

Agora tentarei explicar em detalhes a ideia do esquema da estrutura do EA oferecido no exemplo de um Expert Advisor já pronto, explicando todos os pequenos detalhes para trabalhar com ele. Nesse sistema o sinal para comprar é a mudança da direção de movimento, de queda para ascensão:

O sinal para vender aqui é a mudança de direção de movimento de ascensão para queda:


Para o cálculo de sinais de tendência usamos o valor móvel na primeira, segunda e terceira barras. Valores móveis na barra zero não são levados em consideração, isto é, o sistema trabalha somente com barras fechadas. Como uma móvel, o indicador personalizado JFATL é usado - é um filtro FATL digital simples com suavização JMA adicional. Na internet você pode muitas vezes ver declarações de que inserir uma negociação na mudança de direção FATL prometerá vários pontos de lucro, então cada leitor pode ser facilmente convencido do quanto eficiente essa estratégia é na verdade. Aqui está a versão da implementação do sistema na forma de um Expert Advisor:



Código do Expert Advisor

//+==================================================================+
//|                                                        Exp_1.mq4 |
//|                             Copyright © 2007,   Nikolay Kositsin | 
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+==================================================================+
#property copyright "Copyright © 2007, Nikolay Kositsin"
#property link "farria@mail.redcom.ru"
//---- EA INPUT PARAMETERS FOR BUY TRADES 
extern bool   Test_Up = true;//filter of trade calculations direction
extern int    Timeframe_Up = 240;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;  // smoothing depth 
extern int    Phase_Up = 100; // parameter changing in the range 
          //-100 ... +100, influences the quality of the transient process; 
extern int    IPC_Up = 0;/* Selecting prices, on which the indicator will 
be calculated (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int    STOPLOSS_Up = 50;  // stoploss
extern int    TAKEPROFIT_Up = 100; // takeprofit
extern bool   ClosePos_Up = true; // forced position closing allowed
//---- EA INOUT PARAMETERS FOR SELL TRADES 
extern bool   Test_Dn = true;//filter of trade calculations direction
extern int    Timeframe_Dn = 240;
extern double Money_Management_Dn = 0.1;
extern int    Length_Dn = 4;  // smoothing depth 
extern int    Phase_Dn = 100; // parameter changing in the range
         // -100 ... +100, influences the quality of the transient process; 
extern int    IPC_Dn = 0;/* Selecting prices, on which the indicator will 
be calculated (0-CLOSE, 1-OPEN, 2-HIGH, 3-LOW, 4-MEDIAN, 5-TYPICAL, 
6-WEIGHTED, 7-Heiken Ashi Close, 8-SIMPL, 9-TRENDFOLLOW, 10-0.5*TRENDFOLLOW, 
11-Heiken Ashi Low, 12-Heiken Ashi High, 13-Heiken Ashi Open, 
14-Heiken Ashi Close.) */
extern int   STOPLOSS_Dn = 50;  // stoploss
extern int   TAKEPROFIT_Dn = 100; // takeprofit
extern bool   ClosePos_Dn = true; // forced position closing allowed
//---- Integer variables for the minimum of counted bars
int MinBar_Up, MinBar_Dn;
//+==================================================================+
//| Custom Expert functions                                          |
//+==================================================================+
#include <Lite_EXPERT1.mqh>
//+==================================================================+
//| Custom Expert initialization function                            |
//+==================================================================+
int init()
  {
//---- Checking the correctness of Timeframe_Up variable value
   if (Timeframe_Up != 1)
    if (Timeframe_Up != 5)
     if (Timeframe_Up != 15)
      if (Timeframe_Up != 30)
       if (Timeframe_Up != 60)
        if (Timeframe_Up != 240)
         if (Timeframe_Up != 1440)
           Print(StringConcatenate("Timeframe_Up parameter cannot ",  
                                  "be equal to ", Timeframe_Up, "!!!"));
//---- Checking the correctness of Timeframe_Dn variable value 
   if (Timeframe_Dn != 1)
    if (Timeframe_Dn != 5)
     if (Timeframe_Dn != 15)
      if (Timeframe_Dn != 30)
       if (Timeframe_Dn != 60)
        if (Timeframe_Dn != 240)
         if (Timeframe_Dn != 1440)
           Print(StringConcatenate("Timeframe_Dn parameter cannot ",  
                                 "be equal to ", Timeframe_Dn, "!!!")); 
//---- Initialization of variables             
   MinBar_Up = 4 + 39 + 30;
   MinBar_Dn = 4 + 39 + 30;                                        
//---- end of initialization
   return(0);
  }
//+==================================================================+
//| Expert Advisor deinitialization function                         |
//+==================================================================+  
int deinit()
  {
//----+
   
    //---- End of EA deinitialization
    return(0);
//----+ 
  }
//+==================================================================+
//| Custom Expert iteration function                                 |
//+==================================================================+
int start()
  {
   //----+ Declaration of local variables
   int    bar;
   double Mov[3], dMov12, dMov23;
   //----+ Declaration of static variables
   static int LastBars_Up, LastBars_Dn;
   static bool BUY_Sign, BUY_Stop, SELL_Sign, SELL_Stop;
   
   //----++ CODE FOR LONG POSITIONS
   if (Test_Up) 
    {
      int IBARS_Up = iBars(NULL, Timeframe_Up);
      
      if (IBARS_Up >= MinBar_Up)
       {
         if (LastBars_Up != IBARS_Up)
          {
           //----+ Initialization of variables 
           BUY_Sign = false;
           BUY_Stop = false;
           LastBars_Up = IBARS_Up; 
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Up, 
                                "JFatl", Length_Up, Phase_Up, 
                                                   0, IPC_Up, 0, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES 
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2]; 
                                          
           if (dMov23 < 0)
              if (dMov12 > 0)
                        BUY_Sign = true;
                          
           if (dMov12 < 0)
                        BUY_Stop = true;                                           
          }
          //----+ EXECUTION OF TRADES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);
        }
     }
     
   //----++ CODE FOR SHORT POSITIONS
   if (Test_Dn) 
    {
      int IBARS_Dn = iBars(NULL, Timeframe_Dn);
      
      if (IBARS_Dn >= MinBar_Dn)
       {
         if (LastBars_Dn != IBARS_Dn)
          {
           //----+ Initialization of variables 
           SELL_Sign = false;
           SELL_Stop = false;
           LastBars_Dn = IBARS_Dn; 
           
           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM TO BUFFERS        
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Dn, 
                                "JFatl", Length_Dn, Phase_Dn, 
                                                   0, IPC_Dn, 0, bar);
           
           //----+ DEFINING SIGNALS FOR TRADES
           dMov12 = Mov[0] - Mov[1];
           dMov23 = Mov[1] - Mov[2]; 
                                           
           if (dMov23 > 0)
              if (dMov12 < 0)
                       SELL_Sign = true;
                          
           if (dMov12 > 0)
                       SELL_Stop = true;                                           
          }
          //----+ EXECUTION OF TRADES
          if (!OpenSellOrder1(SELL_Sign, 2, Money_Management_Dn, 
                                            STOPLOSS_Dn, TAKEPROFIT_Dn))
                                                                   return(-1);
          if (ClosePos_Dn)
                if (!CloseOrder1(SELL_Stop, 2))
                                        return(-1);
        }
     }
//----+ 
    
    return(0);
  }
//+------------------------------------------------------------------+

Para obter sinais de Compra e Venda dois algoritmos independentes absolutamente similares são usados; cada dos algoritmos tem seu próprio parâmetro externo para otimização. Minha própria experiência prova que usar esse tipo de abordagem ao escrever esse EA é muito mais rentável do que a variação com somente um algoritmo para detectar sinais de Compra e Venda. Os interessados na variação com um único movimento tanto para posições curtas e longas pode estudar esse algoritmo no EXP_0.mq4 EA; nós continuaremos a discutir o EA com duas médias móveis. O Expert Advisor pode abrir uma posição na direção Comprar e uma posição na direção Vender simultaneamente para um par negociado. O EA executa as negociações a partir do mercado. As negociações são fechadas pelos pedidos StopLoss e TakeProfit. Se sinais de tendências aparecem opostos às posições abertas no EA, o EA permite o fechamento forçado da negociação. O método para receber sinais para sair de negociações é análogo ao de obter sinais para entrar, mas é de caráter contrário.



Conteúdo do arquivo Lite_EXPERT1.mqh

Para o propósito de economia máxima de seu próprio trabalho, tente usar o número máximo de funções universais definidas pelo usuário ao escrever Expert Advisors. Depois monte um código de EA de diferentes partes, como é feito em diferentes fábricas de produção de equipamentos, onde cada novo produto contém o número máximo de detalhes, blocos e módulos unificados e padronizados. Essa é a razão pela qual em todos os EAs em blocos de "Execução de negociações" diversas funções definidas pelo usuário são usadas; elas estão inclusas em um código de EA pela diretiva #include:

bool OpenBuyOrder1
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder1
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

CountLastTime
(
  int lastError
)

bool CloseOrder1
( 
  bool Stop_Signal, int MagicNumber
)

int StopCorrect
( 
  string symbol, int Stop
)

bool MarginCheck
(
  string symbol, int Cmd, double& Lot
)

double GetFreeMargin()

bool DeleteOrder1
(
  bool& CloseStop, int MagicNumber
)

bool OpenBuyLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellLimitOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenSellStopOrder1
(
  bool& Order_Signal, int MagicNumber, 
       double Money_Management, int STOPLOSS, int TAKEPROFIT,
                                      int LEVEL, datetime Expiration
)

bool OpenBuyOrder2
( 
  bool BUY_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool OpenSellOrder2
( 
  bool SELL_Signal, int MagicNumber, 
             double Money_Management, int STOPLOSS, int TAKEPROFIT
)

bool Make_TreilingStop
(
  int MagicNumber, int TRAILINGSTOP

A função OpenBuyOrder1() abre posições longas quando é chamada se o valor da variável externa BUY_Signal for igual a verdadeiro e não existem posições abertas, o número de identificação (mágico) deste sendo igual ao valor da variável MagicNumber. Valores de variáveis externas STOPLOSS e TAKEPROFIT definem correspondentemente o valor de StopLoss e TakeProfit em pontos. O valor da variável Money_Management pode variar de zero a um. Essa variável aponta qual parte do depósito disponível é usado para a execução da negociação. Se o valor dessa variável for menor do que zero, a função OpenBuyOrder1() usará seu valor como o tamanho do lote! Posições curtas são abertas de modo análogo quando a função OpenSellOrder1() é chamada. Ambas as funções abrem posições independentemente da outra, mas somente um comando para execução de negociação pode ser enviado a um servidor dentro de 11 segundos. Além das execuções das negociações, as funções OpenBuyOrder1() e OpenSellOrder1() registram em um arquivo de texto a informação sobre as negociações abertas.


Se a variável Stop_Signal obter o valor real, a função CloseOrder1(), quando referida, fecha uma posição com o número mágico igual ao valor da variável MagicNumber.


A função StopCorrect() aceita como o parâmetro de parada o valor StopLoss ou TakeProfit, verifica sua correspondência com o valor minimamente aceito, se necessário o muda para o valor minimamente permitido e o retorna, levando em consideração possíveis correções.


A associação da função MarginCheck() indica a redução do tamanho do lote que é usado em uma negociação aberta até o tamanho máximo, no qual a margem livre é suficiente para abrir a negociação, no caso em que a margem livre não for suficiente com o tamanho de lote atual.


OpenBuyOrder1(), OpenSellOrder1() e CloseOrder1() são usadas dentro da função start(), enquanto as funções StopCorrect() e MarginCheck() são usadas dentro do código de OpenBuyOrder1() e OpenSellOrder1().


Em um término correto do OpenBuyOrder1(), OpenSellOrder1() e CloseOrder1() qualquer função retorna como 'verdadeira', se um erro ocorrer durante a execução das funções o valor retornado é 'falso'. Todas as três funções: OpenBuyOrder1(), OpenSellOrder1() e CloseOrder1() na execução de negócios envia os valores das suas variáveis externas BUY_Signal, SELL_Signal e Stop_Signal como 'falso'!


A função GetFreeMargin() retorna o tamanho de margem livre da conta atual, permitindo o lucro e perdas que podem ser usadas para abertura de posição. Essa função é usada para calcular o tamanho do lote


A função CountLastTime() torna a inicialização da variável LastTime considerando um erro que ocorreu durante a execução da negociação. A função deve ser chamada imediatamente após a execução de uma negociação, por exemplo:

 //----+ Open Buy position    
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculating a pause between trade operations
  CountLastTime(GetLastError());

Além do mais, as funções enumeradas, o arquivo Lite_EXPERT.mqh contém mais quatro funções para colocar pedidos pendentes e uma função para deletar pedidos pendentes: OpenBuyLimitOrder(), OpenBuyStopOrder1(), OpenSellLimitOrder1(), OpenSellStopOrder1(), DeleteOrder1(). A atribuição de variáveis externas destas funções é absolutamente análoga e compreensível a partir de seus nomes. As novas variáveis LEVEL e Expiration são necessárias para enviar a uma função a distância em pontos do preço atual, no qual os pedidos pendentes são colocados e a data de expiração do pedido pendente de acordo.


Além de todas as funções discutidas, o arquivo contém mais duas funções: OpenBuylOrder2() e OpenSellOrder2(), que são totalmente análogas de OpenBuyOrder1() e OpenSellOrder1(), exceto por um detalhe. Essas funções primeiro abrem posições sem pedidos de StopLoss e TakeProfit e após isso modificam posições já abertas definindo StopLoss e TakeProfit. Essas funções são necessárias para operação nos EAs desenvolvidos para serem usados com corretores que não permitem que um cliente use Stop Loss e Take Profit ao abrir uma posição no mercado devido ao tipo de execução "Observação do Mercado". Em tais agências corretoras os pedidos de Stop Loss e Take Profit são colocados por meio da modificação de uma posição aberta.


E a última função inclusa no arquivo é Make_TreilingStop(). A função executa uma parada móvel padrão.


Além das funções, Lite_EXPERT.mqh contém uma variável completa LastTime, que é declarada em um nível global porque é usada em todas as funções para abrir pedidos.


Na minha opinião esse conjunto de funções é muito conveniente para usar na prática para escrever EAs, salvará muito tempo para escritores de EA iniciantes eliminando a necessidade de escrever esse código. Como um exemplo você pode tomar qualquer função do conjunto de funções oferecidas:

//+==================================================================+
//| OpenBuyOrder1()                                                  |
//+==================================================================+
bool OpenBuyOrder1
        (bool& BUY_Signal, int MagicNumber, 
                double Money_Management, int STOPLOSS, int TAKEPROFIT)
{
//----+
  if (!BUY_Signal)
           return(true); 
  //---- Checking the expiration of minimal time interval 
                                    //between two trade operations         
  if (TimeCurrent() < LastTime)
                          return(true); 
  int total = OrdersTotal();
  //---- Checking the presence of an opened position 
          //with the magic number equal to Value of MagicNumber variable
  for(int ttt = total - 1; ttt >= 0; ttt--)     
      if (OrderSelect(ttt, SELECT_BY_POS, MODE_TRADES))
                      if (OrderMagicNumber() == MagicNumber)
                                                      return(true); 
  string OrderPrice, Symb = Symbol(); 
  int    ticket, StLOSS, TkPROFIT;
  double LOTSTEP, MINLOT, MAXLOT, MARGINREQUIRED;
  double FreeMargin, LotVel, Lot, ask, Stoploss, TakeProfit;                                                 
                                                      
  //----+ calculating lot size for position opening
  if (Money_Management > 0)
    {        
      MARGINREQUIRED = MarketInfo(Symb, MODE_MARGINREQUIRED);
      if (MARGINREQUIRED == 0.0)
                    return(false);
                    
      LotVel = GetFreeMargin()
               * Money_Management / MARGINREQUIRED;         
    }
  else 
    LotVel = MathAbs(Money_Management);
  //----  
  LOTSTEP = MarketInfo(Symb, MODE_LOTSTEP);
  if (LOTSTEP <= 0)
              return(false);  
  //---- fixing lot size for the nearest standard value
  Lot = LOTSTEP * MathFloor(LotVel / LOTSTEP);  
  
  //----+ checking lot for minimally accepted value
  MINLOT = MarketInfo(Symb, MODE_MINLOT);
  if (MINLOT < 0)
         return(false);
  if (Lot < MINLOT)
          return(true);
          
  //----+ checking lot for maximally accepted value
  MAXLOT = MarketInfo(Symb, MODE_MAXLOT);
  if (MAXLOT < 0)
         return(false);
  if (Lot > MAXLOT)
          Lot = MAXLOT;
          
  //----+ checking if free margin is enough for lot size 
  if (!MarginCheck(Symb, OP_BUY, Lot))
                               return(false);
  if (Lot < MINLOT)
          return(true);
  //----
  ask = NormalizeDouble(Ask, Digits);
  if (ask == 0.0)
          return(false);
  //----             
  StLOSS = StopCorrect(Symb, STOPLOSS);
  if (StLOSS < 0)
          return(false);   
  //----
  Stoploss = NormalizeDouble(ask - StLOSS * Point, Digits);
  if (Stoploss < 0)
         return(false);
  //----       
  TkPROFIT = StopCorrect(Symb, TAKEPROFIT);
  if (TkPROFIT < 0)
          return(false);  
  //----               
  TakeProfit = NormalizeDouble(ask + TkPROFIT * Point, Digits);
  if (TakeProfit < 0)
         return(false);
  
  Print(StringConcatenate
         ("Open for ", Symb,
            " a Buy position with the magic number ", MagicNumber));
            
  //----+ Open Buy position
  ticket = OrderSend(Symb, OP_BUY, Lot, ask, 3, 
            Stoploss, TakeProfit, NULL, MagicNumber, 0, Lime);
  
  //---- Calculating pause between trade operations
  CountLastTime(GetLastError());
  //----
  if(ticket > 0)
   {
     if (OrderSelect(ticket, SELECT_BY_TICKET))
       {
         BUY_Signal = false;
         OpderPrice = DoubleToStr(OrderOpenPrice(), Digits);  
         Print(StringConcatenate(Symb, " BUY order with the ticket No",
                ticket, " and magic number ", OrderMagicNumber(), 
                                         " opened with the price ",OpderPrice));
         return(true);
       }
     else
       {
         Print(StringConcatenate("Failed to open ", Symb, 
            " BUY order with the magic number ", MagicNumber, "!!!"));
         return(true);
       }
    }
  else
    {
      Print(StringConcatenate("Failed to open ", Symb, 
           " BUY order with the magic number ", MagicNumber, "!!!"));
      return(true);
    }
  //---- 
  return(true);
//----+
}

Escrever tal código sem erros evidentes e não evidentes é muito difícil e um longo caminho para um usuário iniciante de MQL4. Usar tal código universal pronto (escrito por um especialista) em um Expert Advisor próprio é muito fácil:


          //----+ EXECUTION OF TRADES
          if (!OpenBuyOrder1(BUY_Sign, 1, Money_Management_Up, 
                                          STOPLOSS_Up, TAKEPROFIT_Up))
                                                                 return(-1);
          if (ClosePos_Up)
                if (!CloseOrder1(BUY_Stop, 1))
                                        return(-1);

Algumas poucas linhas de chamadas de função universais definidas pelo usuário - e o código está pronto! Tudo que você precisa é entender uma vez como as chamadas de função são escritas. O que é mais importante - o código do EA se torna mais simples e fácil de entender, devido a isso você pode implementar qualquer estratégia de negociação em seus próprios EAs. No EA Exp_1.mq4 as funções para abrir pedidos pendentes e paradas móveis não são usados, então durante a compilação do EA o MetaEditor mostrará o aviso sobre a exclusão destas funções do EA:



Explicações adicionais sobre o código do EA


Agora podemos começar a discutir o código EA restante. O código do EA consiste de dois algoritmos quase idênticos, então para entender os detalhes adequadamente é suficiente analisar por exemplo a parte do EA que abre as posições longas. O código do EA já contém comentários explicando o significado de fragmentos de códigos separados. Então vamos analisar os detalhes que não são comentados. No bloco de inicialização a variável MinBar_Up é iniciada:

//---- Initialization of variables             
   MinBar_Up = 4 + 39 + 30;

O propósito dessa variável é armazenar na memória do EA a quantidade mínima de barras; com menos barras a operação do EA em direção longa é impossível. Esse valor é definido do algoritmo do indicador personalizado JFATL.mq4. Para o cálculo de somente um valor do filtro digital FATL, 39 barras de tabelas são necessárias. Para obter o JFATL suavizado pelo algoritmo JMA, pelo menos 30 valores FATL são necessários e o algoritmo EA de detecção de sinais para negócios usa as três últimas barras menos uma da tabela, mais a quarta - a barra zero. Ao verificar se o número de barras é suficiente para cálculos posteriores, ele se parece com isso:

if (IBARS_Up >= MinBar_Up)

A verificação

if (LastBars_Up != IBARS_Up)

é necessária para eliminar o recálculo dos sinais do EA para entrar no mercado em cada tick; o EA deve fazer isso somente quando a barra mudar, o que irá salvar suficientemente os recursos do computador e o tempo de otimização do EA. Essa é a razão pela qual a variável LastBars_Up é declarada como uma variável estática - para lembrar o número de barras no tick anterior da função int start(). A inicialização de BUY_Sign e BUY_Stop para entrar e sair do mercado é realizada somente uma vez na mudança da barra; eles devem manter o valor até que a negociação seja executada ou fechada, ou até que mais uma mudança de barras aconteça. É por isso que essas variáveis são declaradas como estáticas. Suponho que outros detalhes do EA estão bem claros e podem ser entendidos a partir do código.



Substituindo as médias móveis no Expert Advisor

Agora eu gostaria de falar sobre a possibilidade de modificação do EA para usar outro movimento. Como um exemplo, vamos usar o indicador personalizado J2JMA.mq4 da minha biblioteca, sua chamada será assim:

           //----+ CALCULATING INDICATOR VALUES AND UPLOADING THEM INTO BUFFERS
           for(bar = 1; bar <= 3; bar++)
                     Mov[bar - 1]=                  
                         iCustom(NULL, Timeframe_Up, 
                                "J2JMA", Length1_Up, Length2_Up,
                                             Phase1_Up, Phase2_Up,  
                                                  0, IPC_Up, 0, bar);

A tarefa consiste em mudar um pouco o bloco dos parâmetros externos do EA (novamente somente a metade do algoritmo é descrita nesse exemplo):


//---- EA INPUT PARAMETERS FOR BUY TRADES
extern bool   Test_Up = true;//filter of trade calculations direction
extern int    Timeframe_Up=240;
extern double Money_Management_Up = 0.1;
extern int    Length1_Up = 4;  // depth of the first smoothing
extern int    Phase1_Up = 100; // parameter of the first smoothing,
       //changing in the range -100 ... +100, influences the quality 
       //of the transient process of averaging;
extern int    Length2_Up = 4;  // depth of the second smoothing
extern int    Phase2_Up = 100; // parameter of the second smoothing,
       //changing in the range -100 ... +100, influences the quality 
       //of the transient process of averaging;

No bloco de inicialização o valor da variável deve ser alterado:


//---- Initialization of variables
   MinBar_Up = 4 + 30 + 30;

Agora temos duas suavizações JMA consecutivas, cada uma precisa de pelo menos 30 barras e 4 barras para o algoritmo de cálculo do EA. Após isso no bloco para obter valores de fonte de ganho a referência do indicador personalizado deve ser alterada para aquela colocada no início do parágrafo. Para a segunda parte do algoritmo, tudo é feito da mesma forma. Assim, podemos não somente usar médias móveis, mas também osciladores que podem muitas vezes serem úteis. O código do EA preparado com base no J2JMA está incluso no arquivo EXP_2.mqh.



Otimização do EA

Agora sobre o exemplo do EA fornecido, vamos discutir alguns detalhes de otimização de todos os EAs que serão inclusos nesses artigos. Como mencionado anteriormente, existem dois algoritmos independentes no EA - para trabalhar com posições longas e curtas. Naturalmente, é mais conveniente e rápido otimizar o EA somente para a direção Comprar ou Vender individualmente. Para esse propósito existem duas variáveis externas do EA - Test_Up e Test_Dn, adequadamente.

Associando o valor 'falso' a uma destas variáveis lógicas excluímos todos os cálculos nessa direção e como resultado a otimização leva menos tempo. Após a otimização dos parâmetros do EA em uma direção, podemos mudar os valores das variáveis Test_Up e Test_Dn nas direções opostas e otimizar o EA na direção oposta. E somente após isso associar o valor 'verdadeiro' a ambas as variáveis e testar o resultado. O processo de otimização e testes é explicado muito bem no artigo Testes de Expert Advisors no Terminal do Cliente do MetaTrader 4: Um Olhar para Fora, a otimização para esse EA é realizada conforme é descrita ali. Inicie o Testador de Estratégia, envie o EA, selecione um par de negociação, período do par, método de otimização e período de tempo para a otimização:

após isso, selecione "Propriedades do Expert" no testador e mova até "Testes";


Aqui definimos o tamanho do depósito, selecionamos uma direção (longa ou curta) para otimização e selecionamos o algoritmo genético de otimização. Após isso, vá para a aba "Entradas":



Aqui associamos 'verdadeiro' a uma das variáveis externas Test_Up e Test_Dn e 'falso' para a segunda. Então associe às variáveis Timeframe_Up e Timeframe_Dn valores de períodos de tabelas, nas quais a otimização será conduzida, em Money_Management_Up e Money_Management_Dn defina a parte do depósito usada para executar negociações de Compra e Venda adequadamente.
Para as variáveis externas restantes defina os limites de alteração durante a otimização. Após isso sinalize as variáveis externas otimizadas. Feche a aba clicando em "Ok" e inicie a otimização clicando em "Iniciar" no Testador. Após a otimização acabar




mova para a aba "Resultados" no testador:



e envie o resultado de otimização satisfatório no Testador de Estratégias. Após isso conduza o mesmo procedimento no lado oposto. Como um resultado pegamos um EA com parâmetros já enviados, que é rentável no período de tempo da otimização. Note que a aba de propriedades do EA no MetaTrader 4 é diferente da mostrada acima:



Não é muito conveniente usar a janela desta forma, a forma maximizada é preferencial nestes casos. O terminal do cliente não permite que seja maximizada a janela, então um arquivo adicional para maximização foi criado (OpenExp.exe). Esse arquivo maximiza a janela de propriedades dos EAs que possuem os nomes de arquivo começando com Exp_ c (sensível a maiúsculas e minúsculas). Para usar o arquivo ele deve ser iniciado a partir de alguma pasta, e depois disso o módulo do programa esperará o aparecimento da janela de propriedades do EA e mudará o tamanho; em tal momento não é recomendado mover o mouse.


Mas qualquer escritor iniciante de EA para usar os testes poderá ter muitas questões sobre vários detalhes de testes. Primeiramente, vamos decidir qual período da tabela deverá ser usado para testes e otimização em nosso caso. Na minha opinião, você dificilmente irá ouvir uma resposta única. É considerado que, quanto maior o período da tabela da operação do EA, mais estável serão as regularidades visíveis usadas no sistema de negociação. É como nas negociações ao vivo. Mas a longo prazo, o mais importante é o resultado que pode ser obtido somente durante uma análise séria e comparação dos diferentes testes e variantes de otimização. É o mesmo com a segurança escolhida para otimização. Cada segurança tem sua característica particular. Agora falemos sobre modelagem. A experiência me mostra que EAs recebendo sinais para executar negócios no momento da primeira barra sendo alterada são muito bem otimizados quando modelados em pontos de controle sem qualquer ganho suficiente ou perdas quantitativas durante as otimizações. Naturalmente, tais declarações devem ser verificadas individualmente por experiência. Estou convencido de que é inútil otimizar tal EA com a modelagem de todos os ticks. Agora vamos falar sobre mais um parâmetro importante - o período de tempo para o qual a otimização é conduzida:



Valores diferentes são possíveis aqui, dependendo do propósito da otimização do EA. O que é importante aqui é que o período não pode exceder os dados históricos disponíveis, de outro modo você poderia ter o seguinte:

O limite inferior de todos os dados históricos disponível nos arquivos de cotações é o seguinte:




Naturalmente erros do tipo a seguir podem ocorrer na operação do Expert Advisor e indicadores inclusos nele durante a otimização e testes:

Estes erros não estão conectados com o EA em si! É somente o fato de que o período de otimização deve ser escolhido do que estiver disponível, não do que é desejado!

Agora darei algumas explicações sobre a aba "Entradas" de um Expert Advisor. Novamente, vamos analisar somente parte deles - por posições longas. Já mencionei Test_Up. O significado do parâmetro Timeframe_Up é claro. Money_Management_Up também já foi descrito anteriormente. Agora vamos analisar o parâmetro Length_Up. O significado desse parâmetro é análogo ao do parâmetro Period (período) de médias simples móveis. Esse parâmetro pode ter valores de um até o infinito. Não faz sentido na configuração do parâmetro superior mais do que 150, na maioria dos casos é mais fácil mover para um período de tempo maior. Durante a otimização desse parâmetro note que quanto maior é o parâmetro, tendências mais estáveis e a longo prazo são detectadas pelo indicador JFATL. Assim neste sistema de negociação temos uma dependência não evidente entre o parâmetro Length_Up, STOPLOSS_Up e TAKEPROFIT_Up. Logicamente, parar perdas e obter lucros diretamente dependem de Length_Up. Claro, podemos colocar pedidos independentemente de Length_Up, mas em tais casos a rentabilidade de um sistema de negociação será definida não pelas propriedades do sistema, mas pela situação atual do mercado, que não é definida neste sistema de negociação! O significado dessa afirmação pode ser entendido no exemplo a seguir. Suponha que conseguimos obter resultados muito bons dos testes do EA durante a otimização desse EA com EUR/USD com os parâmetros a seguir:

extern bool   Test_Up = true;
extern int    Timeframe_Up = 1;
extern double Money_Management_Up = 0.1;
extern int    Length_Up = 4;
extern int    Phase_Up = 100;
extern int    IPC_Up = 0;
extern int    STOPLOSS_Up = 100;
extern int    TAKEPROFIT_Up = 200;
extern bool   ClosePos_Up = false;

O fato é que, JFATL com Length_Up igual a quatro é um indicador de tendências muito rápidas; isso combinado com a tabela de minutos, na qual o EA opera, dá a tal sistema a possibilidade de consertar a escala de preço mudando mesmo em dez a quinze pontos, é por isso que o resultado de testes excepcional com estes grandes valores de stop loss e take profit indica somente o fato de que durante a otimização o mercado passou por uma forte tendência, que não é detectada no sistema em si. Então, deve ser entendido que após enviar estes parâmetros o EA dificilmente mostrará tais bons resultados. Entretanto, se você puder detectar a presença de uma forte tendência no mercado usando algumas outras ferramentas, então o uso de tais parâmetros pode ser justificado.


A faixa de mudança dos valores variáveis Phase_Up é de -100 a +100. Quando os valores são iguais a -100, processos transientes de JFATL se movendo são de mínima importância, mas minha experiência mostra que os melhores resultados de otimização são obtidos quando o valor é +100. A variável IPC_Up define quais preços serão usados para processamento posterior do algoritmo JFATL. Usar a variável ClosePos_Up permite o fechamento forçado da posição se uma tendência contra uma posição aberta for iniciada. Deve ser levado em consideração que se obter lucros e parar perdas são colocadas muito distantes do mercado, todas as posições serão fechadas mediante sinais móveis e TP e SL não influenciarão as negociações se o valor de ClosePos_Up for igual a 'verdadeiro'!



Conclusão


Aqui preciso terminar a explicação, o tópico de otimização discutido no fim do artigo é muito extenso. Então o que restou será descrito no próximo artigo. O propósito principal do primeiro artigo é mostrar a um leitor meu próprio método para escrever Expert Advisors e oferecer uma maneira fácil e universal para construir um código de EA com muito pouca experiência ao escrever EAs. Essa tarefa é resolvida no artigo, eu suponho. No próximo artigo explicarei alguns recursos da análise da otimização dos resultados e oferecerei a você um para o sistema de negociação. E quanto aos EAs que estão inclusos nesse artigo como exemplos, note que eles são muito simples e dificilmente podem ser usados para um sistema de negociação com valor real totalmente automatizado. Entretanto, eles podem ser úteis para a automação de operações de negócios separadas como ferramentas de trabalho quando um investidor deixa seu terminal de cliente em alguns momentos do dia.