Download MetaTrader 5

Expert Advisor Multiplataforma: As classes CExpertAdvisor e CExpertAdvisors

11 dezembro 2017, 12:44
Enrico Lambino
0
797

Índice

  1. Introdução
  2. A classe Expert Advisor
  3. Inicialização
  4. Detecção de uma Nova Barra
  5. Manipulador da OnTick
  6. Container de Expert Advisors
  7. Persistência de Dados
  8. Exemplos
  9. Considerações finais
  10. Conclusão

Introdução

Em artigos anteriores sobre este tópico, os exemplos de expert advisor apresentados possuem seus componentes distribuído em todo o arquivo de cabeçalho principal do EA através do uso de funções personalizadas. Este artigo apresenta as classes CExpertAdvisor e CExpertsAdvisors, que visam a criação de uma interação mais harmoniosa entre os vários componentes de um EA multiplataforma. Ele também aborda alguns problemas comuns, que são geralmente encontrados nos expert advisors, como carregar e salvar os dados voláteis e a detecção de uma nova barra.

A classe Expert Advisor

A classe CExpertAdvisorBase é exibida no seguinte trecho de código. Neste ponto, a maioria das diferenças entre MQL4 e MQL5 são tratadas pelos outros objetos de classe que foram discutidos nos artigos anteriores.

class CExpertAdvisorBase : public CObject
  {
protected:
   //--- parâmetros de negociação
   bool              m_active;
   string            m_name;
   int               m_distance;
   double            m_distance_factor_long;
   double            m_distance_factor_short;
   bool              m_on_tick_process;
   //--- parâmetros do sinal
   bool              m_every_tick;
   bool              m_one_trade_per_candle;
   datetime          m_last_trade_time;
   string            m_symbol_name;
   int               m_period;
   bool              m_position_reverse;
   //--- objetos do sinal
   CSignals         *m_signals;
   //--- objetos de negociação   
   CAccountInfo      m_account;
   CSymbolManager    m_symbol_man;
   COrderManager     m_order_man;
   //--- objetos do horário de negociação
   CTimes           *m_times;
   //--- vela
   CCandleManager    m_candle_man;
   //--- eventos
   CEventAggregator *m_event_man;
   //--- container
   CObject          *m_container;
public:
                     CExpertAdvisorBase(void);
                    ~CExpertAdvisorBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_EXPERT;}
   //--- inicialização
   bool              AddEventAggregator(CEventAggregator*);
   bool              AddMoneys(CMoneys*);
   bool              AddSignal(CSignals*);
   bool              AddStops(CStops*);
   bool              AddSymbol(const string);
   bool              AddTimes(CTimes*);
   virtual bool      Init(const string,const int,const int,const bool,const bool,const bool);
   virtual bool      InitAccount(void);
   virtual bool      InitCandleManager(void);
   virtual bool      InitEventAggregator(void);
   virtual bool      InitComponents(void);
   virtual bool      InitSignals(void);
   virtual bool      InitTimes(void);
   virtual bool      InitOrderManager(void);
   virtual bool      Validate(void) const;
   //--- container
   void              SetContainer(CObject*);
   CObject          *GetContainer(void);
   //--- ativação e desativação
   bool              Active(void) const;
   void              Active(const bool);
   //--- setters e getters       
   string            Name(void) const;
   void              Name(const string);
   int               Distance(void) const;
   void              Distance(const int);
   double            DistanceFactorLong(void) const;
   void              DistanceFactorLong(const double);
   double            DistanceFactorShort(void) const;
   void              DistanceFactorShort(const double);
   string            SymbolName(void) const;
   void              SymbolName(const string);
   //--- ponteiros de objeto
   CAccountInfo     *AccountInfo(void);
   CStop            *MainStop(void);
   CMoneys          *Moneys(void);
   COrders          *Orders(void);
   COrders          *OrdersHistory(void);
   CStops           *Stops(void);
   CSignals         *Signals(void);
   CTimes           *Times(void);
   //--- gerenciador de ordens
   string            Comment(void) const;
   void              Comment(const string);
   bool              EnableTrade(void) const;
   void              EnableTrade(bool);
   bool              EnableLong(void) const;
   void              EnableLong(bool);
   bool              EnableShort(void) const;
   void              EnableShort(bool);
   int               Expiration(void) const;
   void              Expiration(const int);
   double            LotSize(void) const;
   void              LotSize(const double);
   int               MaxOrdersHistory(void) const;
   void              MaxOrdersHistory(const int);
   int               Magic(void) const;
   void              Magic(const int);
   uint              MaxTrades(void) const;
   void              MaxTrades(const int);
   int               MaxOrders(void) const;
   void              MaxOrders(const int);
   int               OrdersTotal(void) const;
   int               OrdersHistoryTotal(void) const;
   int               TradesTotal(void) const;
   //--- gerenciador de sinal   
   int               Period(void) const;
   void              Period(const int);
   bool              EveryTick(void) const;
   void              EveryTick(const bool);
   bool              OneTradePerCandle(void) const;
   void              OneTradePerCandle(const bool);
   bool              PositionReverse(void) const;
   void              PositionReverse(const bool);
   //--- velas adicionais
   void              AddCandle(const string,const int);
   //--- detecção de uma nova barra
   void              DetectNewBars(void);
   //--- eventos
   virtual bool      OnTick(void);
   virtual void      OnChartEvent(const int,const long&,const double&,const string&);
   virtual void      OnTimer(void);
   virtual void      OnTrade(void);
   virtual void      OnDeinit(const int,const int);
   //--- recuperação
   virtual bool      Save(const int);
   virtual bool      Load(const int);

protected:
   //--- gerenciador de velas   
   virtual bool      IsNewBar(const string,const int);
   //--- gerenciador de ordens
   virtual void      ManageOrders(void);
   virtual void      ManageOrdersHistory(void);
   virtual void      OnTradeTransaction(COrder*) {}
   virtual datetime  Time(const int);
   virtual bool      TradeOpen(const string,const ENUM_ORDER_TYPE,double,bool);
   //--- gerenciador de símbolos
   virtual bool      RefreshRates(void);
   //--- desinicialização
   void              DeinitAccount(void);
   void              DeinitCandle(void);
   void              DeinitSignals(void);
   void              DeinitSymbol(void);
   void              DeinitTimes(void);
  };

A maioria dos métodos de classe declarados dentro desta classe servem como wrappers para os métodos de seus componentes. Os principais métodos desta classe serão discutidos nas próximas seções.

Inicialização

Durante a fase de inicialização do EA, nosso principal objetivo é instanciar os objetos necessários à estratégia de negociação (por exemplo, gestão de capital, sinais, etc.) e, em seguida, integrá-los com a instância do CExpertAdvisor, que também precisaria ser criado durante a OnInit. Com este objetivo, quando qualquer uma das funções do evento é desencadeada dentro do EA, tudo o que precisamos fornecer é uma única linha de código que chama o manipulador ou método apropriado da instância da CExpertAdvisor. Isso é muito semelhante com a forma como a CExpert da Biblioteca Padrão MQL5 é utilizada.

Após a criação de uma instância da CExpertAdvisor, o próximo método de chamada é o método Init. O código do método mencionado é exibido abaixo:

bool CExpertAdvisorBase::Init(string symbol,int period,int magic,bool every_tick=true,bool one_trade_per_candle=true,bool position_reverse=true)
  {
   m_symbol_name=symbol;
   CSymbolInfo *instrument;
   if((instrument=new CSymbolInfo)==NULL)
      return false;
   if(symbol==NULL) symbol=Symbol();
   if(!instrument.Name(symbol))
      return false;
   instrument.Refresh();
   m_symbol_man.Add(instrument);
   m_symbol_man.SetPrimary(m_symbol_name);
   m_period=(ENUM_TIMEFRAMES)period;
   m_every_tick=every_tick;
   m_order_man.Magic(magic);
   m_position_reverse=position_reverse;
   m_one_trade_per_candle=one_trade_per_candle;
   CCandle *candle=new CCandle();
   candle.Init(instrument,m_period);
   m_candle_man.Add(candle);
   Magic(magic);
   return false;
  }

Aqui, nós criamos as instâncias da maioria dos componentes que são frequentemente encontrados nas estratégias de negociação. Isso inclui o símbolo ou instrumento a ser usado (que deve ser traduzido para um tipo de objeto) e o período padrão ou tempo gráfico. Ele também contém a regra sobre se deve ou não funcionar suas tarefas principais em cada novo tick ou no primeiro tick de cada vela somente, se deve ou não limitar de no máximo uma negociação por vela somente (para evitar múltiplas entradas na mesma vela) e se deve inverter sua posição em um sinal oposto (fechar negociações existentes e se posicionar com base no novo sinal).

No final da função OnInit, a instância da CExpertAdvisor teria que fazer uma chamada para o método InitComponents. O código a seguir mostra o método mencionado da CExpertBase:

bool CExpertAdvisorBase::InitComponents(void)
  {
   if(!InitSignals())
     {
      Print(__FUNCTION__+": error in signal initialization");
      return false;
     }
   if(!InitTimes())
     {
      Print(__FUNCTION__+": error in time initialization");
      return false;
     }
   if(!InitOrderManager())
     {
      Print(__FUNCTION__+": error in order manager initialization");
      return false;
     }
   if(!InitCandleManager())
     {
      Print(__FUNCTION__+": error in candle manager initialization");
      return false;
     }
   if(!InitEventAggregator())
     {
      Print(__FUNCTION__+": error in event aggregator initialization");
      return false;
     }
   return true;
  }

Neste método, o método Init de cada um dos componentes da instância do Expert Advisor é chamado. É também através deste método que os métodos de validação de cada componente são chamados, verificando se suas configurações passariam na validação.

Detecção de uma Nova Barra

Algumas estratégias de negociação exigem operar no primeiro tick de uma nova vela. Existem várias maneiras de implementar esse recurso. Uma delas é a comparação do horário e do preço de abertura da vela atual com os seus estados anteriores, que é o método implementado na classe CCandle. O código a seguir mostra a declaração para CCandleBase, da qual o CCandle se baseia:

class CCandleBase : public CObject
  {
protected:
   bool              m_new;
   bool              m_wait_for_new;
   bool              m_trade_processed;
   int               m_period;
   bool              m_active;
   MqlRates          m_last;
   CSymbolInfo      *m_symbol;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CCandleBase(void);
                    ~CCandleBase(void);
   virtual int       Type(void) const {return(CLASS_TYPE_CANDLE);}
   virtual bool      Init(CSymbolInfo*,const int);
   virtual bool      Init(CEventAggregator*);
   CObject          *GetContainer(void);
   void              SetContainer(CObject*);
   //--- setters e getters
   void              Active(bool);
   bool              Active(void) const;
   datetime          LastTime(void) const;
   double            LastOpen(void) const;
   double            LastHigh(void) const;
   double            LastLow(void) const;
   double            LastClose(void) const;
   string            SymbolName(void) const;
   int               Timeframe(void) const;
   void              WaitForNew(bool);
   bool              WaitForNew(void) const;
   //--- processamento
   virtual bool      TradeProcessed(void) const;
   virtual void      TradeProcessed(bool);
   virtual void      Check(void);
   virtual void      IsNewCandle(bool);
   virtual bool      IsNewCandle(void) const;
   virtual bool      Compare(MqlRates &) const;
   //--- recuperação
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

A verificação da presença de uma nova vela no gráfico é feita através do método Check, que é exibido abaixo:

CCandleBase::Check(void)
  {
   if(!Active())
      return;
   IsNewCandle(false);
   MqlRates rates[];
   if(CopyRates(m_symbol.Name(),(ENUM_TIMEFRAMES)m_period,1,1,rates)==-1)
      return;
   if(Compare(rates[0]))
     {
      IsNewCandle(true);
      TradeProcessed(false);
      m_last=rates[0];
     }
  }

Se verificar uma nova barra, a instância do expert advisor deve sempre chamar esse método a cada tick. O programador é então livre para estender a CCxpertAdvisor para que ela possa executar tarefas adicionais quando uma nova vela aparecer no gráfico.

Conforme mostrado no código acima, a comparação real do horário e preço de abertura da barra é feita através do método Compare da classe, que é mostrado no seguinte código:

bool CCandleBase::Compare(MqlRates &rates) const
  {
   return (m_last.time!=rates.time ||
           (m_last.open/m_symbol.TickSize())!=(rates.open/m_symbol.TickSize()) || 
           (!m_wait_for_new && m_last.time==0));
  }

Este método de verificar a existência de uma nova barra depende de três condições. Satisfazer pelo menos uma irá garantir um resultado igual a true, o que indica a presença de uma nova vela no gráfico:

  1. O último horário de abertura registrado não é igual ao horário de abertura da barra atual
  2. O último preço de abertura registrado não é igual ao preço de abertura da barra atual
  3. O último horário de abertura registrado é zero e uma nova barra não precisa ser o primeiro tick para essa barra

As duas primeiras condições envolvem a comparação direta das taxas da barra atual com o estado registrado anterior. A terceira condição aplica-se apenas ao primeiro tick que o EA encontrará. Assim que um EA é carregado em um gráfico, ele ainda não possui nenhum registro prévio das taxas (horário e preço de abertura) e, portanto, o último horário de abertura registrado seria zero. Alguns traders consideram esta barra como uma nova barra para seus EA, enquanto outros preferem que o expert advisor espere que uma nova barra atual apareça no gráfico após a inicialização do expert advisor.

Semelhante a outros tipos de classes discutidas anteriormente, a classe CCandle também teria seu container, a CCandleManager. O código a seguir mostra a declaração da CCandleManagerBase:

class CCandleManagerBase : public CArrayObj
  {
protected:
   bool              m_active;
   CSymbolManager   *m_symbol_man;
   CEventAggregator *m_event_man;
   CObject          *m_container;
public:
                     CCandleManagerBase(void);
                    ~CCandleManagerBase(void);
   virtual int       Type(void) const {return(CLASS_TYPE_CANDLE_MANAGER);}
   virtual bool      Init(CSymbolManager*,CEventAggregator*);
   virtual bool      Add(const string,const int);
   CObject          *GetContainer(void);
   void              SetContainer(CObject *container);
   bool              Active(void) const;
   void              Active(bool active);
   virtual void      Check(void) const;
   virtual bool      IsNewCandle(const string,const int) const;
   virtual CCandle *Get(const string,const int) const;
   virtual bool      TradeProcessed(const string,const int) const;
   virtual void      TradeProcessed(const string,const int,const bool) const;
   //--- recuperação
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

Uma instância da CCandle é criada com base no nome do instrumento e no tempo gráfico. Com a CCandleManager, seria mais fácil para um expert advisor rastrear múltiplos gráficos para um determinado instrumento, por exemplo, tendo a capacidade de verificar a ocorrência de uma nova vela no EURUSD M15 e no EURUSD H1 no mesmo EA. Instâncias da CCandle que têm o mesmo símbolo e tempo gráfico são redundantes e devem ser evitadas. Ao procurar uma determinada instância da CCandle, basta chamar o método apropriado encontrado na CCandleManager e especificar o símbolo e o tempo gráfico. O CCandleManager, por sua vez, procuraria a instância CCandle apropriada e chamaria o método pretendido.

Além de verificar a ocorrência de uma nova vela, a CCandle e CCandleManager atendem a outra finalidade: verificar se uma operação foi realizada por um expert advisor em um determinado símbolo e tempo gráfico. A negociação recente em um símbolo pode ser verificado, mas não o seu tempo gráfico. O alternador para esta sinalização deve ser configurada ou reiniciada pela instância da CExpertAdvisor em si, quando necessário. Para ambas as classes, o alternador pode ser configurado usando o método TradeProcessed.

Para o gerenciador de velas, os métodos TradeProcessed (getter e setter) tratam apenas de encontrar a instância do CCandle solicitada e aplicar o valor apropriado:

bool CCandleManagerBase::TradeProcessed(const string symbol,const int timeframe) const
  {
   CCandle *candle=Get(symbol,timeframe);
   if(CheckPointer(candle))
      return candle.TradeProcessed();
   return false;
  }

Para a CCandle, o processo envolve a atribuição de um novo valor a um dos seus membros da classe, a m_trade_processed. Os seguintes métodos tratam da definição do valor do membro da classe mencionada:

bool CCandleBase::TradeProcessed(void) const
  {
   return m_trade_processed;
  }

CCandleBase::TradeProcessed(bool value)
  {
   m_trade_processed=value;
  }

Manipulador da OnTick

O método OnTick da CExpertAdvisor é a função mais utilizada dentro da classe. É a partir deste método onde ocorre a maior parte da ação. A operação principal deste método é mostrada no seguinte diagrama:


CExpertAdvisorBase OnTick


O processo começa por alternar a sinalização do tick do expert advisor. Isso é para garantir que o processamento duplo de um tick não possa vir a ocorrer. O método OnTick do CExpertAdvisor é idealmente chamado apenas dentro da função de evento OnTick, mas ela também pode ser chamada por outros meios, como a OnChartEvent. Na ausência deste sinalizador, se o método OnTick da classe for chamado enquanto ele ainda está processando um tick anterior, um tick pode ser processado mais de uma vez e, se o tick gerasse uma negociação, isso geralmente resultaria em uma operação em duplicata.

A atualização dos dados também são necessários, pois isso garante que o expert advisor tenha acesso aos dados de mercado mais recentes e não reprocessará um tick anterior. Se o expert advisor não atualizar os dados, ele redefinirá o sinalizador de processamento da tick, encerraria o método e aguardaria um novo tick.

Os próximos passos são a detecção de novas barras e a verificação dos sinais de negociação. A verificação para isso é realizada a cada tick por padrão. No entanto, é possível estender esse método para que ele apenas verifiquem os sinais quando um novo sinal é detectado (para acelerar o tempo de processamento, especialmente durante o teste e a otimização).

A classe também fornece um membro, m_position_reverse, que se destina a inverter a(s) posição(s) oposta(s) ao sinal atual. A reversão realizada aqui é apenas para a neutralização da(s) posição(s) atuai(s). No modo hedge da MetaTrader 4 e MetaTrader 5, isso lida com a saída dos negócios que são opostos ao sinal atual (aqueles que estão com o sinal atual não serão encerrados). No modo netting da MetaTrader 5, só pode haver uma posição em qualquer momento, de modo que o expert advisor entrará em uma nova posição de igual volume, porém, oposta à da posição atual.

O sinal de negociação é basicamente verificar usando a m_signals, mas outros fatores, como a negociação somente em uma barra nova e os filtros de tempo também podem impedir que o EA execute uma nova negociação. Somente quando todas as condições estiverem satisfeitas, o EA poderá entrar em uma nova operação.

No final do processamento do tick, o EA definirá o sinalizador de tick para false, e então poderá processar outro tick.

Container de Expert Advisors

Semelhante a outros objetos de classe discutidos em artigos anteriores, a classe CExpertAdvisor também teria seu container designado, que é a CExpertAdvisors. O código a seguir mostra a declaração para a sua classe base, a CExpertAdvisorsBase:

class CExpertAdvisorsBase : public CArrayObj
  {
protected:
   bool              m_active;
   int               m_uninit_reason;
   CObject          *m_container;
public:
                     CExpertAdvisorsBase(void);
                    ~CExpertAdvisorsBase(void);
   virtual int       Type(void) const {return CLASS_TYPE_EXPERTS;}
   virtual int       UninitializeReason(void) const {return m_uninit_reason;}
   //--- getters e setters
   void              SetContainer(CObject *container);
   CObject          *GetContainer(void);
   bool              Active(void) const;
   void              Active(const bool);
   int               OrdersTotal(void) const;
   int               OrdersHistoryTotal(void) const;
   int               TradesTotal(void) const;
   //--- inicialização
   virtual bool      Validate(void) const;
   virtual bool      InitComponents(void) const;
   //--- eventos
   virtual void      OnTick(void);
   virtual void      OnChartEvent(const int,const long&,const double&,const string&);
   virtual void      OnTimer(void);
   virtual void      OnTrade(void);
   virtual void      OnDeinit(const int,const int);
   //--- recuperação
   virtual bool      CreateElement(const int);
   virtual bool      Save(const int);
   virtual bool      Load(const int);
  };

Este container reflete principalmente os métodos públicos encontrados na classe CExpertAdvisor. Um exemplo disso é o manipulador da OnTick. O método simplesmente itera em cada instância da CExpertAdvisor para chamar o método OnTick:

void CExpertAdvisorsBase::OnTick(void)
  {
   if(!Active()) return;
   for(int i=0;i<Total();i++)
     {
      CExpertAdvisor *e=At(i);
      e.OnTick();
     }
  }

Com esse container, é possível armazenar várias instâncias da CExpertAdvisor. Esta é provavelmente a única maneira de executar vários expert advisors em uma única instância do gráfico. Basta inicializar várias instâncias da CExpertAdvisor, armazenar seus ponteiros em um único container da CExpertAdvisors e, em seguida, usar o método OnTick do container para acionar os métodos da OnTick de cada instância da CExpertAdvisor. O mesmo pode ser feito com cada instância da classe CExpert da Biblioteca Padrão MQL5 usando a classe CArrayObj ou seus herdeiros.

Persistência de Dados

Alguns dados usados ​​em uma instância da CExpertAdvisor residem apenas na memória do computador. Normalmente, os dados são frequentemente armazenados na plataforma e o EA obtém os dados necessários da própria plataforma através de uma chamada de função. No entanto, este geralmente não é o caso para os dados criados dinamicamente enquanto o EA está em execução. Quando o evento OnDeinit é acionado em um EA, o expert advisor destrói todos os objetos e, assim, perde os dados.

A OnDeinit pode ser desencadeada de várias maneiras, como fechar toda a plataforma de negociação (MetaTrader 4 ou MetaTrader 5), remover o EA do gráfico ou mesmo o ato de recompilar o código-fonte do EA. A lista completa de eventos possíveis que podem desencadear a desinitialização pode ser encontrada usando a função UninitializeReason. Quando um expert advisor perde o acesso nesses dados, ele pode comportar-se como se fosse apenas carregado no gráfico pela primeira vez.

A maioria dos dados voláteis da classe CExpertAdvisor podem ser encontrados em um de seus membros, que é uma instância da COrderManager. É aí que os casos da COrder e COrderStop (e descendentes) são criados à medida que o expert advisor executa sua rotina usual. Como essas instâncias são criadas dinamicamente durante a OnTick, elas não são recriadas quando o expert advisor reinicializa. Portanto, o expert advisor deve implementar um método para salvar e recuperar esses dados voláteis. Uma maneira de implementar isso é usar um descendente da classe CFileBin, CExpertFile. O trecho do código a seguir mostra a declaração da CExpertFileBase, sua classe base:

class CExpertFileBase : public CFileBin
  {
public:
                     CExpertFileBase(void);
                    ~CExpertFileBase(void);
   void              Handle(const int handle) { m_handle=handle; };
   uint              WriteBool(const bool value);
   bool              ReadBool(bool &value);
  };

Aqui, nós estamos ampliando a CFileBin para declarar explicitamente os métodos para escrever e ler os dados do tipo booleano.

No final do arquivo da classe, nós declaramos uma instância da classe CExpertFile. Esta instância será usada em todo o expert advisor como se fosse para salvar e carregar os dados voláteis. Alternativamente, pode-se simplesmente confiar nos métodos Save e Load herdados da CObject, e processar a gravação e o carregamento de dados da maneira usual. No entanto, isso pode ser um processo muito rigoroso. Um grande esforço e linhas de código podem ser salvas ao usar a CFile (ou seus herdeiros) sozinho.

//Definição da classe CExpertFileBase
//+------------------------------------------------------------------+
#ifdef __MQL5__
#include "..\..\MQL5\File\ExpertFile.mqh"
#else
#include "..\..\MQL4\File\ExpertFile.mqh"
#endif
//+------------------------------------------------------------------+
CExpertFile file;
//+------------------------------------------------------------------+

O gerenciador de ordens salva os dados voláteis através do método Save:

bool COrderManagerBase::Save(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   file.WriteDouble(m_lotsize);
   file.WriteString(m_comment);
   file.WriteInteger(m_expiration);
   file.WriteInteger(m_history_count);
   file.WriteInteger(m_max_orders_history);
   file.WriteBool(m_trade_allowed);
   file.WriteBool(m_long_allowed);
   file.WriteBool(m_short_allowed);
   file.WriteInteger(m_max_orders);
   file.WriteInteger(m_max_trades);
   file.WriteObject(GetPointer(m_orders));
   file.WriteObject(GetPointer(m_orders_history));
   return true;
  }

A maioria desses dados são de tipos primitivos, exceto os dois últimos, que são as ordens e o container do histórico de ordens. Para esses dados, o método WriteObject da CFileBin é usado, que por sua vez chama o método Save do objeto a ser escrito. O código a seguir mostra o método Save da COrderBase:

bool COrderBase::Save(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   file.WriteBool(m_initialized);
   file.WriteBool(m_closed);
   file.WriteBool(m_suspend);
   file.WriteInteger(m_magic);
   file.WriteDouble(m_price);
   file.WriteLong(m_ticket);
   file.WriteEnum(m_type);
   file.WriteDouble(m_volume);
   file.WriteDouble(m_volume_initial);
   file.WriteString(m_symbol);
   file.WriteObject(GetPointer(m_order_stops));
   return true;
  }

Como nós podemos ver aqui, o processo apenas se repete ao salvar os objetos. Para os tipos de dados primitivos, os dados são simplesmente salvos no arquivo como de costume. Para os tipos de dados complexos, o método Save do objeto é chamado através do método WriteObject da CFileBin.

Nos casos em que múltiplas instâncias da CExpertAdvisor está presente, o container da CExpertAdvisors também deve ter a capacidade de salvar os dados:

bool CExpertAdvisorsBase::Save(const int handle)
  {
   if(handle!=INVALID_HANDLE)
     {
      for(int i=0;i<Total();i++)
        {
         CExpertAdvisor *e=At(i);
         if(!e.Save(handle))
            return false;
        }
     }
   return true;
  }

O método chama o método Save de cada instância da CExpertAdvisor. O manipulador de arquivo único significa que só haveria um arquivo de armazenamento para cada arquivo do EA. É possível que cada instância da CExpertAdvisor tenha seu próprio arquivo de armazenamento, mas essa seria a abordagem mais complicada.

A parte mais complexa é o carregamento dos dados. Ao salvar os dados, os valores de algumas classes são simplesmente escritas em arquivo. Por outro lado, ao carregar os dados, as instâncias do objeto precisarão ser recriadas idealmente no mesmo estado antes de terem sido salvas. O código a seguir exibe o método Load do gerenciador de ordens:

bool COrderManagerBase::Load(const int handle)
  {
   if(handle==INVALID_HANDLE)
      return false;
   if(!file.ReadDouble(m_lotsize))
      return false;
   if(!file.ReadString(m_comment))
      return false;
   if(!file.ReadInteger(m_expiration))
      return false;
   if(!file.ReadInteger(m_history_count))
      return false;
   if(!file.ReadInteger(m_max_orders_history))
      return false;
   if(!file.ReadBool(m_trade_allowed))
      return false;
   if(!file.ReadBool(m_long_allowed))
      return false;
   if(!file.ReadBool(m_short_allowed))
      return false;
   if(!file.ReadInteger(m_max_orders))
      return false;
   if(!file.ReadInteger(m_max_trades))
      return false;
   if(!file.ReadObject(GetPointer(m_orders)))
      return false;
   if(!file.ReadObject(GetPointer(m_orders_history)))
      return false;
   for(int i=0;i<m_orders.Total();i++)
     {
      COrder *order=m_orders.At(i);
      if(!CheckPointer(order))
         continue;
      COrderStops *orderstops=order.OrderStops();
      if(!CheckPointer(orderstops))
         continue;
      for(int j=0;j<orderstops.Total();j++)
        {
         COrderStop *orderstop=orderstops.At(j);
         if(!CheckPointer(orderstop))
            continue;
         for(int k=0;k<m_stops.Total();k++)
           {
            CStop *stop=m_stops.At(k);
            if(!CheckPointer(stop))
               continue;
            orderstop.Order(order);
            if(StringCompare(orderstop.StopName(),stop.Name())==0)
              {
               orderstop.Stop(stop);
               orderstop.Recreate();
              }
           }
        }
     }
   return true;
  }

O código acima para a COrderManager é muito mais complicado em contraste com o método Load da CExpertAdvisor. A razão é que, ao contrário do gerenciador de ordens, as instâncias da CExpertAdvisor são criadas durante a OnInit e, portanto, o container simplesmente teria que chamar o método Load de cada instância da CExpertAdvisor, em vez de usar o método ReadObject da CFileBin.

As instâncias de classe que não foram criadas durante a OnInit, terão que ser criadas também ao recarregar o EA. Isto é alcançado estendendo o método CreateElement da CArrayObj. Um objeto não pode simplesmente ser criado por conta própria, portanto, ele deve ser criado pelo seu objeto ou container pai, ou mesmo a partir do arquivo principal de origem ou cabeçalho. Um exemplo pode ser visto no método ExtendedElement expandido, encontrado na COrdersBase. Sob esta classe, o container é a COrders (um descendente da COrdersBase), e o objeto a ser criado é do tipo COrder:

bool COrdersBase::CreateElement(const int index)
  {
   COrder*order=new COrder();
   if(!CheckPointer(order))
      return(false);
   order.SetContainer(GetPointer(this));
   if(!Reserve(1))
      return(false);
   m_data[index]=order;
   m_sort_mode=-1;
   return CheckPointer(m_data[index]);
  }

Aqui, além de criar o elemento, nós também estabelecemos o seu objeto ou recipiente principal, para diferenciar se ele pertence à lista de operações ativas (membro da classe m_orders da COrderManagerBase) ou o histórico (m_orders_history da COrderManagerBase).

Exemplos

Os exemplos 1-4 deste artigo são versões modificadas dos quatro exemplos encontrados no artigo anterior (veja Expert Advisor Multiplataforma: Stops Personalizados, Breakeven e Stop Móveis). Vamos dar uma olhada no exemplo mais complexo, expert_custom_trail_ha_ma.mqh, que é uma versão modificada de custom_trail_ha_ma.mqh.

Antes da função OnInit, nós declaramos as seguintes instâncias de objetos globais:

COrderManager *order_manager;
CSymbolManager *symbol_manager;
CSymbolInfo *symbol_info;
CSignals *signals;
CMoneys *money_manager;
CTimes *time_filters;

Nós substituímos isso por uma instância da CExpert. Alguns dos itens acima podem ser encontrados na própria CExpetAdvisor (por exemplo, COrderManager), enquanto o resto precisa ser instanciado durante a OnInit (ou seja, os containers):

CExpertAdvisors experts;

No início do método, nós criamos uma instância da CExpertAdvisor. Nós também chamamos seu método Init, inserindo as configurações mais básicas:

int OnInit()
  {
//---
   CExpertAdvisor *expert=new CExpertAdvisor();
   expert.Init(Symbol(),Period(),12345,true,true,true);
//--- outro código
//---
   return(INIT_SUCCEEDED);
  }

A CSymbolInfo/CSymbolManager já não precisa ser instanciada, uma vez que a instância da classe CExpertAdvisor é capaz de criar instâncias dessas classes por conta própria.

A função definida pelo usuário também precisaria ser removida, já que nosso novo EA não precisará mais disso.

Nós removemos a declaração global para os containers em nosso código, então eles precisam ser declarados dentro da OnInit. Um exemplo disto é o container de filtros de tempo (CTimeFilters), conforme mostrado no código a seguir, encontrado na função OnInit:

CTimes *time_filters=new CTimes();

Os ponteiros para os containers que foram "adicionados" anteriormente ao gerenciador de ordens são, em vez disso, adicionados à instância da CExpertAdvisor. Todos os outros containers que não são adicionados ao gerenciador de ordens também devem ser adicionados à instância da CExpertAdvisor. Seria a instância da COrderManager que armazenaria os ponteiros. A instância CExpertAdvisor apenas cria os métodos wrapper.

Depois disso, nós adicionamos a instância da CExpertAdvisor a uma instância da CExpertAdvisors. Em seguida, nós chamamos o método InitComponents da instância CExpertAdvisors. Isso garantiria a inicialização de todas as instâncias da CExpertAdvisor e seus componentes.

int OnInit()
  {
//---
//--- outro código
   experts.Add(GetPointer(expert));
   if(!experts.InitComponents())
      return(INIT_FAILED);
//--- outro código
//---
   return(INIT_SUCCEEDED);
  }

Finalmente, nós inserimos o código necessário para carregar se o expert advisor foi interrompido em sua operação:

int OnInit()
  {
//---
//--- outro código   
 file.Open(savefile,FILE_READ);
   if(!experts.Load(file.Handle()))
      return(INIT_FAILED);
   file.Close();
//---
   return(INIT_SUCCEEDED);
  }

Se o expert advisor não conseguir carregar do arquivo, ele retornará INIT_FAILED. No entanto, no caso em que nenhum arquivo de armazenamento for fornecido (e, portanto, geraria INVALID_HANDLE), o EA não falhará na inicialização, uma vez que os métodos Load da CExpertAdvisors e CExpertAdvisor retornam true ao receber um identificador inválido. Há risco com essa abordagem, mas é muito improvável que um outro arquivo seja aberto por outro programa. Certifique-se de que cada instância do EA em execução em um gráfico possui um arquivo de salvaguarda exclusivo (como o número mágico).

O quinto exemplo não pode ser encontrado no artigo anterior. Em vez disso, ele combina todos os quatro expert advisors neste artigo em um único EA. Ele simplesmente usa uma versão ligeiramente modificada da função OnInit de cada um dos expert advisors e declara-o como uma função personalizada. Seu valor de retorno é do tipo CExpertAdvisor*. Se a criação do expert advisor falhar, ele retornará NULL ao invés de INIT_SUCCEEDED. O código a seguir mostra a função OnInit atualizada no arquivo de cabeçalho do EA combinado:

int OnInit()
  {
//---
   CExpertAdvisor *expert1=expert_breakeven_ha_ma();
   CExpertAdvisor *expert2=expert_trail_ha_ma();
   CExpertAdvisor *expert3=expert_custom_stop_ha_ma();
   CExpertAdvisor *expert4=expert_custom_trail_ha_ma();
      
   if (!CheckPointer(expert1))
      return INIT_FAILED;
   if (!CheckPointer(expert2))
      return INIT_FAILED;
   if (!CheckPointer(expert3))
      return INIT_FAILED;
   if (!CheckPointer(expert4))
      return INIT_FAILED;
   
   experts.Add(GetPointer(expert1));
   experts.Add(GetPointer(expert2));
   experts.Add(GetPointer(expert3));
   experts.Add(GetPointer(expert4));   
   
   if(!experts.InitComponents())
      return(INIT_FAILED);
   file.Open(savefile,FILE_READ);
   if(!experts.Load(file.Handle()))
      return(INIT_FAILED);
   file.Close();
//---
   return(INIT_SUCCEEDED);
  }

O expert advisor começa instanciando cada instância da CExpertAdvisor. Em seguida, é verificado cada um dos ponteiros para a CExpertAdvisor. Se o ponteiro não for dinâmico, a função retornará INIT_FAILED e a inicialização falhará. Se cada uma das instâncias passar na verificação de ponteiros, esses ponteiros são armazenados em uma instância da CExpertAdvisors. A instância CExpertAdvisors (o container, e não a instância do EA) inicializaria seus componentes e carregaria os dados anteriores, se necessário.

O expert advisor usa funções personalizadas para criar uma instância da CExpertAdvisor. O código a seguir mostra a função usada para criar a 4ª instância do EA:

CExpertAdvisor *expert_custom_trail_ha_ma()
{
   CExpertAdvisor *expert=new CExpertAdvisor();
   expert.Init(Symbol(),Period(),magic4,true,true,true);
   CMoneys *money_manager=new CMoneys();
   CMoney *money_fixed=new CMoneyFixedLot(0.05);
   CMoney *money_ff=new CMoneyFixedFractional(5);
   CMoney *money_ratio=new CMoneyFixedRatio(0,0.1,1000);
   CMoney *money_riskperpoint=new CMoneyFixedRiskPerPoint(0.1);
   CMoney *money_risk=new CMoneyFixedRisk(100);

   money_manager.Add(money_fixed);
   money_manager.Add(money_ff);
   money_manager.Add(money_ratio);
   money_manager.Add(money_riskperpoint);
   money_manager.Add(money_risk);
   expert.AddMoneys(GetPointer(money_manager));

   CTimes *time_filters=new CTimes();
   if(time_range_enabled && time_range_end>0 && time_range_end>time_range_start)
     {
      CTimeRange *timerange=new CTimeRange(time_range_start,time_range_end);
      time_filters.Add(GetPointer(timerange));
     }
   if(time_days_enabled)
     {
      CTimeDays *timedays=new CTimeDays(sunday_enabled,monday_enabled,tuesday_enabled,wednesday_enabled,thursday_enabled,friday_enabled,saturday_enabled);
      time_filters.Add(GetPointer(timedays));
     }
   if(timer_enabled)
     {
      CTimer *timer=new CTimer(timer_minutes*60);
      timer.TimeStart(TimeCurrent());
      time_filters.Add(GetPointer(timer));
     }

   switch(time_intraday_set)
     {
      case INTRADAY_SET_1:
        {
         CTimeFilter *timefilter=new CTimeFilter(time_intraday_gmt,intraday1_hour_start,intraday1_hour_end,intraday1_minute_start,intraday1_minute_end);
         time_filters.Add(timefilter);
         break;
        }
      case INTRADAY_SET_2:
        {
         CTimeFilter *timefilter=new CTimeFilter(0,0,0);
         timefilter.Reverse(true);
         CTimeFilter *sub1 = new CTimeFilter(time_intraday_gmt,intraday2_hour1_start,intraday2_hour1_end,intraday2_minute1_start,intraday2_minute1_end);
         CTimeFilter *sub2 = new CTimeFilter(time_intraday_gmt,intraday2_hour2_start,intraday2_hour2_end,intraday2_minute2_start,intraday2_minute2_end);
         timefilter.AddFilter(sub1);
         timefilter.AddFilter(sub2);
         time_filters.Add(timefilter);
         break;
        }
      default: break;
     }
   expert.AddTimes(GetPointer(time_filters));

   CStops *stops=new CStops();
   CCustomStop *main=new CCustomStop("main");
   main.StopType(stop_type_main);
   main.VolumeType(VOLUME_TYPE_PERCENT_TOTAL);
   main.Main(true);
//main.StopLoss(stop_loss);
//main.TakeProfit(take_profit);
   stops.Add(GetPointer(main));

   CTrails *trails=new CTrails();
   CCustomTrail *trail=new CCustomTrail();
   trails.Add(trail);
   main.Add(trails);

   expert.AddStops(GetPointer(stops));

   MqlParam params[1];
   params[0].type=TYPE_STRING;
#ifdef __MQL5__
   params[0].string_value="Examples\\Heiken_Ashi";
#else
   params[0].string_value="Heiken Ashi";
#endif
   SignalHA *signal_ha=new SignalHA(Symbol(),0,1,params,signal_bar);
   SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar);
   CSignals *signals=new CSignals();
   signals.Add(GetPointer(signal_ha));
   signals.Add(GetPointer(signal_ma));
   expert.AddSignal(GetPointer(signals));
//---
   return expert;
}

Como nós podemos ver, o código está muito parecido com a função OnInit do arquivo de cabeçalho do expert advisor original (expert_custom_trail_ha_ma.mqh). As outras funções personalizadas também são organizadas da mesma maneira.

Notas finais

Antes de concluir este artigo, qualquer leitor que deseje usar esta biblioteca deve ser informado desses fatores que contribuem para o desenvolvimento da biblioteca:

No momento da escrita deste artigo, a biblioteca apresentada aqui possui mais de 10.000 linhas de código (incluindo os comentários). Apesar disso, ela continua sendo um trabalho em andamento. Mais trabalho precisa ser feito para utilizar plenamente os recursos da MQL4 e da MQL5.

O autor começou a trabalhar neste projeto antes da introdução do modo de hedge na MetaTrader 5. Isso influenciou muito o desenvolvimento da biblioteca. Como resultado, a biblioteca tende a estar mais perto de adotar as convenções usadas na MetaTrader 4 do que na MetaTrader 5. Além disso, o autor também experimentou alguns problemas de compatibilidade com algumas atualizações das versões lançadas nos últimos anos, o que levou a alguns ajustes menores e maiores ao código (e algum atraso na publicação de alguns artigos). No momento da redação, o autor constatou que as atualizações de versão para ambas as plataformas estão menos frequentes e mais estáveis ​​ao longo do tempo. Esta tendência deverá melhorar ainda mais. No entanto, futuras atualizações de versão que podem causar incompatibilidades ainda precisam ser abordadas.

A biblioteca depende dos dados guardados na memória para acompanhar os seus próprios negócios. Isso faz com que os expert advisors criados usando esta biblioteca dependam fortemente de salvar e carregar os dados para lidar com possíveis interrupções que o EA pode experimentar durante sua execução. O trabalho futuro sobre esta biblioteca, bem como qualquer outra biblioteca que visa a compatibilidade entre plataformas, deve ser orientada para alcançar uma implementação apátrida ou quase apátrida, semelhante à implementação da Biblioteca Padrão MQL5.

Como uma observação final, a biblioteca apresentada neste artigo não deve ser vista como uma solução permanente. Em vez disso, ela deve ser usada como uma oportunidade para uma transição mais suave da MetaTrader 4 para a MetaTrader 5. As incompatibilidades entre a MQL4 e a MQL5 apresentam um enorme obstáculo para os traders que pretendem fazer a transição para a nova plataforma. Como resultado, o código-fonte da MQL4 dos seus expert advisors deve ser refatorado para ser compatível com o compilador MQL5. A biblioteca apresentada neste artigo é fornecida como um meio para implantar um EA para a nova plataforma com pouco ou nenhum ajuste ao principal código-fonte do EA. Isso pode ajudar o trader em sua decisão de continuar usando a MetaTrader 4 ou mudar para a MetaTrader 5. No caso em que ele decidiu trocar, seriam necessários pequenos ajustes, e o trader poderá operar da maneira usual com seus expert advisors. Por outro lado, se ele decidir continuar usando a plataforma antiga, ela oferece uma opção para mudar rapidamente para a nova plataforma, uma vez que a MetaTrader 4 se torne software legado.

Conclusão

Este artigo apresentou os objetos da classe CExpertAdvisor e CExpertAdvisors, que são usados ​​para integrar todos os componentes de um expert advisor multiplataforma discutido nesta série de artigos. O artigo discute como as duas classes são instanciadas e vinculadas com os outros componentes de um expert advisor multiplataforma. Ele também apresenta algumas soluções para problemas geralmente encontrados por expert advisors, como a detecção de novas barras e o armazenamento e carregamento de dados voláteis.

Programas Usados ​​no Artigo

 # Nome
Tipo
Descrição
1.
expert_breakeven_ha_ma.mqh
Arquivo de Cabeçalho
O arquivo de cabeçalho principal usado no primeiro exemplo
2.
expert_breakeven_ha_ma.mq4 Expert Advisor
O arquivo principal foi usado para a versão MQL4 no primeiro exemplo
3.
expert_breakeven_ha_ma.mq5 Expert Advisor O arquivo principal foi usado para a versão MQL5 no primeiro exemplo
4.
 expert_trail_ha_ma.mqh Arquivo de Cabeçalho O arquivo de cabeçalho principal usado no segundo exemplo
5.
 expert_trail_ha_ma.mq4 Expert Advisor O arquivo principal foi usado para a versão MQL4 no segundo exemplo
6.
 expert_trail_ha_ma.mq5 Expert Advisor O arquivo principal foi usado para a versão MQL5 no segundo exemplo
7.
 expert_custom_stop_ha_ma.mqh Arquivo de Cabeçalho O arquivo de cabeçalho principal usado no terceiro exemplo
8.
 expert_custom_stop_ha_ma.mq4 Expert Advisor O arquivo principal foi usado para a versão MQL4 no terceiro exemplo
9.
 expert_custom_stop_ha_ma.mq5 Expert Advisor O arquivo principal foi usado para a versão MQL5 no terceiro exemplo
10.
 expert_custom_trail_ha_ma.mqh Arquivo de Cabeçalho O arquivo de cabeçalho principal usado no quarto exemplo
11.
 expert_custom_trail_ha_ma.mq4 Expert Advisor O arquivo principal foi usado para a versão MQL4 no quarto exemplo
12.
 expert_custom_trail_ha_ma.mq5 Expert Advisor O arquivo principal foi usado para a versão MQL5 no quarto exemplo
13.
 combined.mqh Arquivo de Cabeçalho O arquivo de cabeçalho principal foi usado no quinto exemplo
14.
 combined.mq4 Expert Advisor O arquivo principal foi usado para a versão MQL4 no quinto exemplo
15.
 combined.mq5 Expert Advisor O arquivo principal foi usado para a versão MQL5 no quinto exemplo

Arquivos de Classe Apresentados no Artigo

#
Nome
Tipo
Descrição
1. MQLx\Base\Expert\ExperAdvisorsBase Arquivo de Cabeçalho
CExpertAdvisors (container CExpertAdvisor, classe base)
2.
MQLx\MQL4\Expert\ExperAdvisors Arquivo de Cabeçalho CExpertAdvisors (versão MQL4)
3.
MQLx\MQL5\Expert\ExperAdvisors Arquivo de Cabeçalho
CExpertAdvisors (versão MQL5)
4.
MQLx\Base\Expert\ExperAdvisorBase Arquivo de Cabeçalho
CExpertAdvisor (classe base)
5.
MQLx\MQL4\Expert\ExperAdvisor Arquivo de Cabeçalho
CExpertAdvisor (versão MQL4)
6.
MQLx\MQL5\Expert\ExperAdvisor Arquivo de Cabeçalho
CExpertAdvisor (versão MQL5)
7.
MQLx\Base\Candle\CandleManagerBase Arquivo de Cabeçalho CCandleManager (container CCandle, classe base)
8.
MQLx\MQL4\Candle\CandleManager Arquivo de Cabeçalho CCandleManager (versão MQL4)
9.
MQLx\MQL5\Candle\CandleManager Arquivo de Cabeçalho CCandleManager (versão MQL5)
10.
MQLx\Base\Candle\CandleBase Arquivo de Cabeçalho CCandle (classe base)
11.
MQLx\MQL4\Candle\Candle Arquivo de Cabeçalho CCandle (versão MQL4)
12.
MQLx\MQL5\Candle\Candle Arquivo de Cabeçalho
CCandle (versão MQL5)
13.
MQLx\Base\File\ExpertFileBase Arquivo de Cabeçalho CExpertFile (classe base)
14.
MQLx\MQL4\File\ExpertFile Arquivo de Cabeçalho CExpertFile (versão MQL4)
15.
MQLx\MQL5\File\ExpertFile Arquivo de Cabeçalho CExpertFile (versão MQL5)


Traduzido do Inglês por MetaQuotes Software Corp.
Artigo original: https://www.mql5.com/en/articles/3622

Arquivos anexados |
MQL5.zip (143.3 KB)
Uso do filtro de Kalman na previsão da tendência Uso do filtro de Kalman na previsão da tendência

Para o sucesso na negociação, quase sempre são necessários indicadores, cujo objetivo é a separação entre o movimento principal do preço e as flutuações ruidosas. Neste artigo, é examinado um dos filtros digitais mais promissores, o filtro de Kalman. Além disso, são descritos tanto sua construção como uso na prática.

Comparação de diferentes tipos de médias móveis durante a negociação Comparação de diferentes tipos de médias móveis durante a negociação

São examinados 7 tipos de médias móveis (MA), é criada uma estratégia de negociação para trabalhar com eles. É levado a cabo o teste e comparação de diferentes MA numa mesma estratégia de negociação, são apresentadas as características comparativas quanto a eficiência de cada média móvel.

Mini-emulador do mercado ou Testador de estratégias manual Mini-emulador do mercado ou Testador de estratégias manual

O mini-emulador do mercado é um indicador projetado para emulação parcial do trabalho no terminal. Presumivelmente, ele pode ser usado no teste de estratégias "manuais" de análise e negociação no mercado.

R quadrado como uma estimativa da qualidade da curva de saldo da estratégia R quadrado como uma estimativa da qualidade da curva de saldo da estratégia

Este artigo descreve a construção do R² - critério de otimização personalizado. Esse critério pode ser usado para estimar a qualidade da curva de saldo de uma estratégia e para selecionar as estratégias mais consistentes e lucrativas. O trabalho discute os princípios de sua construção e os métodos estatísticos utilizados na estimativa de propriedades e qualidade desta métrica.