Expert Advisor Multiplataforma: As classes CExpertAdvisor e CExpertAdvisors
Índice
- Introdução
- A classe Expert Advisor
- Inicialização
- Detecção de uma Nova Barra
- Manipulador da OnTick
- Container de Expert Advisors
- Persistência de Dados
- Exemplos
- Considerações finais
- 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:
- O último horário de abertura registrado não é igual ao horário de abertura da barra atual
- O último preço de abertura registrado não é igual ao preço de abertura da barra atual
- 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:
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 pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/3622
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso