English Русский 中文 Español Deutsch 日本語
Construção de um Expert Advisor utilizando módulos independentes

Construção de um Expert Advisor utilizando módulos independentes

MetaTrader 5Experts | 20 janeiro 2020, 08:15
2 211 0
Andrei Novichkov
Andrei Novichkov

Introdução

Ao desenvolver indicadores, Expert Advisors e scripts, os desenvolvedores geralmente precisam criar vários trechos de código, que não estão diretamente relacionados à estratégia de negociação. Por exemplo, esse código pode estar relacionado ao horário de operações do Expert Advisor: diário, semanal ou mensal. Como resultado, nós criaremos um projeto independente, que pode interagir com a lógica de negociação e outros componentes usando uma interface simples. Com o mínimo de esforço, este módulo de horário pode ser usado em Expert Advisors e indicadores personalizados, sem realizar grandes modificações. Neste artigo, nós tentaremos aplicar uma abordagem de sistemas modularizados à criação do Expert Advisor. Nós também consideraremos uma possibilidade interessante, que surgirá como resultado de nosso trabalho. O artigo é destinado à desenvolvedores iniciantes.

Qual é a prática usual?

Vamos tentar entender a aparência desse Expert Advisor e quais partes/componentes/módulos ele pode conter. Para onde levamos esses componentes? A resposta é simples e clara — no processo de desenvolvimento de aplicativos, o programador precisa criar vários componentes, que geralmente têm a mesma funcionalidade ou são muito semelhantes.

É óbvio que não há necessidade de implementar o mesmo recurso, por exemplo, uma função de Stop Móvel, sempre do zero. Em geral, o stop móvel pode executar funções semelhantes e ter parâmetros de entrada semelhantes para diferentes Expert Advisors. Assim, o programador pode criar o código da função de stop móvel uma única vez e depois inseri-lo nos EAs com o mínimo de esforço. O mesmo se aplica a muitos outros componentes, incluindo o horário de negociação, vários filtros de notícias e módulos que contêm as funções de negociação, etc.

Assim, nós temos um tipo de conjunto de funções, com base no qual nós podemos construir um Expert Advisor através de módulos/blocos separados. Os módulos trocam informações entre si e com o "kernel" do Expert Advisor, que é a "estratégia" que toma decisões. Vamos exibir as possíveis interações entre os módulos separados:



O esquema resultante é bastante confuso. Embora ele mostre apenas a interação de três módulos e dois manipuladores do EA: OnStart e OnTick. Em um Expert Advisor mais complexo, as ligações internas seriam ainda mais complicadas. É difícil gerenciar esse Expert Advisor. Além disso, se algum dos módulos precisar ser excluído ou um adicional precisar ser adicionado, isso causaria consideráveis dificuldades. Além disso, a depuração inicial e a solução de problemas não seriam fáceis. Uma das razões para tais dificuldades está relacionada ao fato de que os vínculos são projetados sem uma abordagem sistemática adequada. Devemos proibir que os módulos se comuniquem entre si e com os manipuladores do EA, sempre que necessário, e uma certa ordem aparecerá:

  • Todos os módulos são criados no OnInit.
  • A lógica do EA está contida na OnTick.
  • Os módulos trocam informações apenas com a OnTick.
  • Se necessário, os módulos são excluídos na OnDeinit.

Uma solução tão simples pode ter um efeito positivo rápido. Módulos separados são mais fáceis de conectar/desconectar, depurar e modificar. A lógica da OnTick se tornará mais acessível para manutenção e aprimoramento se as ligações forem implementadas em um manipulador em vez de serem adicionadas em locais diferentes no código do EA:


Essa pequena alteração de projeto fornece uma estrutura de EA mais clara, que se torna mais intuitiva. A nova estrutura se assemelha ao resultado da aplicação do padrão "Observador", embora a própria estrutura seja diferente do padrão. Vamos ver como nós podemos melhorar ainda mais o projeto.

EA para os Experimentos

Nós precisamos de um Expert Advisor simples para verificar nossas ideias. Nós não precisamos de um EA muito complicado, porque nosso objetivo agora é apenas demonstrar os recursos. O EA abrirá uma ordem de venda se a vela anterior for baixista. O Expert Advisor será projetado usando uma estrutura modular. O primeiro módulo implementa as funções de negociação:

class CTradeMod {
public:
   double dBaseLot;
   double dProfit;
   double dStop;
   long   lMagic;
   void   Sell();
   void   Buy();
};

void CTradeMod::Sell()
{
  CTrade Trade;
  Trade.SetExpertMagicNumber(lMagic);
  double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK);
  Trade.Sell(dBaseLot,NULL,0,ask + dStop,ask - dProfit);
} 

void CTradeMod::Buy()
{
  CTrade Trade;
  Trade.SetExpertMagicNumber(lMagic);
  double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID);
  Trade.Buy(dBaseLot,NULL,0,bid - dStop,bid + dProfit);
} 

O módulo é implementado como uma classe que possui os campos e métodos abertos. Por enquanto, nós não precisamos de um método Buy() implementado no módulo, mas ele será necessário mais tarde. O valor dos campos separados deve ser claro; eles são usados para o volume, os níveis de negociação e o número mágico. Como usar o módulo: criamos ele e chamamos o método Sell() quando surgir um sinal de entrada.

Outro módulo será incluído no EA:

class CParam {
public:
   double dStopLevel;      
   double dFreezeLevel;   
    CParam() {
      new_day();      
    }//EA_PARAM()
    void new_day() {
      dStopLevel   = SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL) * Point();
      dFreezeLevel = SymbolInfoInteger(Symbol(),SYMBOL_TRADE_FREEZE_LEVEL) * Point();
    }//void new_day()
};

Vamos considerar este módulo em mais detalhes. Este é um módulo auxiliar que contém vários parâmetros usados por outros módulos e manipuladores do EA. Você pode ter encontrado um código como este:
...
input int MaxSpread = 100;
...
OnTick()
 {
   if(ask - bid > MaxSpread * Point() ) return;
....
 }

Obviamente, este fragmento é ineficaz. Se adicionarmos todos os parâmetros de entrada (e outros) que precisam ser atualizados ou convertidos em um módulo separado (aqui lidamos com MaxSpread*Point()), mantemos o espaço global limpo e podemos controlar com eficiência o seu estado, como é feito com os valores de stops_level e freeze_level no módulo CParam acima.

Provavelmente, uma solução melhor seria fornecer getters especiais em vez de abrir os campos do módulo. Aqui a solução acima é usada para simplificar o código. Para um projeto real, é melhor usar um getter.

Além disso, nós poderíamos abrir uma exceção para o módulo CParam e permitir o acesso a esse módulo não apenas pelo manipulador OnTick(), mas também por todos os outros módulos e manipuladores.

Aqui está o bloco de entradas e os manipuladores do EA:

input double dlot   = 0.01;
input int    profit = 50;
input int    stop   = 50;
input long   Magic  = 123456;

CParam par;
CTradeMod trade;

int OnInit()
  {  
   trade.dBaseLot = dlot;
   trade.dProfit  = profit * _Point;
   trade.dStop    = stop   * _Point;
   trade.lMagic   = Magic;
   
   return (INIT_SUCCEEDED);
  }
  
void OnDeinit(const int reason)
  {
  }

void OnTick()
  {
   int total = PositionsTotal(); 
   ulong ticket, tsell = 0;
   ENUM_POSITION_TYPE type;
   double l, p;
   for (int i = total - 1; i >= 0; i--) {
      if ( (ticket = PositionGetTicket(i)) != 0) {
         if ( PositionGetString(POSITION_SYMBOL) == _Symbol) {
            if (PositionGetInteger(POSITION_MAGIC) == Magic) {
               type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
               l    = PositionGetDouble(POSITION_VOLUME);
               p    = PositionGetDouble(POSITION_PRICE_OPEN);
               switch(type) {
                  case POSITION_TYPE_SELL:
                     tsell = ticket;    
                     break;
               }//switch(type)
            }//if (PositionGetInteger(POSITION_MAGIC) == lmagic)
         }//if (PositionGetString(POSITION_SYMBOL) == symbol)
      }//if ( (ticket = PositionGetTicket(i)) != 0)
   }//for (int i = total - 1; i >= 0; i--)
   if (tsell == 0) 
      {
        double o = iOpen(NULL,PERIOD_CURRENT,1); 
        double c = iClose(NULL,PERIOD_CURRENT,1); 
        if (c < o) 
          {
            trade.Sell();
          }   
      }                         
  }

O EA inicializa os módulos no manipulador OnInit() e os acessa apenas no manipulador OnTick(). Na OnTick(), o EA percorre as posições abertas para verificar se a posição solicitada já foi aberta. Se a posição ainda não foi aberta, o EA abre uma, desde que haja um sinal.

Observe que o manipulador OnDeinit(const int reason) está vazio no momento. Os módulos são criados para que não precisem ser explicitamente excluídos. Além disso, o CParam ainda não foi utilizado, porque não são realizadas verificações de abertura de posição. Se essas verificações forem executadas, o módulo CTradeMod poderá precisar de acesso ao módulo CParam e o desenvolvedor precisará fazer uma exceção mencionada anteriormente e permitir acesso ao CParam. No entanto, isso não é necessário no nosso caso.

Vamos ver esse momento com mais detalhes. O módulo CTradeMod pode precisar de dados do CParam para verificar o stop loss e obter níveis de lucro, bem como o volume da posição. Mas a mesma verificação pode ser realizada no ponto de tomada de decisão: se os níveis e o volume não atenderem aos requisitos, não abrimos uma posição. Assim, a verificação é movida para o manipulador OnTick(). Como no nosso exemplo, os valores do volume e dos níveis de negociação são especificados nos parâmetros de entrada, a verificação pode ser realizada uma única vez, no manipulador OnInit(). Se a verificação não for bem-sucedida, a inicialização de todo o EA deve ser encerrada com um erro. Assim, os módulos CTradeMod e CParam podem atuar de forma independente. Isso é relevante para a maioria dos Expert Advisors: módulos independentes operam pelo manipulador OnTick() e não sabem nada um do outro. No entanto, essa condição não pode ser observada em alguns casos. Nós vamos considerá-los mais tarde.

Implementando Melhorias

O primeiro problema a ser resolvido é a enorme quantidade de iterações de posições no manipulador OnTick(). Este trecho de código é necessário se o desenvolvedor desejar:

  1. evitar mais abertura de posição se o sinal de entrada permanecer ativo,
  2. permitir que o EA detecte ordens já colocadas após uma pausa ou interrupção na operação,
  3. permitir que o EA colete estatísticas atuais, como o volume total de posições em aberto ou o seu rebaixamento máximo.

Essa iteração será ainda maior nos Expert Advisors que usam grades e técnicas de preço médio. Além disso, ele é necessário se o EA utilizar os níveis de negociação virtual. Portanto, é melhor criar um módulo separado com base nesse trecho de código. No caso mais simples, o módulo detectará posições com o número mágico específico e notificará o EA dessas posições. Em um caso mais complicado, o módulo pode ser implementado como um kernel, incluindo módulos mais simples, como módulos de coleta de registos ou estatísticas. Nesse caso, o EA terá uma estrutura de árvore com os manipuladores da OnInit() e OnTick() na base da árvore. Aqui está a aparência de um módulo:

class CSnap {
public:
           void CSnap()  {
               m_lmagic = -1; 
               m_symbol = Symbol();               
           }
   virtual void  ~CSnap() {}   
           bool   CreateSnap();
           long   m_lmagic;
           string m_symbol;
};

Todos os campos estão novamente abertos. Na verdade, o código possui dois campos: para o número mágico e para o nome do símbolo no qual o EA está sendo executado. Se necessário, os valores para esses campos podem ser definidos no manipulador OnInit(). A parte principal do trabalho é executada pelo método CreateSnap():
bool CSnap::CreateSnap() {
   int total = PositionsTotal(); 
   ulong ticket;
   ENUM_POSITION_TYPE type;
   double l, p;
   for (int i = total - 1; i >= 0; i--) {
      if ( (ticket = PositionGetTicket(i)) != 0) {
         if (StringLen(m_symbol) == 0 || PositionGetString(POSITION_SYMBOL) == m_symbol) {
            if (m_lmagic < 0 || PositionGetInteger(POSITION_MAGIC) == m_lmagic) {
               type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
               l    = PositionGetDouble(POSITION_VOLUME);
               p    = PositionGetDouble(POSITION_PRICE_OPEN);
               switch(type) {
                  case POSITION_TYPE_BUY:
// ???????????????????????????????????????
                     break;
                  case POSITION_TYPE_SELL:
// ???????????????????????????????????????
                     break;
               }//switch(type)
            }//if (lmagic < 0 || PositionGetInteger(POSITION_MAGIC) == lmagic)
         }//if (StringLen(symbol) == 0 || PositionGetString(POSITION_SYMBOL) == symbol)
      }//if ( (ticket = PositionGetTicket(i)) != 0)
   }//for (int i = total - 1; i >= 0; i--)
   return true;
}

O código é simples, mas ele tem alguns problemas. Como e onde o módulo deve passar as informações obtidas? O que deve ser escrito nas linhas com os pontos de interrogação no último fragmento de código? Isso pode parecer simples. Chamamos o método CreateSnap() no manipulador OnTick(): esse método executa as tarefas necessárias e salva os resultados nos campos da classe CSnap. Em seguida, o manipulador verifica os campos e tira certas conclusões.

Bem, esta solução pode ser implementada no caso mais simples. O que faríamos se nós precisarmos de tratamento separado de cada parâmetro de posição, por exemplo, para calcular um valor médio ponderado? Nesse caso, é necessária uma abordagem mais universal, na qual os dados são encaminhados para algum outro objeto para processamento adicional. Para esse fim, um ponteiro especial para esse objeto deve ser fornecido no CSnap:

CStrategy* m_strategy;
O método para atribuir um valor a este arquivo:
     bool SetStrategy(CStrategy* strategy) {
              if(CheckPointer(strategy) == POINTER_INVALID) return false;
                 m_strategy = strategy;
                 return true;
              }//bool SetStrategy(CStrategy* strategy)   

O nome do objeto CStrategy foi selecionado porque as decisões de entrada e outras decisões podem ser tomadas nesse objeto. Portanto, o objeto pode definir a estratégia de todo o EA.

Agora, o 'switch' no método CreateSnap() será o seguinte:

               switch(type) {
                  case POSITION_TYPE_BUY:
                     m_strategy.OnBuyFind(ticket, p, l);
                     break;
                  case POSITION_TYPE_SELL:
                     m_strategy.OnSellFind(ticket, p, l);
                     break;
               }//switch(type

Se necessário, o código pode ser facilmente complementado com opções para ordens pendentes e as chamadas dos métodos correspondentes. Além disso, o método pode ser facilmente modificado para permitir a coleta de mais dados. O CreateSnap() pode ser implementado como um método virtual para fornecer uma potencial herança da classe CSnap. Mas isso não é necessário no nosso caso e, portanto, nós usaremos uma versão de código mais simples.

Além disso, um ponteiro para esse objeto (nós o implementamos como CStrategy* pointer) pode ser útil não apenas para o módulo atual. A possível conexão de um módulo com a lógica de operação do EA pode ser necessária para qualquer módulo que execute cálculos ativos. Portanto, vamos fornecer um campo especial e um método de inicialização na classe base:

class CModule {
   public:
      CModule() {m_strategy = NULL;}
     ~CModule() {}
     virtual bool SetStrategy(CStrategy* strategy) {
                     if(CheckPointer(strategy) == POINTER_INVALID) return false;
                     m_strategy = strategy;
                     return true;
                  }//bool SetStrategy(CStrategy* strategy)   
   protected:   
      CStrategy* m_strategy;  
};


Além disso, nós criaremos os módulos herdados da CModule. Em alguns casos, nós podemos ter código em excesso. Mas essa desvantagem será compensada por esses módulos, nos quais esse ponteiro pode realmente ser necessário. Se algum dos módulos não precisar desse ponteiro, simplesmente não chame o método SetStrategy(...) nesse módulo. A classe do módulo base também pode ser útil para colocar os campos e métodos adicionais ainda desconhecidos para nós. Por exemplo, o seguinte método (que não é implementado em nosso caso) pode ser útil:
public:
   const string GetName() const {return m_sModName;}
protected:
         string m_sModName;


O método retorna o nome do módulo que pode ser usado para solucionar problemas, depurar ou nos painéis de informações.

Agora vamos ver como nós podemos implementar a classe essencial para o EA, a CStrategy:

A Estratégia do Expert Advisor

Como mencionado anteriormente, esse deve ser um objeto que toma as decisões de entrada. Esse objeto também pode tomar decisões sobre as saídas, modificação e encerramento parcial, etc. É por isso que o objeto é chamado assim. Obviamente, ele não pode ser implementado como um módulo: é fundamental que o objeto de tomada de decisão seja individual em cada Expert Advisor. Caso contrário, o EA resultante será idêntico ao desenvolvido anteriormente. Portanto, nós não podemos desenvolver esse objeto uma única vez e inseri-lo em todos os EAs, como é feito com os módulos. Mas esse não é o nosso propósito. Vamos começar o desenvolvimento da classe base usando os fatos já conhecidos:

class CStrategy  {
public:
   virtual  void    OnBuyFind  (ulong ticket, double price, double lot) = 0;
   virtual  void    OnSellFind (ulong ticket, double price, double lot) = 0;       
};// class CStrategy


Simples assim. Nós adicionamos dois métodos de chamada, caso o CreateSnap() detecte as posições necessárias. A CStrategy é implementada como uma classe abstrata, enquanto os métodos são declarados de forma virtual. Isso ocorre porque o objeto será diferente para diferentes Expert Advisor. Assim, a classe base só pode ser usada para herança, enquanto os seus métodos serão substituídos.

Agora nós precisamos adicionar o arquivo CStrategy.mqh ao arquivo CModule.mqh:

#include "CStrategy.mqh"


Depois disso, a estrutura do EA pode ser considerada como finalizada e, assim, podemos prosseguir com outras melhorias.

Melhorias na estratégia do Expert Advisor

Usando os métodos virtuais, o objeto CSnap acessa o objeto CStrategy. Mas deve haver outros métodos no objeto CStrategy. O objeto da estratégia deve poder tomar decisões. Portanto, ele deve fornecer as recomendações de entrada se um sinal apropriado for detectado e, em seguida, ele deve executar essa entrada. Nós precisamos de métodos que forçam o objeto CSnap a chamar o seu método CreateSnap(), etc. Vamos adicionar alguns dos métodos à classe CStrategy:

   virtual  string  Name() const     = 0;
   virtual  void    CreateSnap()     = 0;  
   virtual  bool    MayAndEnter()    = 0;  
   virtual  bool    MayAndContinue() = 0;       
   virtual  void    MayAndClose()    = 0;


Esta é uma lista muito condicional, podendo ser alterada ou expandida para EAs específicos. O que esses métodos fazem:

  • Name()                — retorna o nome da estratégia.
  • CreateSnap()        — chama o mesmo método do objeto CSnap.
  • MayAndEnter()      — verifica se há um sinal de entrada e entra se há um sinal.
  • MayAndContinue() — verifica se há um sinal de entrada e entra novamente se houver um sinal.
  • MayAndClose()      — verifica se existe um sinal de saída e encerra todas as posições se houver um sinal.

Obviamente, esta lista está longe de estar completa. Ela falta um método muito importante, o método de inicialização da estratégia. Nosso objetivo é implementar ponteiros para todos os módulos do objeto CStrategy e, portanto, a OnTick() e outros manipuladores acessarão apenas o objeto CStrategy e não saberão nada sobre a existência de outros módulos. Portanto, os ponteiros do módulo precisam ser adicionados ao objeto CStrategy. Nós não podemos simplesmente fornecer os campos abertos correspondentes e inicializá-los no manipulador OnInit(). isso será explicado mais tarde.

Em vez disso, vamos adicionar um método para a inicialização:

virtual  bool    Initialize(CInitializeStruct* pInit) = 0;
  com o objeto de inicialização CInitializeStruct, que conterá os ponteiros necessários. O objeto é descrito na CStrategy.mqh da seguinte maneira:
class CInitializeStruct {};

Ela é uma classe vazia, destinada à herança, semelhante ao CStrategy. Nós concluímos os trabalhos preparatórios e podemos prosseguir para um verdadeiro Expert Advisor.

Uso prático

Vamos criar um Expert Advisor de demonstração operando de acordo com uma lógica muito simples: se a vela anterior estiver em baixa, é aberto uma posição de venda com níveis fixos de Take Profit e Stop Loss. A próxima posição não deve ser aberta até que a anterior seja fechada.

Nós temos os módulos quase prontos. Vamos considerar a classe derivada da CStrategy:

class CRealStrat1 : public CStrategy   {
public:
   static   string  m_name;
                     CRealStrat1(){};
                    ~CRealStrat1(){};
   virtual  string  Name() const {return m_name;}
   virtual  bool    Initialize(CInitializeStruct* pInit) {
                        m_pparam = ((CInit1* )pInit).m_pparam;
                        m_psnap = ((CInit1* )pInit).m_psnap;
                        m_ptrade = ((CInit1* )pInit).m_ptrade;
                        m_psnap.SetStrategy(GetPointer(this));
                        return true;
                    }//Initialize(EA_InitializeStruct* pInit)
   virtual  void    CreateSnap() {
                        m_tb = 0;
                        m_psnap.CreateSnap();
                    }  
   virtual  bool    MayAndEnter();
   virtual  bool    MayAndContinue() {return false;}       
   virtual  void    MayAndClose()   {}
   virtual  bool    Stop()            {return false;}   
   virtual  void    OnBuyFind  (ulong ticket, double price, double lot) {}
   virtual  void    OnBuySFind (ulong ticket, double price, double lot) {}   
   virtual  void    OnBuyLFind (ulong ticket, double price, double lot) {}
   virtual  void    OnSellFind (ulong ticket, double price, double lot) {tb = ticket;}  
   virtual  void    OnSellSFind(ulong ticket, double price, double lot) {}   
   virtual  void    OnSellLFind(ulong ticket, double price, double lot) {}      
private:
   CParam*          m_pparam;
   CSnap*           m_psnap;  
   CTradeMod*       m_ptrade;   
   ulong            m_tb;            
};
static string CRealStrat1::m_name = "Real Strategy 1";

bool CRealStrat1::MayAndEnter() {
   if (tb != 0) return false;  
   double o = iOpen(NULL,PERIOD_CURRENT,1); 
   double c = iClose(NULL,PERIOD_CURRENT,1); 
   if (c < o) {
      m_ptrade.Sell();
      return true;
   }   
   return false;
} 

O código do EA é simples e não precisa de explicações. Vamos considerar apenas algumas das partes. O método CreateSnap() da classe CRealStrat1 redefine o campo que contém o ticket da posição de vendida já aberta e chama o método CreateSnap() do módulo CSnap. O módulo CSnap verifica as posições em aberto. Se uma posição vendida aberta por este EA for encontrada, o módulo chama o método OnSellFind(...) da classe CStrategy, um ponteiro para o qual está contido no módulo CSnap. Como resultado, o método OnSellFind(...) da classe CRealStrat1 é chamado. Ele altera o valor do campo m_tb novamente. O método MayAndEnter() vê que uma posição já foi aberta e não abrirá uma nova. Outros métodos da classe base CStrategy não são usados em nosso EA, é por isso que sua implementação está vazia.

Outro ponto interessante diz respeito ao método Initialize(...). Esse método adiciona os ponteiros da classe CRealStrat1 a outros módulos que podem ser necessários para a tomada de decisões separadas. A classe CStrategy não sabe quais módulos podem ser necessários para a classe CRealStrat1 e, portanto, ela usa uma classe CInitializeStruct vazia. Nós adicionaremos a classe CInit1 no arquivo que contém a classe CRealStrat1 (embora isso não seja necessário). CInit1 é herdado de CInitializeStruct:

class CInit1: public CInitializeStruct {
public:
   bool Initialize(CParam* pparam, CSnap* psnap, CTradeMod* ptrade) {
   if (CheckPointer(pparam) == POINTER_INVALID || 
       CheckPointer(psnap)  == POINTER_INVALID) return false;   
      m_pparam = pparam; 
      m_psnap  = psnap;
      m_ptrade = ptrade;
       return true;
   }
   CParam* m_pparam;
   CSnap*  m_psnap;
   CTradeMod* m_ptrade;
};

O objeto da classe pode ser criado e inicializado no manipulador OnInit, podendo ser passado para o método correspondente no objeto da classe CRealStrat1. Assim, nós temos uma estrutura complexa que consiste em objetos separados. Mas nós podemos trabalhar com a estrutura por meio de uma interface simples no manipulador OnTick().

Os manipuladores OnInit() e OnTick()

Aqui está o manipulador OnInit() e uma lista possível de objetos globais:

CParam     par;
CSnap      snap;
CTradeMod  trade;

CStrategy* pS1;

int OnInit()
  {  
   ...
   pS1 = new CRealStrat1();
   CInit1 ci;
   if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return (INIT_FAILED);
   pS1.Initialize(GetPointer(ci));      
   return (INIT_SUCCEEDED);
  }
  


  Somente um objeto é criado no manipulador OnInit(): uma instância da classe CRealStrat1. Ele é inicializado com um objeto da classe CInit1. O objeto é destruído no manipulador OnDeinit():
void OnDeinit(const int reason)
  {
      if (CheckPointer(pS1) != POINTER_INVALID) delete pS1;      
  }


O manipulador OnTick() resultante é muito simples:
void OnTick()
  {
      if (IsNewCandle() ){
         pS1.CreateSnap();
         pS1.MayAndEnter();
         
      }    
  }


Verificamos a posição existente na abertura de uma nova vela e, em seguida, verificamos se há um sinal de entrada. Se houver um sinal e o EA ainda não tiver realizado uma entrada, é aberto uma nova posição. O manipulador é tão simples que algum código "global" adicional pode ser facilmente incluído nele, se necessário. Por exemplo, você pode instruir o EA a não começar a negociar imediatamente após o lançamento dele em um gráfico, mas para aguardar a confirmação do usuário através de um clique no botão e assim por diante.

Algumas outras funções do EA não são descritas aqui, mas estão disponíveis no arquivo zip em anexo.

Assim, nós projetamos um Expert Advisor composto por vários blocos separados, os módulos. Mas isso não é tudo. Vamos ver outras oportunidades interessantes fornecidas por essa abordagem de programação.

Qual é o próximo?

A primeira coisa que pode ser implementada posteriormente diz respeito à substituição dinâmica dos módulos. Um módulo de horário simples pode ser substituído por outro mais avançado. Nesse caso, nós precisamos substituir o módulo de horário como um objeto, em vez de adicionar as propriedades e métodos ao existente, o que complicaria a depuração e a manutenção. Nós podemos fornecer versões de "Debug" e "Release" para os módulos separados, criar um gerenciador para controlar os módulos.  

Mas há uma possibilidade ainda mais interessante. Com a nossa abordagem de programação, nós podemos implementar a substituição dinâmica da estratégia do EA, que no nosso caso é implementada como uma substituição da classe CRealStrat1. O EA resultante terá dois núcleos que implementam duas estratégias, por exemplo, a negociação de tendências e as estratégias de negociação pra mercados lateralizados. Além disso, uma terceira estratégia pode ser adicionada a negociação durante a sessão asiática. Isso significa que nós podemos implementar vários Expert Advisors em um EA e alterná-los dinamicamente. Como fazer isso:

  1. Nós desenvolvemos uma classe contendo a lógica de decisão, derivada da classe CStrategy (como a CRealStrat1)
  2. Nós desenvolvemos uma classe que inicializa essa nova estratégia, derivada da CInitializeStruct (CInit1)
  3. Conectamos o arquivo resultante ao projeto.
  4. Adicionamos uma nova variável nos parâmetros de entrada, que define uma estratégia ativa no início.
  5. Implementamos os alternadores da estratégia, ou seja, um painel de negociação.

Como exemplo, vamos adicionar mais uma estratégia ao nossa EA de demonstração. Mais uma vez, nós prepararemos uma estratégia muito simples para não complicar o código. A primeira estratégia abriu uma posição de venda. Essa tem uma lógica semelhante: se a vela anterior for de alta, é aberto uma posição de compra com os níveis fixos de Take Profit e Stop Loss. A próxima posição não deve ser aberta até que a anterior seja fechada. O segundo código de estratégia é muito semelhante ao primeiro e está disponível no arquivo zip em anexo. A nova classe de inicialização da estratégia também está contida no arquivo.

Vamos considerar quais alterações devem ser feitas no arquivo do EA que contém os parâmetros de entrada e os manipuladores:

enum CSTRAT {
   strategy_1 = 1,
   strategy_2 = 2
};

input CSTRAT strt  = strategy_1;

CSTRAT str_curr = -1;

int OnInit()
  {  
   ...
   if (!SwitchStrategy(strt) ) return (INIT_FAILED);
   ...       
   return (INIT_SUCCEEDED);
  }
  
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
      if (id == CHARTEVENT_OBJECT_CLICK && StringCompare(...) == 0 ) {
         SwitchStrategy((CSTRAT)EDlg.GetStratID() );
      } 
  }  

bool SwitchStrategy(CSTRAT sr) {
   if (str_curr == sr) return true;
   CStrategy* s = NULL;
   switch(sr) {
      case strategy_1:
         {
            CInit1 ci;
            if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return false;
            s = new CRealStrat1();
            s.Initialize(GetPointer(ci));  
         }
         break;
      case strategy_2:
         {
            CInit2 ci;
            if (!ci.Initialize(GetPointer(par), GetPointer(snap), GetPointer(trade)) ) return false;
            s = new CRealStrat2();
            s.Initialize(GetPointer(ci));              
         }   
         break;
   }
   if (CheckPointer(pS1) != POINTER_INVALID) delete pS1;
   pS1 = s;    
   str_curr = sr;
   return true;
}

A função de alternador de estratégia SwitchStrategy(...) e o manipulador OnChartEvent(...) são conectados a um painel de negociação. Seu código não é fornecido no artigo, mas está disponível no arquivo zip em anexo. Além disso, o gerenciamento dinâmico da estratégia não é uma tarefa complicada. Criamos um novo objeto com a estratégia, excluímos o anterior e escrevemos um novo ponteiro para a variável:

CStrategy* pS1;


Depois disso, o EA acessará a nova estratégia na OnTick() e, portanto, a nova lógica de operação será usada. A hierarquia de objetos e as principais dependências são assim:

A figura não exibe o painel de negociação e as conexões que ocorrem como secundárias durante a inicialização. Nesse estágio, nós podemos considerar a nossa tarefa como concluída: o Expert Advisor está pronto para operação e aprimoramentos adicionais. Com a utilização da abordagem de blocos, melhorias e modificações críticas podem ser feitas em um período de tempo bem curto.

    Conclusão

    Nós criamos um Expert Advisor usando os elementos do padrão de projeto Observer e Facade. A descrição completa desses (e outros) padrões é fornecida no livro "Design Patterns. Elements of Reusable Object-Oriented Software" por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. Eu recomendo a leitura deste livro.

    Programas utilizados no artigo

     # Nome
    Tipo
     Descrição
    1
    EaxModules.zip Arquivo Um arquivo zip com os arquivos do Expert Advisor.


    Traduzido do russo pela MetaQuotes Ltd.
    Artigo original: https://www.mql5.com/ru/articles/7318

    Arquivos anexados |
    EaxModules.zip (7.74 KB)
    Criando um EA gradador multiplataforma (conclusão): diversificação como forma de aumentar a lucratividade Criando um EA gradador multiplataforma (conclusão): diversificação como forma de aumentar a lucratividade
    Nos artigos anteriores desta série, tentamos de várias maneiras criar um EA gradador mais ou menos rentável. Agora é a vez de tentarmos aumentar a lucratividade do EA por meio da diversificação. Nosso objetivo é obter o desejado lucro de 100% ao ano, com um rebaixamento máximo de saldo de 20%.
    Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXI): classes de negociação - objeto básico de negociação multiplataforma Biblioteca para criação simples e rápida de programas para MetaTrader (Parte XXI): classes de negociação - objeto básico de negociação multiplataforma
    Neste artigo, iniciaremos uma nova seção da biblioteca, nomeadamente as classes de negociação, e consideraremos a criação de um único objeto básico de negociação para as plataformas MetaTrader 5 e MetaTrader 4. Tal objeto de negociação implicará que, ao enviar uma consulta ao servidor, para ele terão sido enviados os parâmetros da solicitação de negociação já verificados e corretos.
    Otimização Walk Forward Contínua (Parte 1): Trabalhando com os Relatórios de Otimização Otimização Walk Forward Contínua (Parte 1): Trabalhando com os Relatórios de Otimização
    O primeiro artigo é dedicado à criação de um kit de ferramentas para trabalhar com os relatórios de otimização, importá-los da plataforma e para filtrar e classificar os dados obtidos. A MetaTrader 5 permite baixar os resultados da otimização, no entanto, nosso objetivo é adicionar nossos próprios dados ao relatório de otimização.
    Desenvolvimento do Oscilador Pivô Médio: um novo Indicador para a Média Móvel Acumulada Desenvolvimento do Oscilador Pivô Médio: um novo Indicador para a Média Móvel Acumulada
    Este artigo apresenta o Oscilador Pivô Médio (PMO), uma implementação da média móvel cumulativa (CMA) como um indicador de negociação para as plataformas MetaTrader. Em particular, nós introduzimos primeiro o Pivô Médio (PM) como um índice de normalização para as séries temporais que calcula a fração entre qualquer ponto de dados e o CMA. Em seguida, nós criamos o PMO como a diferença entre as médias móveis aplicadas a dois sinais de PM. Também são relatadas algumas experiências preliminares realizadas no símbolo EURUSD para testar a eficácia do indicador proposto, deixando um amplo espaço para considerações e melhorias adicionais.