Expert Advisor multiplataforma: Sinais
Tabela de conteúdos
- Introdução
- Objetivos
- Sinais de negociação
- Tipos de sinais
- Comparação com CExpertSignal
- Fases
- OnInit e OnDeinit
- OnTick
- Implementação
- Classe CSignal
- Classe CSignals
- Instâncias do indicador
- Restrições
- Exemplos
- Exemplo #1: modelo de Gerenciador de ordens
- Exemplo #2: Expert Advisor MA
- Exemplo #3: Expert Advisor HA
- Exemplo #4: Expert Advisor com base no HA e MA
- Fim do artigo
Introdução
No artigo anterior nós mostramos a classe COrderManager e como ela pode ser usada para automatizar o processo de abertura e fechamento de transações. Neste artigo, usaremos aproximadamente os mesmos princípios para automatizar os processos de geração de sinais. Isso pode ser conseguido através da classe CSignal e seu recipiente, a classe CSignals. Este artigo detalha a implementação dos objetos dessas classes.
Objetivos
As classes CSignal e CSignals, mencionadas neste artigo, têm os seguintes objetivos:
- A implementação deve ser compatível com MQL4 e MQL5.
- A maioria dos processos associados à avaliação de sinais de negociação deve ser automatizada.
- A implementação deve ser simples
Sinais de negociação
Tanto CSignal quanto seu recipiente, CSignals, são responsáveis pela avaliação de todos os sinais, com base no estado atual do mercado. Sinais de negociação são divididos em dois grupos principais: os sinais de entrada e de saída. Para que um sinal de entrada resulte no fato de um EA executar uma transação, esse sinal e todos os outros sinais de entrada devem ter a mesma direção (todos longos ou todos curtos). Por outro lado, para sinais de saída, cada sinal é independente e pode influenciar o resultado final com base apenas no seu resultado. Os sinais de saída também são avaliados cumulativamente, assim, por exemplo, se o sinal № 1 indicar fechamento de todas as posições sell, enquanto o sinal № 2, fechamento de todas as posições buy, o resultado final será o fechamento de todas as transações.
Tipos de sinais
O EA fornece quatro tipos diferentes de sinais, que são interpretados pelos objetos de sinal (e, portanto, pelo EA), com base em como eles são usados (para entrada ou saída). A tabela a seguir mostra os tipos de sinais, seus valores e como eles são interpretados de acordo com o uso do destino:
Tipo de sinal |
Valor | Entrada | Saída |
---|---|---|---|
CMD_VOID | -1 |
Invalida todos os outros sinais |
Sair de todas as transações |
CMD_NEUTRAL | 0 |
Ignorado |
Ignorado |
CMD_LONG | 1. |
Abertura de posição longa |
Saída de transações curtas |
CMD_SHORT | 2 |
Abertura de posição curta |
Saída de transações longas |
Os tipos de sinais CMD_LONG e CMD_SHORT são bastante fáceis de entender, então nos concentraremos mais nos outros dois tipos de sinais.
CMD_VOID tem um valor inteiro de -1, e refere-se a um sinal que está em forte desacordo com o atual. Se ele é um sinal de saída, ele invalida a saída de todos os outros sinais de entrada. Isso quer dizer que seu resultado deve ser obrigatório, e independentemente dos resultados e tendências individuais, se ele mostrar condições não comerciais, em todos os outros sinais as condições para negociação também serão desfavoráveis. Como exemplo, considere o seguinte cenário de três sinais para entrada:
Sinal 1: CMD_VOID Sinal 2: CMD_LONG Sinal 3: CMD_SHORT Resultado: CMD_VOID
Neste caso, podemos ver que o sinal 1 acabará por invalidar os outros dois sinais, e, eventualmente, irá se tornar CMD_VOID. Mas também note que os sinais 2 e 3 não têm a mesma direção e, portanto, qualquer que seja o valor do sinal 1, neste caso, em última análise, estaremos perante uma situação pouco favorável para o EA.
Consideremos agora uma situação ligeiramente modificada, como mostrado abaixo:
Sinal 1: CMD_VOID Sinal 2: CMD_LONG Sinal 3: CMD_LONG Resultado: CMD_VOID
Neste caso, os sinais 2 e 3 estão indo no mesmo sentido, mas o sinal 1 cancela os outros. Como resultado, o sinal geral indica que a situação não é favorável para negociar. O sinal 1 recebe mais peso por ser inválido, mesmo se os outros sinais têm igual direção.
Ao procurar um sinal de saída, o segundo exemplo resulta na saída de todas as posições: todos os três sinais concordam em fechar posições longas, enquanto o sinal 1 envia uma mensagem para fechar posições longas e curtas. Como todos os sinais de saída são avaliados cumulativamente, o resultado final liva ao fechamento de todas as transações.
CMD_NEUTRAL tem um valor inteiro de 0 e indica a ausência de sinal. Isto é aproximadamente equivalente à abstinência num processo eleitoral. Um sinal "neutro" renuncia ao seu direito de influenciar o resultado final e deixa a decisão para o resto dos sinais. No entanto, se só temos um sinal e ele "se abstém", o Expert Advisor fica numa situação sem entradas nem saídas, o que é como ter vários sinais com diferentes direções.
Vamos agora fazer um exemplo usando CMD_NEUTRAL e modificando ligeiramente o primeiro exemplo nesta seção:
Sinal 1: CMD_NEUTRAL Sinal 2: CMD_LONG Sinal 3: CMD_SHORT Resultado: CMD_VOID
Em nosso terceiro exemplo, o sinal 1 dá uma posição neutra. Neste caso, apenas os sinais 2 e 3 serão considerados após a saída do sinal final. Como eles têm diferentes direções, como resultado veremos um cenário pouco favorável para negociar.
O caso é diferente quando os sinais restantes possuem a mesma direção. Em nosso quarto exemplo (mostrado abaixo), o primeiro sinal é neutro e os sinais restantes são unidirecionais. Neste caso, o sinal 1 é ignorado, enquanto os sinais 2 e 3 são avaliados e, como resultado, dão um sinal de compra como resultado.
Sinal 1: CMD_NEUTRAL Sinal 2: CMD_LONG Sinal 3: CMD_LONG Resultado: CMD_LONG
Repare que a ordem dos sinais não é importante, e o seguinte conjunto de sinais é:
Sinal 1: CMD_NEUTRAL Sinal 2: CMD_LONG Sinal 3: CMD_LONG Sinal 4: CMD_NEUTRAL Sinal 5: CMD_LONG Sinal 6: CMD_NEUTRAL
Como resultado, aparecerá um sinal geral CMD_LONG, e não CMD_NEUTRAL.
Se se trata de um sinal de saída, o sinal com resultado CMD_NEUTRAL não afetará o resultado geral. Ao definir o sinal final tudo será como se ele não existisse.
Vale a pena ressaltar o valor atribuído de CMD_NEUTRAL igual a 0, e que nós usamos esta enumeração personalizada como um substituto para ENUM_ORDER_TYPE. O uso desta enumeração tem vantagens claras.
Primeiro, podemos personalizar melhor como o sinal é interpretado.
Outra vantagem é que podemos evitar a execução acidental de transações não inicializadas usando variáveis. Por exemplo, ORDER_TYPE_BUY tem um valor inteiro de 0. Se tivermos uma EA que envia a variável int diretamente para o método que processa a solicitação de negociação, e essa variável não for inicializada ou não for reatribuída a outro valor (provavelmente não intencional), então o valor padrão será 0, o que resultará na entrada de uma ordem de compra. Por outro lado, com a enumeração personalizada, tal acidente nunca acontecerá, pois um valor de zero para a variável sempre resultará numa situação sem negociação.
Comparação com CExpertSignal
CExpertSignal avalia a direção geral da seguinte forma:
- Calcula sua própria direção e armazena-a na variável m_direction
- Para cada um dos seus filtros
- obtém sua direção,
- adiciona a direção para m_direction (subtrai, se o filtro particular estiver numa outra direção)
- Se o valor final de m_direction exceder o limite, surgirá um sinal de negociação
Usando esse método, podemos inferir que quanto mais positivo o valor de m_direction é, mais os sinais que avaliam que o preço provavelmente aumentará (aumenta a chance de exceder o valor de limiar). Da mesma forma, quanto mais negativo o valor de m_direction é, mais sinais preverão que o preço cairá. O valor de limiar é sempre positivo e, portanto, o valor absoluto de m_direction é usado ao verificar o sinal de venda.
Os objetos de sinal apresentados neste artigo podem ser considerados uma versão simplificada do CExpertSignal. No entanto, em vez disso, para avaliar o sinal e seus filtros coletivamente usando operações aritméticas, cada sinal é avaliado separadamente. Esta abordagem é menos versátil, mas que dá ao trader ou ao programador mais controle sobre a extensão pela qual cada sinal individual pode influenciar o resultado final do sinal.
Fases
OnInit e OnDeinit
A fase de inicialização de cada sinal está muitas vezes associada com a criação e inicialização dos indicadores que são usados nela, bem como com os membros adicionais da classe (se houver) que podem ser requeridos por os diferentes métodos que se encontram na classe do objeto. Ao anular a inicialização, as instâncias do indicador devem ser excluídas.
OnTick
- Fase preliminar (Cálculo) — os valores necessários para o cálculo (verificação de sinal) são atualizados.
- Fase principal (ou Verificação de Sinal) — durante esta fase é determinado o estado atual do sinal. De preferência, o corpo deste método deve ter apenas uma única linha de código, para melhorar a legibilidade do código (veja de relance o que o sinal realmente faz).
- Fase final (ou de Atualização) — em alguns sinais, podem existir certos membros que só podem ser atualizados após a verificação do sinal real ter sido realizada. Um exemplo disto seria o rastreamento do valor do preço bid anterior, possivelmente comparando com o preço atual bid ou algum outro valor (possivelmente de um objeto de gráfico ou saída de indicador). A atualização da variável que armazena o preço bid anterior durante a fase preliminar não faz sentido, pois isso faria seu valor sempre igual ao preço atual bid após a verificação do sinal.
É importante ressaltar que o MQL5 possui uma matriz de ticks, enquanto o MQL4 não possui esse recurso. No entanto, neste caso, os padrões МQL4, aos quais o código será ligado, serão um fator limitante para assegurar a compatibilidade entre plataformas. O fator determinante aqui será uma implementação separada.
Implementação
Classe CSignal
Antes de verificar qualquer sinal de negociação, você deve primeiro atualizar os dados necessários para os cálculos. Isso é realizado usando o método Refresh da classe CSignal, na qual os indicadores (bem como o TimeSeries) são atualizados para os valores mais recentes. No fragmento de código a seguir demonstrado o método Refresh da classe CSignal:
bool CSignalBase::Refresh(void) { for(int i=0;i<m_indicators.Total();i++) { CSeries *indicator=m_indicators.At(i); if(indicator!=NULL) indicator.Refresh(OBJ_ALL_PERIODS); } return true; }
A chamada real do método Refresh da classe CSignal ocorre dentro do método Check da mesma classe. Como mostrado no código abaixo, esse método interrompe o processamento se os dados não podem ser atualizados (caso contrário, os sinais serão insuficientes).
void CSignalBase::Check(void) { if(!Active()) return; if(!Refresh()) return; if(!Calculate()) return; int res=CMD_NEUTRAL; if(LongCondition()) { if (Entry()) m_signal_open=CMD_LONG; if (Exit()) m_signal_close=CMD_LONG; } else if(ShortCondition()) { if (Entry()) m_signal_open=CMD_SHORT; if (Exit()) m_signal_close=CMD_SHORT; } else { if (Entry()) m_signal_open=CMD_NEUTRAL; if (Exit()) m_signal_close=CMD_NEUTRAL; } if(m_invert) { SignalInvert(m_signal_open); SignalInvert(m_signal_close); } Update(); }
Dentro do método Check da classe CSignal o sinal atual é definido pela chamada dos métodos LongCondition e ShortCondition, que são aproximadamente análogo aos métodos utilizados na biblioteca padrão MQL5.
O retorno do sinal atual é realizado mediante chamada dos métodos CheckOpenLong e CheckOpenShort, que devem ser chamados de fora da classe (a partir de outra classe ou diretamente dentro da função OnTick):
bool CSignalBase::CheckOpenLong(void) { return m_signal_open==CMD_LONG; } bool CSignalBase::CheckOpenShort(void) { return m_signal_open==CMD_SHORT; }
O CSignal por si não dá quaisquer sinais de compra ou venda. Assim, os métodos são virtuais e recebem implementação real apenas quando expandido CSignal.
virtual bool LongCondition(void)=0; virtual bool ShortCondition(void)=0;Mas, se as informações - a partir dos dados de TimeSeries e indicadores não processados- não são suficientes e requerem mais cálculos, primeiro será necessário implementar o método Calculate, e só após ele, os métodos mencionados acima. Assim como para os indicadores que serão utilizados no CSignal, as variáveis nas quais são armazenados os valores devem ser membros de classe. Portanto, os métodos LongCondition e ShortCondition fornecerão o acesso a elas.
virtual bool Calculate(void)=0; virtual void Update(void)=0;
Repare que o método Calculate se refere ao tipo booleano (bool), enquanto o método Update não retorna quaisquer valores. Isto significa que é possível configurar o Expert Advisor para que cancele a verificação de sinais, se você não poder executar determinados cálculos. O método Update tem o tipo void, e não é necessário atribuir a ele o tipo booleano, porque ele é chamado somente após a entrada dos sinais atuais.
Instância separada do CSignal pode ser criada para produzir os resultados de sinais de entrada, de saída, ou sinais de ambos os tipos. Isso é possível graças à alternância dos métodos Entry() e Exit() de classe. Geralmente, isto acontece ao inicializar o Expert Advisor.
Classe CSignals
CSignals é descendente de CArrayObj. Isso lhe permite armazenar as instâncias de СObject, que, neste caso, armazenarão as instâncias CSignal.
A inicialização desta classe envolve a transferência do objeto CSymbolManager, que foi discutido num artigo anterior. Isso permite a outros sinais obter os dados necessários (tanto do símbolo no gráfico quanto como de outro símbolo). Além disso, ao ser executado este método, é chamado o método Init para cada sinal:
bool CSignalsBase::Init(CSymbolManager *symbol_man) { m_symbol_man= symbol_man; m_event_man = aggregator; if(!CheckPointer(m_symbol_man)) return false; for(int i=0;i<Total();i++) { CSignal *signal=At(i); if(!signal.Init(symbol_man)) return false; } return true; }
O método Check inicializa o sinal como neutro, e, logo, percorre cada um dos sinais, a fim de obter seus valores atuais. Se o método obtém um sinal inválido ou válido (de compra ou de venda), mas que difere do válido anterior, a resposta final é definida por um sinal que cancela a negociação.
CSignalsBase::Check(void) { if(m_signal_open>0) m_signal_open_last=m_signal_open; if(m_signal_close>0) m_signal_close_last=m_signal_close; m_signal_open=CMD_NEUTRAL; m_signal_close=CMD_NEUTRAL; for(int i=0;i<Total();i++) { CSignal *signal=At(i); signal.Check(); if(signal.Entry()) { if(m_signal_open>CMD_VOID) { ENUM_CMD signal_open=signal.SignalOpen(); if(m_signal_open==CMD_NEUTRAL) { m_signal_open=signal_open; } else if(m_signal_open!=signal_open) { m_signal_open=CMD_VOID; } } } if(signal.Exit()) { if(m_signal_close>CMD_VOID) { ENUM_CMD signal_close=signal.SignalClose(); if(m_signal_close==CMD_NEUTRAL) { m_signal_close=signal_close; } else if(m_signal_close!=signal_close) { m_signal_close=CMD_VOID; } } } } if(m_invert) { CSignal::SignalInvert(m_signal_open); CSignal::SignalInvert(m_signal_close); } if(m_new_signal) { if(m_signal_open==m_signal_open_last) m_signal_open = CMD_NEUTRAL; if(m_signal_close==m_signal_close_last) m_signal_close= CMD_NEUTRAL; } }
Instâncias do indicador
Cada instância da CSignal possui seu próprio conjunto de instâncias de indicador que são armazenadas no m_indicators (instância do CIndicators). O ideal é que cada instância de indicador, que pertence a uma determinada instância da CSignal, será independente de qualquer outra instância da CSignal. Este é o desvio em relação ao método a ser utilizado na Biblioteca padrão MQL5 que armazena todos os indicadores usados pelo EA numa instância CIndicators que é um membro da classe CExpert. Embora esta abordagem seja propensa a duplicar objetos (por exemplo, o objeto do indicador МА para o sinal 1, e, em seguida, o mesmo objeto do indicador para o sinal 2) e, como consequência, leve a cálculos duplicados, ela tem certas vantagens.
Pela menos, ela considera os objetos de sinais como unidades independentes. Isso dá a cada sinal de maior liberdade no uso de indicadores, particularmente ao secionar o símbolo e timeframe. Por exemplo, para EAs que trabalham em vários pares de moedas, a produção de indicadores para processar dados de outros instrumentos pode resultar difícil, se aplicadas apenas as classes do EA na biblioteca padrão. Provavelmente, a maneira mais fácil de evitar isso (em vez de modificar ou ampliar o CExpert) consiste em escrever um novo indicador personalizado (que fornece o acesso aos dados necessários e/ou já processados) que, após isso, pode ser utilizado pela classe CExpert para o seu sinal e/ou filtros.
Restrições
- Disponibilidade dos indicadores. Nem todos os indicadores disponíveis no MetaTtrader 4 estão também disponíveis no MetaTrader 5 (e vice-versa). Portanto, se precisarmos de um EA multiplataforma que trabalhe em ambos os terminais, os indicadores para MetaTrader 4 deverão ser compatíveis com Metatrader 5. Caso contrário, o EA não estará disponível para uso na outra plataforma. Normalmente isto não é um problema para os indicadores padrão, com algumas exceções, por exemplo, o indicador de volume MT4 é diferente da versão MT5. Os indicadores personalizados devem ser feitos em duas versões para fornecer a característica multiplataforma.
- Disponibilidade de certos dados. Alguns dados da série temporal simplesmente não estão disponíveis no MetaTrader 4. Portanto, algumas estratégias que dependem de dados estão disponíveis apenas no MetaTrader 5 (ou seja, no volume do tick) podem ser difíceis ou mesmo impossíveis de traduzir para o código MQL4.
Exemplos
Exemplo #1: opção de Gerenciamento de ordens
Em nosso artigo anterior, foi mostrado um exemplo de um EA para ver como trabalhava o gerenciador de ordens no EA atual. O método desse EA consiste em alternar entre as transações de compra e as de venda, no início da nova barra. O seguinte trecho de código mostra a função OnTick neste Expert Advisor:
void OnTick() { //--- static int bars = 0; static int direction = 0; int current_bars = 0; #ifdef __MQL5__ current_bars = Bars(NULL,PERIOD_CURRENT); #else current_bars = Bars; #endif if (bars<current_bars) { symbol_info.RefreshRates(); COrder *last = order_manager.LatestOrder(); if (CheckPointer(last) && !last.IsClosed()) order_manager.CloseOrder(last); if (direction<=0) { Print("Entering buy trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask()); direction = 1; } else { Print("Entering sell trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid()); direction = -1; } bars = current_bars; } }
Como podemos ver, as linhas de código que são responsáveis pelo comportamento do Expert Advisor (relativas à geração do sinal) estão dispostas em diferentes lugares da função. Para EAs simples como este, o código é fácil de decifrar e, portanto, modificável. No entanto, a manutenção do código-fonte pode se tornar cada vez mais difícil à medida que o Expert Advisor escala em complexidade. Nosso objetivo é organizar a geração de sinais para este EA, usando classes que discutiremos neste artigo.
Para fazer isso, precisamos estender a classe CSignal - no arquivo de cabeçalho principal - mediante três membros protegidos: (1) número de barras anteriores, (2) número anterior de barras anteriores e (3) direção atual, como se mostra no código abaixo:
class SignalOrderManagerExample: public CSignal { protected: int m_bars_prev; int m_bars; int m_direction; //resto da classe
Nós também precisamos expandir os métodos que estão na classe CSignal. Para o método Calculate, também usamos o mesmo método de cálculo que no exemplo anterior:
bool SignalOrderManagerExample::Calculate(void) { #ifdef __MQL5__ m_bars=Bars(NULL,PERIOD_CURRENT); #else m_bars=Bars; #endif return m_bars>0 && m_bars>m_bars_prev; }
Métodos de obtenção de número de barras no gráfico atual são diferentes nas duas plataformas, e, portanto, como no exemplo anterior, dividimos a implementação. Observe também que o método Calculate é uma variável booleana. Como discutido acima, se o método Сalculate retornar false, será interrompido o futuro processamento de sinais a partir desse evento de ticks. Aqui nós definimos claramente duas regras sobre quando o processamento do sinal de evento de tick deve ser efetuado: (1) número atual de barras acima zero e (2) número atual de barras maior do que o anterior.
Em seguida, lidamos com o método Update da classe, estendendo o método em nossa classe personalizada, que é mostrado nas seguintes linhas de código:
void SignalOrderManagerExample::Update(void) { m_bars_prev=m_bars; m_direction= m_direction<=0?1:-1; }
Após a verificação dos sinais, atualizamos o número anterior de barras (m_bars_prev) para o atual (m_bars). Também atualizamos a direção. Se o valor atual for menor ou igual a zero (a direção anterior indica venda, ou a transação é executada pela primeira vez), o novo valor será definido como 1 para essa variável. Caso contrário, seu valor será -1.
Finalmente, estamos trabalhando com a geração dos próprios sinais. Com base nas variáveis necessárias para a avaliação do sinal para o tick atual, nós especificamos condições que determinarão se o sinal de saída deve ser um sinal de compra ou um sinal de venda. Isso é feito estendendo os métodos LongCondition e ShortCondition da classe CSignal:
bool SignalOrderManagerExample::LongCondition(void) { return m_direction<=0; } bool SignalOrderManagerExample::ShortCondition(void) { return m_direction>0; }
A função Init para este exemplo é muito semelhante ao exemplo anterior. Exceto que, para este exemplo, temos que criar o descendente da classe CSignal que acabamos de definir (SignalOrderManagerExample), bem como seu contêiner (CSignals):
int OnInit() { //--- order_manager=new COrderManager(); symbol_manager=new CSymbolManager(); symbol_info=new CSymbolInfo(); if(!symbol_info.Name(Symbol())) Print("symbol not set"); symbol_manager.Add(GetPointer(symbol_info)); order_manager.Init(symbol_manager,NULL); SignalOrderManagerExample *signal_ordermanager=new SignalOrderManagerExample(); signals=new CSignals(); signals.Add(GetPointer(signal_ordermanager)); //--- return(INIT_SUCCEEDED); }
Aqui, declaramos o sign_ordermanager para ser um ponteiro para um novo objeto do tipo SignalOrderManagerExample, que acabamos de definir. Em seguida, fazemos o mesmo para CSignals através do ponteiro de sinais e, em seguida, adicionamos o ponteiro para SignalOrderManagerExample, invocando o método Add.
O uso de CSignal e CSignals em nosso Expert Advisor resultará numa função OnTick muito mais simples:
void OnTick() { //--- symbol_info.RefreshRates(); signals.Check(); if(signals.CheckOpenLong()) { close_last(); Print("Entering buy trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask()); } else if(signals.CheckOpenShort()) { close_last(); Print("Entering sell trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid()); } }
Todos os outros cálculos necessários para a geração do sinal atual são movidos para os objetos CSignal e CSignals. Assim, tudo o que precisamos é fazer com que CSignals realize uma verificação e, em seguida, obtenha seu resultado invocando os métodos CheckOpenLong e CheckOpenShort. As seguintes capturas de tela mostram os resultados do teste dos Expert Advisors sobre as plataformas MetaTrader 4 e MetaTrader 5:
(MT4)
(MT5)
Exemplo #2: Expert Advisor MA
Nosso próximo exemplo envolve o uso do indicador МА na avaliação de sinais de negociação. MA é um indicador padrão para ambas as plataformas, bem com um dos EAs multiplataforma mais fáceis de usar.
Como no exemplo anterior, criamos um sinal personalizado, estendendo CSignal:
class SignalMA: public CSignal { protected: CiMA *m_ma; CSymbolInfo *m_symbol; string m_symbol_name; ENUM_TIMEFRAMES m_timeframe; int m_signal_bar; double m_close; //resto da classe
Como podemos ver, tanto as bibliotecas MQL4 quanto MQL5 já fornecem um objeto de classe para o indicador Moving Average. Isto simplifica a implementação do indicador na nossa classe de sinal personalizada. Apesar do fato de que isso não é necessário, neste exemplo, também armazenamos o símbolo de destino através de m_symbol, um ponteiro para um objeto CSybmolInfo. Também declaramos uma variável m_close, onde o valor do preço de fechamento da barra de sinal será armazenado. O resto dos membros protegidos são os parâmetros para o indicador МА.
O exemplo anterior não havia uma estrutura de dados complexa que pudesse ser preparada antes de ser usada. Neste exemplo, no entanto, ela existe (como indicador) e, portanto, teremos que inicializá-la no construtor da classe:
void SignalMA::SignalMA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int period,const int shift,const ENUM_MA_METHOD method,const ENUM_APPLIED_PRICE applied,const int bar) { m_symbol_name= symbol; m_timeframe = timeframe; m_signal_bar = bar; m_ma=new CiMA(); m_ma.Create(symbol,timeframe,period,0,method,applied); m_indicators.Add(m_ma); }
O sinal obtido da média móvel geralmente envolve a comparação de seu valor com um determinado preço no gráfico. Ele pode ser um preço específico no gráfico, como os preços open ou close, ou os preços atuais bid/ask. Em último caso, precisaremos de um objeto de símbolo com o qual trabalhar. Neste exemplo, também ampliaremos o método Init, de modo a inicializar a obtenção do símbolo correto a ser usado a partir do CSymbolManager, para aqueles que gostariam de usar o os preços bid/ask para comparação, em vez de dados OHLC (e seus derivados).
bool SignalMA::Init(CSymbolManager *symbol_man,CEventAggregator *event_man=NULL) { if(CSignal::Init(symbol_man,event_man)) { if(CheckPointer(m_symbol_man)) { m_symbol=m_symbol_man.Get(); if(CheckPointer(m_symbol)) return true; } } return false; }
O próximo método a ser estendido é o método Сalculate, conforme mostrado no seguinte código:
bool SignalMA::Calculate(void) { double close[]; if(CopyClose(m_symbol_name,m_timeframe,signal_bar,1,close)>0) { m_close=close[0]; return true; } return false; }
Não há mais necessidade de atualizar os dados dos indicadores, já que isso já é executado no método Refresh da classe CSignal. Alternativamente, também podemos implementar o descendente da classe CSignal, a fim de obter o preço de fechamento da barra de sinal usando a classe CCloseBuffer. Ele também é um descendente de CSeries, assim, podemos adicioná-lo ao m_indicators para que a instância CCloseBuffer também seja atualizada juntamente com os outros indicadores. Neste caso, não é necessário expandir mais os métodos Refresh ou Calculate da classe CSignal.
Para este sinal, não há necessidade de expandir adicionalmente o método Update, por isso avançamos para a geração real do próprio sinal. Os trechos de código a seguir mostram os métodos LongCondition e ShortCondition:
bool SignalMA::LongCondition(void) { return m_close>m_ma.Main(m_signal_bar); } bool SignalMA::ShortCondition(void) { return m_close<m_ma.Main(m_signal_bar); }
As condições são muito simples: se o preço de fechamento da barra de sinal for maior que o valor do МА nessa barra, será um sinal de compra. Por outro lado, se o preço de fechamento for menor, então obteremos um sinal de venda.
Semelhante ao exemplo anterior, simplesmente inicializamos todos os outros ponteiros necessários e, em seguida, adicionamos a instância CSignal ao seu contêiner (instância CSignals). O seguinte código mostra o código adicional necessário para a inicialização do sinal em OnInit:
SignalMA *signal_ma=new SignalMA(Symbol(),(ENUM_TIMEFRAMES) Period(),maperiod,0,mamethod,maapplied,signal_bar); signals=new CSignals(); signals.Add(GetPointer(signal_ma)); signals.Init(GetPointer(symbol_manager),NULL);
O seguinte código mostra a função OnTick, que é semelhante à função OnTick no exemplo anterior:
void OnTick() { //--- symbol_info.RefreshRates(); signals.Check(); if(signals.CheckOpenLong()) { close_last(); Print("Entering buy trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask()); } else if(signals.CheckOpenShort()) { close_last(); Print("Entering sell trade.."); order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid()); } }
As seguintes capturas de tela são os resultados do teste realizado pelo Expert Advisor em MT4 e MT5. Como podemos ver, os eles trabalha, segundo a mesma lógica:
(MT4)
(MT5)
Exemplo #3: Expert Advisor HA
Em nosso próximo exemplo, tentaremos usar o indicador Heiken Ashi num EA. Ao contrário do indicador МА, НА é um indicador personalizado, por isso desenvolver um EA com base nele será um pouco mais complicado do que no exemplo anterior, já que também precisaremos declarar a classe para o indicador Heiken Ashi ao expandir CiCustom. Para começar, consideremos a definição de classe para CiHA, de nosso objeto de classe para o indicador HA:
class CiHA: public CiCustom { public: CiHA(void); ~CiHA(void); bool Create(const string symbol,const ENUM_TIMEFRAMES period, const ENUM_INDICATOR type,const int num_params,const MqlParam ¶ms[],const int buffers); double GetData(const int buffer_num,const int index) const; };
Existem dois métodos que precisamos estender, isto é, Create e GetData. Para o método Create, redefinimos o construtor da classe:
bool CiHA::Create(const string symbol,const ENUM_TIMEFRAMES period,const ENUM_INDICATOR type,const int num_params,const MqlParam ¶ms[],const int buffers) { NumBuffers(buffers); if(CIndicator::Create(symbol,period,type,num_params,params)) return Initialize(symbol,period,num_params,params); return false; }
Aqui nós declaramos o número de buffers que deve ter o indicador, em seguida, inicializamos os parâmetros enviados para eles. Os parâmetros do indicador são armazenados numa estrutura (MqlParam).
Para o método GetData, as duas línguas diferem na implementação. No MQL4, é feita uma chamada direta para a função iCustom que dá o valor do indicador numa barra individual, no gráfico. No МQL5 a chamada do indicador é processada de forma diferente. iCustom dá o identificador ao indicador (aproximadamente semelhante ao que é feito nas operações de arquivo). Para obter o valor do indicador - no MetaTrader 5 - de uma determinada barra, deve-se usar esse identificador, e não a chamada da função iCustom. Neste caso, nós dividimos a implementação:
double CiHA::GetData(const int buffer_num,const int index) const { #ifdef __MQL5__ return CiCustom::GetData(buffer_num,index); #else return iCustom(m_symbol,m_period,m_params[0].string_value,buffer_num,index); #endif }
Observe que neste método, para a versão MQL5, estamos simplesmente retornando o resultado de uma chamada ao método pai (CiCustom). Por outro lado, no MQL4, o método pai (CiCustom) retorna apenas zero e, portanto, temos que estendê-lo ao fazer a chamada para a função MQL4 iCustom. Uma vez que esta função MQL4 não usa uma estrutura (MqlParams) para armazenar os parâmetros do indicador, sua chamada quase sempre será diferente para cada indicador personalizado.
Na extensão do CSignal para este EA, não há muita diferença em comparação com os exemplos anteriores. Para o construtor, simplesmente redefinimos os argumentos do método, a fim de acomodar os parâmetros dos indicadores necessários para a avaliação do sinal. Para este sinal particular, usamos apenas um indicador:
void SignalHA::SignalHA(const string symbol,const ENUM_TIMEFRAMES timeframe,const int numparams,const MqlParam ¶ms[],const int bar) { m_symbol_name= symbol; m_signal_bar = bar; m_ha=new CiHA(); m_ha.Create(symbol,timeframe,IND_CUSTOM,numparams,params,4); m_indicators.Add(m_ha); }
Para o método Calculate, também precisamos dividir a implementação, já que os indicadores Heiken Ashi para MetaTrader 4 e MetaTrader 5 diferem no arranjo de seus buffers. Para o primeiro, são Low/High, High/Low, Open e Close que ocupam o primeiro (buffer 0), segundo, terceiro e quarto buffers. Por outro lado, para a versão MQL5, o arranjo é Open, High, Low e Close. Assim, devemos considerar o buffer particular para acessar, a fim de obter um valor do indicador, dependendo da plataforma que está sendo usada:
bool SignalHA::Calculate(void) { #ifdef __MQL5__ m_open=m_ha.GetData(0,signal_bar); #else m_open=m_ha.GetData(2,signal_bar); #endif m_close=m_ha.GetData(3,signal_bar); return true; }
Para a versão MQL5, o preço de abertura de vela do HA é tomado do primeiro buffer (buffer 0), enquanto para a versão MQL4, ele é armazenado no terceiro buffer (buffer 2). O preço de fechamento da vela do HA pode ser encontrado no quarto buffer (buffer 3) para ambas as versões, então colocamos a declaração fora da declaração do pré-processador.
Para a avaliação dos sinais, sempre temos que atualizar os métodos LongCondition e ShortCondition, dependendo do critério particular que será usado para avaliar os valores armazenados. Para isso, usamos o trabalho típico com Heiken Ashi ao verificar se a barra de sinal é de alta ou de baixa:
bool SignalHA::LongCondition(void) { return m_open<m_close; } bool SignalHA::ShortCondition(void) { return m_open>m_close; }
A função OnTick para este Expert Advisor será semelhante à dos exemplos anteriores, então avançamos com a função OnInit:
int OnInit() { //--- order_manager=new COrderManager(); symbol_manager=new CSymbolManager(); symbol_info=new CSymbolInfo(); if(!symbol_info.Name(Symbol())) Print("symbol not set"); symbol_manager.Add(GetPointer(symbol_info)); order_manager.Init(symbol_manager,NULL); 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); signals=new CSignals(); signals.Add(GetPointer(signal_ha)); signals.Init(GetPointer(symbol_manager),NULL); //--- return(INIT_SUCCEEDED); }
Aqui vemos que a localização do arquivo ex4 do indicador Heiken Ashi é diferente dependendo da plataforma de negociação utilizada. Como o MqlParams exige que o primeiro parâmetro a ser armazenado deve ser o nome do indicador personalizado (sem a extensão), mais uma vez, precisamos dividir a implementação especificando o primeiro parâmetro. No MQL5, o indicador pode ser encontrado por padrão em"Indicators\Examples\Heiken Ashi" enquanto no MQL4, o indicador pode ser encontrado em "Indicators\Heiken Ashi".
As seguintes capturas de tela mostram os resultados de teste do Expert Advisor no MetaTrader 4 e no MetaTrader 5. Conforme mostrado, embora os indicadores variem de alguma forma na plotagem no gráfico, podemos ver que ambos têm a mesma lógica e que o Expert Advisor para ambas as versões consegue executar com base na mesma lógica:
(MT4)
(MT5)
Exemplo #4: Expert Advisor com base no HA e MA
Nosso último exemplo é a combinação dos indicadores MA e HA para serem incluídos no EA. Não há muitas diferenças neste exemplo. Simplesmente adicionamos as definições de classe encontradas nos 2º e 3º exemplos, e depois adicionamos os ponteiros para instâncias CSignalMA e CSignalHA, na instância CSignals. O seguinte mostra um resultado de teste usando este Expert Advisor.
(MT4)
(MT5)
Fim do artigo
Neste artigo, discutimos as classes CSignal e CSignals, que são os objetos de classe a serem usados por um Expert Advisor multiplataforma, a fim de avaliar o sinal geral, num determinado tick. As classes mencionadas foram projetadas para que os processos envolvidos na avaliação de sinais sejam separadas do resto do código do Expert Advisor.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/3261
- 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