Expert Advisor Universal: Estratégias Personalizadas e Classes Auxiliares de Negociação (Parte 3)
Conteúdo
- Introdução
- Logs, CMessage e CLog Classes, Padrão Singleton
- Acessando Cotações Usando os Índices do MetaTrader 4
- Utilizando Indicadores Orientados a Objetos
- Métodos a Serem Substituídos pelo Expert Advisor Personalizado
- Exemplo de Expert Advisor de Negociação com Duas Médias Móveis
- Exemplo de Expert Advisor Baseado na Ruptura de Canais das Bandas Bollinger
- Carregando Estratégias Personalizadas ao Mecanismo de Negociação
- Conclusão
Introdução
Nesta parte, continuamos a discutir o mecanismo de negociação CStrategy. A seguir um breve resumo do conteúdo das duas partes anteriores: No primeiro artigo sobre o Expert Advisor Universal: Modos de Negociação das Estratégias , foi discutido os modos de negociação com detalhes que permitem a configuração lógica do Expert Advisor, de acordo com o tempo e o dia da semana. No segundo artigo Expert Advisors Universal: O Modelo de Evento e o Protótipo da Estratégia de Negociação, analisamos o modelo de evento baseado em eventos centralizados de manipulação, bem como os principais algoritmos subjacentes da classe básica CStrategy dos Expert Advisors personalizados.
Na terceira parte da série, vamos descrever em detalhe os exemplos dos Expert Advisors baseadso no motor de negociação CStrategy e alguns algoritmos auxiliares que podem ser necessários no desenvolvimento do EA. É dada especial atenção ao processo de registro. Apesar de seu papel puramente de apoio, o registro é um elemento muito importante de qualquer sistema complexo. Um bom registrador permite compreender rapidamente a causa do problema e encontrar o lugar onde ocorreu. Este registrador é escrito usando uma técnica de programação especial chamada padrão Singleton. Informações sobre ele serão interessante não apenas a aqueles que organizam o processo de negociação, mas também aos que criam algoritmos de execuçao das tarefas fora do padrão.
Além disso, o artigo descreve os algoritmos que permitem acesso aos dados de mercado através dos índices de conveniências e intuitivos. Na verdade, o acesso aos dados através de índices como Close[1] e High[0] é uma característica popular do MetaTrader 4. Então por que evitar este acesso, se ele também pode ser usado no MetaTrader 5? Este artigo explica como fazer isso, descrevendo em detalhes os algoritmos que implementam a idéia acima.
Para terminar a conclusão, eu gostaria de usar as palavras do meu artigo anterior. O motor de negociação CStrategy com todos os seus algoritmos é um conjunto bastante complexo. No entanto, a compreensão completa e profunda do seu princípio de funcionamento não é necessária. Você só deve compreender os princípios gerais e a funcionalidade do motor de negociação. Portanto, se qualquer uma das partes do artigo não estiver clara, você pode ignorá-la. Este é um dos princípios fundamentais da abordagem orientada a objetos: você pode usar um sistema complexo sem conhecer sua estrutura.
Logs, CMessage e CLog Classes, Padrão Singleton
O registro é uma das tarefas tradicionais auxiliares. Como regra geral, aplicações simples usam as funções comuns Print ouprintfque imprimem uma mensagem de erro no terminal MetaTrader 5:
... double closes[]; if(CopyClose(Symbol(), Period(), 0, 100, closes) < 100) printf("Not enough data."); ...
No entanto, esta abordagem simples nem sempre é o suficiente para entender o que está acontecendo em grandes programas complexos que contêm centenas de linhas de código-fonte. Portanto, a melhor solução para tais tarefas é desenvolver um módulo de registro especial - a classe CLog.
O método mais óbvio do registrador é o AddMessage(). Por exemplo, se o Log é um objeto do nosso CLog, podemos escrever a seguinte construção:
Log.AddMessage("Aviso! O número recebido de barras é menor do que o necessário");
No entanto, o alerta enviado contém informações muito menos úteis em relaçao ao que é necessário a depuração. Como você sabe a partir de qual mensagem ele foi criado? Em qual função foi criado? Como você pode saber o tipo de informação importante contida nele? Para evitar isso, precisamos expandir a noção da mensagem. Além de texto, cada mensagem deve conter os seguintes atributos:
- tempo de criação
- fonte da mensagem
- tipo de mensagem (informações, aviso, mensagem de erro)
Também seria útil, se a sua mensagem contivesse alguns detalhes adicionais:
- ID de erro do sistema
- ID de erro de negociação (se uma ação de negociação foi realizada)
- hora do servidor de negociação a partir do momento da criação da mensagem
Todas estas informações podem ser convenientemente combinadas na classe especial CMessage. Já que a nossa mensagem é uma classe, você pode facilmente adicionar mais dados e métodos para trabalhar com os registros. Aqui está o cabeçalho de classe:
//+------------------------------------------------------------------+ //| Logs.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Object.mqh> #include <Arrays\ArrayObj.mqh> #define UNKNOW_SOURCE "unknown" // Uma fonte desconhecida de mensagens //+------------------------------------------------------------------+ //| Tipo de mensagem | //+------------------------------------------------------------------+ enum ENUM_MESSAGE_TYPE { MESSAGE_INFO, // Mensagem de informação MESSAGE_WARNING, // Mensagem de aviso MESSAGE_ERROR // Mensagem de erro }; //+------------------------------------------------------------------+ //| Uma mensagem transmitida à classe logger (registrador) | //+------------------------------------------------------------------+ class CMessage : public CObject { private: ENUM_MESSAGE_TYPE m_type; // Tipo de mensagem string m_source; // Fonte da mensagem string m_text; // Texto da mensagem int m_system_error_id; // Cria uma ID de um erro do SISTEMA int m_retcode; // Contém um código de retorno do servidor de negociação datetime m_server_time; // Hora do servidor de negociação no momento da criação da mensagem datetime m_local_time; // Hora local no momento da criação da mensagem void Init(ENUM_MESSAGE_TYPE type,string source,string text); public: CMessage(void); CMessage(ENUM_MESSAGE_TYPE type); CMessage(ENUM_MESSAGE_TYPE type,string source,string text); void Type(ENUM_MESSAGE_TYPE type); ENUM_MESSAGE_TYPE Type(void); void Source(string source); string Source(void); void Text(string text); string Text(void); datetime TimeServer(void); datetime TimeLocal(); void SystemErrorID(int error); int SystemErrorID(); void Retcode(int retcode); int Retcode(void); string ToConsoleType(void); string ToCSVType(void); };
Em primeiro lugar o cabeçalho deve conter o ENUM_MESSAGE_TYPE. Ele define o tipo da mensagem a ser criada. A mensagem pode ser informacional (MESSAGE_INFO), alerta (MESSAGE_WARNING) e notifição de um erro (MESSAGE_ERROR)
A classe consiste de vários métodos Get/Set que definem ou leem vários atributos de uma mensagem. Para a criar uma mensagem fácil em apenas uma linha, o CMessage proporciona um construtor sobrecarregado correspondente que deve ser chamado com os parâmetros que definem o texto da mensagem, o seu tipo e a fonte. Por exemplo, se precisamos criar uma mensagem de aviso com a função OnTick notificando uma pequena quantidade de dados carregados, pode ser feito da seguinte maneira:
void OnTick(void) { double closes[]; if(CopyClose(Symbol(),Period(),0,100,closes)<100) CMessage message=new CMessage(MESSAGE_WARNING,__FUNCTION__,"Not enough data"); }
Esta mensagem contém mais informação do que a anterior. Além da própria mensagem que contém o nome da função que a chamou e o tipo de mensagem, também possui dados que você não necessita preencher durante a criação. Por exemplo, o objeto message contém o tempo de criação de mensagem e o código atual do erro de negociação, se por acaso houver algum.
Agora é hora de considerar o registrador CLog. A classe serve como um armazenamento de mensagens CMessage. Uma de suas funções mais interessantes é o envio de notificações push para terminais móveis usando a função SendNotification. Esta é uma característica extremamente útil quando o monitoramento constante de operação do Expert Advisor é impossível. Em vez disso, podemos enviar impulsos para notificar o usuário que algo deu errado.
A característica específica do registro é que ele deve ser um único processo a todas as partes do programa. Seria estranho se cada função ou classe tivesse seu próprio mecanismo de registro. Portanto, a classe CLog é implementada usando um padrão de programação especial chamado Singleton. Este padrão assegura que existe apenas uma cópia de um certo tipo de objeto. Por exemplo, se o programa utiliza dois apontadores, ambos se referindo ao objeto do tipo CLog, os ponteiros irão apontar para o mesmo objeto. Um objeto é realmente criado e excluído nos bastidores, em métodos privados da classe.
Vamos considerar o título desta classe e os métodos que implementam o padrão Singleton:
//+------------------------------------------------------------------+ //| Logs.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Object.mqh> #include <Arrays\ArrayObj.mqh> #include "Message.mqh" //+------------------------------------------------------------------+ //| A classe implementa o registro de mensagens como Singleton | //+------------------------------------------------------------------+ class CLog { private: static CLog* m_log; // Um ponteiro para a amostra estática global CArrayObj m_messages; // A lista de mensagens salvas bool m_terminal_enable; // Verdadeiro se você precisa imprimir a mensagem recebida ao terminal de negociação bool m_push_enable; // Se for verdade, envia notificações push ENUM_MESSAGE_TYPE m_push_priority; // Contém a prioridade específica de exibição da mensagem na janela do terminal ENUM_MESSAGE_TYPE m_terminal_priority; // Contém a prioridade específica de envio de mensagens push a dispositivos móveis bool m_recursive; // Uma flag que indica a chamada recursiva do processo de destruição bool SendPush(CMessage* msg); void CheckMessage(CMessage* msg); CLog(void); // Construtor privado string GetName(void); void DeleteOldLogs(int day_history = 30); void DeleteOldLog(string file_name, int day_history); ~CLog(void){;} public: static CLog* GetLog(void); // O método para receber um objeto estático bool AddMessage(CMessage* msg); void Clear(void); bool Save(string path); CMessage* MessageAt(int index)const; int Total(void); void TerminalEnable(bool enable); bool TerminalEnable(void); void PushEnable(bool enable); bool PushEnable(void); void PushPriority(ENUM_MESSAGE_TYPE type); ENUM_MESSAGE_TYPE PushPriority(void); void TerminalPriority(ENUM_MESSAGE_TYPE type); ENUM_MESSAGE_TYPE TerminalPriority(void); bool SaveToFile(void); static bool DeleteLog(void); }; CLog* CLog::m_log;
A classe CLog armazena um ponteiro para o objeto estático de si mesmo como um membro privado. Pode parecer uma construção de programação estranha, mas faz sentido. O único construtor de classe é privado e não pode ser chamado. Em vez de chamar o construtor, o método GetLog pode ser usado:
//+------------------------------------------------------------------+ //| Retorna o objeto do registrador (logger) | //+------------------------------------------------------------------+ static CLog* CLog::GetLog() { if(CheckPointer(m_log) == POINTER_INVALID) m_log = new CLog(); return m_log; }
Ele verifica se o ponteiro estático aponta a um objeto CLog existente e, em caso afirmativo, retorna uma referência a ele. Caso contrário, ele cria um novo objeto e associa o ponteiro interno m_log com ele. Isto significa que o objeto é criado apenas uma vez. Durante mais chamadas do método GetLog, um objeto criado anteriormente será devolvido.
A exclusão do objeto também é feita apenas uma vez. Isso é feito usando o método DeleteLog:
//+------------------------------------------------------------------+ //| Exclui o objeto do registrador (logger) | //+------------------------------------------------------------------+ bool CLog::DeleteLog(void) { bool res = CheckPointer(m_log) != POINTER_INVALID; if(res) delete m_log; return res; }
Se o m_log existe, ele será excluído e se verdadeiro será retornado.
Pode parecer que o sistema de registro descrito é complexo, porém seus recursos suportados são bastante impressionantes. Por exemplo, você pode classificar as mensagens por tipo, ou enviá-los como notificações push. Um usuário, em última instância, decide se utiliza este sistema ou não. Ele é implementado em módulos separados como Message.mqh e Logs.mqh, para usá-los tanto separadamente do projeto descrito, como no seu interior.
Acessando Cotações Usando os Índices do MetaTrader 4
Uma das principais mudanças no MetaTrader 5, em comparação com o seu antecessor, é um modelo de acesso as cotações e dados do indicador. Por exemplo, se for necessário descobrir o preço final da barra atual, você poderia fazê-lo no MetaTrader 4, adicionando o seguinte procedimento:
double close = Close[0];
Isso significa que você poderia acessar os dados quase que diretamente através da indexação de uma série de tempo adequado. Porém no MetaTrader 5 outras ações são necessárias para encontrar o nosso preço de fechamento da barra atual:
- Definir um array receptor para copiar a quantidade necessária das cotações.
- Copie as cotações necessárias usando uma das funções do grupo Copy* (funções de acesso a séries temporais e dados do indicador).
- Referem-se ao índice requerido do array copiado.
Para encontrar o nosso preço de fechamento da barra atual no MetaTrader 5, as seguintes ações são necessárias:
double closes[]; double close = 0.0; if(CopyClose(Symbol(), Period(), 0, 1, closes)) close = closes[0]; else printf("Falha ao copiar o preço de fechamento.");
Este acesso aos dados é mais difícil do que no MetaTrader 4. No entanto, esta abordagem torna o acesso de dados universal: a mesma interface e mecanismos unificados são usados para acessar os dados recebidos de diferentes símbolos e indicadores.
Embora não seja exigido em tarefas diárias como uma regra. Muitas vezes somente precisamos obter o último valor do símbolo atual. Podendo ser Abertura (Open) ou Fechamento (Close) dos preços de uma barra, bem como a Máxima (High) ou Mínima (Low). De qualquer forma, seria conveniente usar o modelo de acesso aos dados adotado no MetaTrader 4. Devido à natureza orientada a objeto do MQL5, é possível criar classes especiais com um indexador que pode ser usado da mesma maneira para acessar dados de negociação, como no MetaTrader 4. Por exemplo, para obter o Fechamento (Close) do preço no MetaTrader 5:
double close = Close[0];
Devemos adicionar o seguinte invólucro para fechamento dos preços:
//+------------------------------------------------------------------+ //| Acesso ao Fechamento (Close) dos preços da barra do símbolo | //+------------------------------------------------------------------+ class CClose : public CSeries { public: double operator[](int index) { double value[]; if(CopyClose(m_symbol, m_timeframe, index, 1, value) == 0)return 0.0; return value[0]; } };
O mesmo código deve ser escrito à outra série, incluindo o tempo, volume, bem como a abertura, máxima e mínima dos preços. É claro que, em alguns casos, o código pode ser executado mais lentamente do que a cópia de uma só vez do array exigido nas cotações, usando as funções Copy* do sistema. No entanto, como mencionado acima, muitas vezes precisamos acessar apenas o último elemento, pois todos os elementos anteriores são levados em conta ao mover a janela
Este conjunto simples de classes está incluído no arquivo Series.mqh. Fornece uma interface conveniente para acessar as cotações como no MetaTrader 4 e usadas ativamente no mecanismo de negociação.
Uma característica distintiva destas classes é a sua independência de plataforma. Por exemplo, no MetaTrader 5 um Expert Advisor pode chamar o método de uma destas classes "pensando" na referência direta das cotações. Este método de acesso também irá funcionar no MetaTrader 4, mas em vez do invólucro especializado, acessará diretamente as séries do sistema, tais como Open, High, Low ou Close.
Utilizando Indicadores Orientados a Objetos
Quase todos os indicadores tem um número de definição para a configuração. O trabalho com indicadores no MetaTrader 5 é semelhante ao trabalho com cotações, a única diferença é a necessidade de criar uma chamada ao handle do indicador, ou seja, um ponteiro especial para alguns objetos internos do MetaTrader 5 contendo valores de cálculo, antes devem ser copiados os dados do indicador. Os parâmetros do indicador são definidos no momento da criação do handle. Se você precisa editar um dos parâmetros do indicador, por algum motivo, você deve excluir o handle antigo do indicador e criar um novo com parâmetros atualizados. Os parâmetros do indicador devem ser armazenados num local externo, por exemplo, nas variáveis do Expert Advisor.
Como resultado, a maioria das operações com indicadores são transmitidas ao Expert Advisor. Nem sempre é conveniente. Vamos considerar um exemplo simples: uma negociação do Expert Advisor usando os sinais de cruzamento da Média Móvel. Apesar de sua simplicidade, o indicador de Média Móvel tem seis parâmetros a serem definidos:
- O símbolo para o cálculo da Média Móvel
- O prazo ou período do gráfico
- O período médio
- O tipo de Média Móvel (simples, exponencial, ponderada, etc.)
- Deslocamento do indicador a partir da barra de preço
- Preço aplicado (um dos preços OHLC de uma barra ou um buffer de cálculo de outro indicador
Portanto, se desejamos escrever uma negociação do Expert Advisor, a intersecção de duas médias usa uma lista completa de configurações da MA, serão doze parâmetros - seis parâmetros para a média móvel rápida e mais seis para a lenta. Além disso, se o trader alterar o timeframe ou símbolo do gráfico onde o EA está sendo executado, entao os handles usados nos indicadores utilizados também terão serão reinicializados.
Para liberar o Expert Advisor de tarefas relacionadas aos handles do indicador, devemos usar as versões orientadas a objetos dos indicadores. Usando as classes orientadas a objeto do indicador, podemos escrever construções como:
CMovingAverageExp MAExpert; // Criando um EA que negocia com base em duas médias móveis. //+------------------------------------------------------------------+ //| função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- Configurando a Média Móvel rápida do Expert Advisor MAExpert.FastMA.Symbol("EURUSD"); MAExpert.FastMA.Symbol(PERIOD_M10); MAExpert.FastMA.Period(13); MAExpert.FastMA.AppliedPrice(PRICE_CLOSE); MAExpert.FastMA.MaShift(1); //--- Configurando a Média Móvel lenta do Expert Advisor MAExpert.SlowMA.Symbol("EURUSD"); MAExpert.SlowMA.Symbol(PERIOD_M15); MAExpert.SlowMA.Period(15); MAExpert.SlowMA.AppliedPrice(PRICE_CLOSE); MAExpert.SlowMA.MaShift(1); return(INIT_SUCCEEDED); }
O usuário final apenas define os parâmetros dos indicadores utilizados pelo Expert Advisor. O Expert Advisor só lerá dados a partir deles.
Há uma outra vantagem importante da utilização de objetos do indicador. Indicadores orientados a objetos escondem a sua implementação. Isso significa que eles podem calcular os seus valores por conta própria ou usando handles apropriados. Quando vários indicadores de cálculo são usados, sendo necessário alta velocidade de execução, é aconselhável adicionar a unidade de cálculo de indicadores diretamente no Expert Advisor. Graças à abordagem orientada a objeto, isso pode ser feito sem reescrever o Expert Advisor. Você somente precisa calcular os valores do indicador dentro da classe apropriada sem o uso de handles.
Para ilustrar o que foi explicado acima, a seguir veja o código de fonte da classe CIndMovingAverage que se baseia no sistema do indicador iMA:
//+------------------------------------------------------------------+ //| MovingAverage.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Strategy\Message.mqh> #include <Strategy\Logs.mqh> //+------------------------------------------------------------------+ //| definição | //+------------------------------------------------------------------+ class CIndMovingAverage { private: int m_ma_handle; // handle do Indicador ENUM_TIMEFRAMES m_timeframe; // Timeframe int m_ma_period; // Período int m_ma_shift; // Deslocamento string m_symbol; // Símbolo ENUM_MA_METHOD m_ma_method; // Método da Média Móvel uint m_applied_price; // O handle do indicador para calcular o valor Média Móvel, // ou um dos valores de preços do ENUM_APPLIED_PRICE CLog* m_log; // Registrador void Init(void); public: CIndMovingAverage(void); /*Params*/ void Timeframe(ENUM_TIMEFRAMES timeframe); void MaPeriod(int ma_period); void MaShift(int ma_shift); void MaMethod(ENUM_MA_METHOD method); void AppliedPrice(int source); void Symbol(string symbol); ENUM_TIMEFRAMES Timeframe(void); int MaPeriod(void); int MaShift(void); ENUM_MA_METHOD MaMethod(void); uint AppliedPrice(void); string Symbol(void); /*Out values*/ double OutValue(int index); }; //+------------------------------------------------------------------+ //| Construtor padrão. | //+------------------------------------------------------------------+ CIndMovingAverage::CIndMovingAverage(void) : m_ma_handle(INVALID_HANDLE), m_timeframe(PERIOD_CURRENT), m_ma_period(12), m_ma_shift(0), m_ma_method(MODE_SMA), m_applied_price(PRICE_CLOSE) { m_log=CLog::GetLog(); } //+------------------------------------------------------------------+ //| Inicialização. | //+------------------------------------------------------------------+ CIndMovingAverage::Init(void) { if(m_ma_handle!=INVALID_HANDLE) { bool res=IndicatorRelease(m_ma_handle); if(!res) { string text="Realise iMA indicator failed. Error ID: "+(string)GetLastError(); CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); m_log.AddMessage(msg); } } m_ma_handle=iMA(m_symbol,m_timeframe,m_ma_period,m_ma_shift,m_ma_method,m_applied_price); if(m_ma_handle==INVALID_HANDLE) { string params="(Period:"+(string)m_ma_period+", Shift: "+(string)m_ma_shift+ ", MA Method:"+EnumToString(m_ma_method)+")"; string text="Create iMA indicator failed"+params+". Error ID: "+(string)GetLastError(); CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text); m_log.AddMessage(msg); } } //+------------------------------------------------------------------+ //| Definindo o timeframe. | //+------------------------------------------------------------------+ void CIndMovingAverage::Timeframe(ENUM_TIMEFRAMES tf) { m_timeframe=tf; if(m_ma_handle!=INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Retorna o timeframe atual. | //+------------------------------------------------------------------+ ENUM_TIMEFRAMES CIndMovingAverage::Timeframe(void) { return m_timeframe; } //+------------------------------------------------------------------+ //| Define o período médio da Média Móvel | //+------------------------------------------------------------------+ void CIndMovingAverage::MaPeriod(int ma_period) { m_ma_period=ma_period; if(m_ma_handle!=INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Retorna o período médio atual da Média Móvel. | //+------------------------------------------------------------------+ int CIndMovingAverage::MaPeriod(void) { return m_ma_period; } //+------------------------------------------------------------------+ //| Define o tipo de Média Móvel. | //+------------------------------------------------------------------+ void CIndMovingAverage::MaMethod(ENUM_MA_METHOD method) { m_ma_method=method; if(m_ma_handle!=INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Retorna o tipo de Média Móvel. | //+------------------------------------------------------------------+ ENUM_MA_METHOD CIndMovingAverage::MaMethod(void) { return m_ma_method; } //+------------------------------------------------------------------+ //| Retorna o deslocamento da Média Móvel. | //+------------------------------------------------------------------+ int CIndMovingAverage::MaShift(void) { return m_ma_shift; } //+------------------------------------------------------------------+ //| Define deslocamento da Média Móvel. | //+------------------------------------------------------------------+ void CIndMovingAverage::MaShift(int ma_shift) { m_ma_shift=ma_shift; if(m_ma_handle!=INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Define o tipo do preço utilizado no cálculo da MA. | //+------------------------------------------------------------------+ void CIndMovingAverage::AppliedPrice(int price) { m_applied_price = price; if(m_ma_handle != INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Retorna o tipo do preço utilizado no cálculo da MA. | //+------------------------------------------------------------------+ uint CIndMovingAverage::AppliedPrice(void) { return m_applied_price; } //+------------------------------------------------------------------+ //| Define o símbolo para calcular o indicador para | //+------------------------------------------------------------------+ void CIndMovingAverage::Symbol(string symbol) { m_symbol=symbol; if(m_ma_handle!=INVALID_HANDLE) Init(); } //+------------------------------------------------------------------+ //| Retorna o símbolo do indicador calculado para | //+------------------------------------------------------------------+ string CIndMovingAverage::Symbol(void) { return m_symbol; } //+------------------------------------------------------------------+ //| Retorna o valor do MA com o índice 'index' | //+------------------------------------------------------------------+ double CIndMovingAverage::OutValue(int index) { if(m_ma_handle==INVALID_HANDLE) Init(); double values[]; if(CopyBuffer(m_ma_handle,0,index,1,values)) return values[0]; return EMPTY_VALUE; }
A classe é bastante simples. Sua tarefa principal é reinicializar o indicador se um dos seus parâmetros for alterado, bem como retornar o valor calculado pelo index. Um handle é reinicializado utilizando o método Init e o valor necessário é retornado usando OutValue. Métodos que retornam um dos valores do indicador começam com o prefixo Out. Isso facilita a busca pelo método necessário ao programar em editores que oferecem substituição intelectual dos parâmetros, tais como o MetaEditor.
O pacote do motor de negociação inclui uma série de indicadores orientados a objetos. Isso ajudará você a entender como eles funcionam e criar suas próprias versões orientadas a objetos com os indicadores clássicos. A seção referente ao desenvolvimento customizado dos Expert Advisors ilustra os princípios de trabalho com eles.
Métodos a Serem Substituídos pelo Expert Advisor Personalizado
No primeiro artigo do Expert Advisor Universal: Modos de Negociação das Estratégias (Parte 1), consideramos em detalhes os modos de negociação de uma estratégia e seus principais métodos a serem substituídos. Agora, é hora da prática.
Cada Expert Advisor criado usando o motor CStrategy deve substituir métodos virtuais que são responsáveis por algumas propriedades e comportamento do Expert Advisor. Vamos listar todos os métodos a ser substituídos como uma tabela de três colunas. A primeira contém o nome do método virtual, a segunda apresenta o evento ou ação a ser rastreado ou realizado. A terceira coluna contém a descrição da finalidade da utilização do método. Então aqui está a tabela:
Método virtual | Evento/Ação | Propósito |
---|---|---|
OnSymbolChanged | Chamado quando o nome de um símbolo de negociação for alterado. | Quando alterar o instrumento de negociação, os indicadores do EA devem ser reinicializados. O evento permite realizar a reinicialização dos indicadores do Expert Advisor. |
OnTimeframeChanged | Mudança do timeframe do gráfico | Quando alterar o timeframe de trabalho no gráfico, os indicadores do EA devem ser reinicializados. O evento permite realizar a reinicialização dos indicadores do Expert Advisor. |
ParseXmlParams | Analisa os parâmetros personalizados da estratégia carregados através de um arquivo XML. | A estratégia deve reconhecer parâmetros XML transmitidos ao método e configurar suas definições em conformidade. |
ExpertNameFull | Retorna o nome completo do Expert Advisor | O nome completo do Expert Advisor consiste no nome de estratégia e, como regra, um único conjunto de parâmetros de estratégia. Uma instância da estratégia deve determinar de forma independente o seu nome completo. Este nome também é usado no painel visual, na lista suspensa Agent. |
OnTradeTransaction | Ocorre no caso de um evento de negociação | Algumas estratégias precisam analisar eventos de negociação para a operação adequada. Permite a transmissão e análise de um evento de negociação no Expert Advisor. |
InitBuy | Inicia uma operação de Compra | Um dos métodos básicos que deve ser substituído. Neste método, você deve executar uma operação de Compra, se as condições de negocição adequadas se formarem. |
InitSell | Inicia uma operação de Venda | Um dos métodos básicos que deve ser substituído. Neste método, você deve executar uma operação de Venda, se as condições de negociação adequadas se formarem. |
SupportBuy | Gerencia uma posição de compra aberta anteriormente | Uma posição de compra aberta precisa ser gerenciada. Por exemplo, você deve definir Stop Loss ou fechar a posição com um sinal de saída da posição. Todos estes passos devem ser realizados neste método. |
SupportSell | Gerencia uma posição de venda aberta anteriormente | Uma posição de venda aberta precisa ser gerenciada. Por exemplo, você deve definir Stop Loss ou fechar a posição com um sinal de saída da posição. Todos estes passos devem ser realizados neste método. |
Tabela 1. Métodos virtuais e seus efeitos
Os métodos mais importantes que você deve substituir são: InitBuy, InitSell, SupportBuy e SupportSell. Eles são mostrados em negrito na tabela. Caso se esqueça de substituir, por exemplo, o InitBuy, a estratégia customizada não vai comprar. Se você não substituir um dos métodos "Support", uma posição poderá ficar aberta para sempre. Portanto, ao criar um Expert Advisor, tenha cuidado substituir esses métodos.
Se você deseja que o mecanismo de negociação carregue automaticamente uma estratégia a partir de um arquivo XML e configure seus parâmetros de acordo com as definições fornecidas no arquivo, você também precisará substituir o método ParseXmlParams. Neste método, uma estratégia deve determinar os parâmetros que são transmitidos a ela e entender como alterar as suas próprias definições de acordo com estes parâmetros. Trabalhar com parâmetros XML será descrito em mais detalhe na quarta parte da série: Expert Advisor Universal: Negociação em Grupo e Gestão de uma Carteira de Estratégias (Parte 4)". Um exemplo de subustituição ParseXmlParams está incluído na lista de estratégias com base no indicador Bollinger Bands.
Exemplo de Expert Advisor de Negociação com Duas Médias Móveis
Agora é hora de criar o nosso primeiro Expert Advisor usando as possibilidades do CStrategy. Para fazer o código fonte simples e compacto, não vamos usar a função de registro nele. Vamos descrever brevemente as ações que precisam ser executadas no nosso Expert Advisor:
- Ao mudar o timeframe e o símbolo, alterar as configurações das médias móveis rápidas e lentas, substituindo os métodos OnSymbolChanged e OnTimeframeChanged.
- Métodos de substituição InitBuy, InitSell, SupportBuy e SupportSell. Definir a lógica de negociação do EA nestes métodos (abertura de posição e regras de gestão).
O resto do trabalho do EA deve ser realizado pelo mecanismo de negociação e dos indicadores utilizados pelo EA. Aqui está o código-fonte do Expert Advisor:
//+------------------------------------------------------------------+ //| Samples.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Strategy\Strategy.mqh> #include <Strategy\Indicators\MovingAverage.mqh> //+------------------------------------------------------------------+ //| Um exemplo de estratégia clássica baseada em duas Médias Móveis: | //| Se a MM rápida cruza a lenta de baixo para cima, compramos. | //| Se de cima para baixo-nós vendemos. | //+------------------------------------------------------------------+ class CMovingAverage : public CStrategy { private: bool IsTrackEvents(const MarketEvent &event); protected: virtual void InitBuy(const MarketEvent &event); virtual void InitSell(const MarketEvent &event); virtual void SupportBuy(const MarketEvent &event,CPosition *pos); virtual void SupportSell(const MarketEvent &event,CPosition *pos); virtual void OnSymbolChanged(string new_symbol); virtual void OnTimeframeChanged(ENUM_TIMEFRAMES new_tf); public: CIndMovingAverage FastMA; // media móvel rápida CIndMovingAverage SlowMA; // média movel lenta CMovingAverage(void); virtual string ExpertNameFull(void); }; //+------------------------------------------------------------------+ //| Inicialização. | //+------------------------------------------------------------------+ CMovingAverage::CMovingAverage(void) { } //+------------------------------------------------------------------+ //| Resposta a alteração do símbolo | //+------------------------------------------------------------------+ void CMovingAverage::OnSymbolChanged(string new_symbol) { FastMA.Symbol(new_symbol); SlowMA.Symbol(new_symbol); } //+------------------------------------------------------------------+ //| Resposta a alteração do timeframe | //+------------------------------------------------------------------+ void CMovingAverage::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf) { FastMA.Timeframe(new_tf); SlowMA.Timeframe(new_tf); } //+------------------------------------------------------------------+ //| Comprar quando a MM rápida está acima da lenta | //+------------------------------------------------------------------+ void CMovingAverage::InitBuy(const MarketEvent &event) { if(!IsTrackEvents(event))return; // Manipula apenas o evento necessário! if(positions.open_buy > 0) return; // Se houver pelo menos uma posição aberta, não há necessidade de comprar if(FastMA.OutValue(1) > SlowMA.OutValue(1)) // Se existem posições de compra abertas, verifica se a MM rápida esta acima da lenta: Trade.Buy(MM.GetLotFixed(), ExpertSymbol(), ""); // Se for acima, compre. } //+------------------------------------------------------------------+ //| Fechar a posição de compra quando a MM rápida está abaixo da | //| mais lenta. | //+------------------------------------------------------------------+ void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))return; // Manipula apenas o evento necessário! if(FastMA.OutValue(1) < SlowMA.OutValue(1)) // Se a MM rápida está abaixo da lenta - pos.CloseAtMarket("Exit by cross over"); // Feche a posição. } //+------------------------------------------------------------------+ //| Comprar quando a MM rápida está acima da lenta | //+------------------------------------------------------------------+ void CMovingAverage::InitSell(const MarketEvent &event) { if(!IsTrackEvents(event))return; // Manipula apenas o evento necessário! if(positions.open_sell > 0) return; // Se houver pelo menos uma posição de venda, não há necessidade de vender. if(FastMA.OutValue(1) < SlowMA.OutValue(1)) // Se existem posições de compra abertas, verifique se a MM rápida está acima da lenta: Trade.Sell(1.0, ExpertSymbol(), ""); // Se for acima, nós compramos. } //+------------------------------------------------------------------+ //| Fecha a posição de venda quando a MM rápida está acima | //| da MM lenta | //+------------------------------------------------------------------+ void CMovingAverage::SupportSell(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))return; // Manipula apenas o evento necessário! if(FastMA.OutValue(1) > SlowMA.OutValue(1)) // Se a MM rápida está acima da lenta - pos.CloseAtMarket("Exit by cross under"); // Fecha a posiçao. } //+------------------------------------------------------------------+ //| Filtros dos eventos de entrada. Se o evento transferido não é | //| processado pela estratégia, retorna falso; se for processado | //| retorna verdadeiro. | //+------------------------------------------------------------------+ bool CMovingAverage::IsTrackEvents(const MarketEvent &event) { //--- Nós manipulamos uma única abertura de uma nova barra no símbolo e timeframe selecionado if(event.type != MARKET_EVENT_BAR_OPEN)return false; if(event.period != Timeframe())return false; if(event.symbol != ExpertSymbol())return false; return true; }
O código-fonte acima é fácil de entender. No entanto, é preciso esclarecer alguns pontos. O motor CStrategy chama os métodos InitBuy, InitSell, SupportBuy e SuportSell (métodos lógicos de negociação) na emergência de qualquer evento, como a profundidade das mudanças do mercado, na chegada de um novo tick ou na mudança no temporizador. Normalmente, esses métodos são chamados frequentemente. No entanto, um Expert Advisor usa um conjunto muito limitado de eventos. Este somente é utilizado no caso de formação de uma nova barra. Portanto, todos os outros eventos que chamam os métodos lógicos de negociação devem ser ignorados. O método IsTrackEvents é utilizado para isso. Ele verifica se o evento transmitido a ele está sendo monitorado, se assim for - retorna verdadeiro, caso contrário, retorna falso.
A estrutura das posições é usada como uma variável auxiliar. Contém o número de posições compradas e vendidas pertencentes da atual estratégia. O motor CStrategy calcula as estatísticas, de modo que a estratégia não precisa passar por todas as posições abertas, a fim de contá-las. A lógica de abertura de posição do Expert Advisor é realmente reduzida à verificação das seguintes condições:
- Um evento de negociação é a abertura de uma nova barra.
- Não há nenhuma outra posição aberta na mesma direção.
- A média móvel rápida está acima (para comprar) ou abaixo (para vender) da média móvel lenta.
As condições que devem ser cumpridas para fechar uma posição são mais simples:
- Um evento de negociação é a abertura de uma nova barra.
- A média móvel rápida está abaixo (para fechar uma posição de compra) ou acima (para fechar uma posição de venda) da média móvel lenta.
Neste caso, não há necessidade de verificar as posições abertas, porque a chamada do SupportBuy e SupportSell com a posição atual como um parâmetro indica que existe uma posição aberta no EA.
A lógica real do Expert Advisor, sem ter em conta as definições dos métodos e sua classe, é descrita em 18 linhas de código. Além disso, metade dessas linhas (condições de Venda) é um espelho da outra metade (condições para Comprar). Esta simplificação da lógica só é possível quando se utiliza bibliotecas auxiliares, como a CStrategy.
Exemplo de Expert Advisor Baseado na Ruptura de Canais das Bandas Bollinger
Continuamos a criação de estratégias que utilizam o motor de negociação CStrategy. No segundo exemplo, vamos criar uma estratégia que negocia fugas do canal das Bandas Bollinger. Se o preço atual está acima da banda Bollinger superior, vamos comprar. Por outro lado, se o preço de fechamento da barra atual está abaixo da banda Bollinger inferior, vamos vender. Vamos sair das posições de compra e venda uma vez que o preço chega a linha média do indicador.
Desta vez, vamos usar o handle do indicador padrão iBands. Isto é feito para mostrar que o nosso modelo de negociação permite trabalhar diretamente com o handle do indicador, ou seja, a construção de classes de indicadores especiais orientados a objetos não é necessária. No entanto, neste caso será necessário especificar dois principais parâmetros do indicador - o período médio e o valor do desvio padrão no Expert Advisor. Aqui está o código-fonte da estratégia:
//+------------------------------------------------------------------+ //| ChannelSample.mqh | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #include <Strategy\Strategy.mqh> //+------------------------------------------------------------------+ //| definição | //+------------------------------------------------------------------+ class CChannel : public CStrategy { private: int m_handle; // O handle do indicador que usaremos int m_period; // Período Bollinger double m_std_dev; // Valor do desvio padrão bool IsTrackEvents(const MarketEvent &event); protected: virtual void OnSymbolChanged(string new_symbol); virtual void OnTimeframeChanged(ENUM_TIMEFRAMES new_tf); virtual void InitBuy(const MarketEvent &event); virtual void SupportBuy(const MarketEvent &event,CPosition *pos); virtual void InitSell(const MarketEvent &event); virtual void SupportSell(const MarketEvent &event,CPosition *pos); virtual bool ParseXmlParams(CXmlElement *params); virtual string ExpertNameFull(void); public: CChannel(void); ~CChannel(void); int PeriodBands(void); void PeriodBands(int period); double StdDev(void); void StdDev(double std); }; //+------------------------------------------------------------------+ //| Construtor padrão | //+------------------------------------------------------------------+ CChannel::CChannel(void) : m_handle(INVALID_HANDLE) { } //+------------------------------------------------------------------+ //| O destruidor libera o handle usado no indicador | //+------------------------------------------------------------------+ CChannel::~CChannel(void) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); } //+------------------------------------------------------------------+ //| Resposta a alteração do símbolo | //+------------------------------------------------------------------+ void CChannel::OnSymbolChanged(string new_symbol) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(new_symbol,Timeframe(),m_period,0,m_std_dev,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Resposta a alteração do timeframe | //+------------------------------------------------------------------+ void CChannel::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf) { if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,0,m_std_dev,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Retorna o período do indicador | //+------------------------------------------------------------------+ int CChannel::PeriodBands(void) { return m_period; } //+------------------------------------------------------------------+ //| Define o período do indicador | //+------------------------------------------------------------------+ void CChannel::PeriodBands(int period) { if(m_period == period)return; m_period=period; if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,0,m_std_dev,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Define o valor do desvio padrão | //+------------------------------------------------------------------+ double CChannel::StdDev(void) { return m_std_dev; } //+------------------------------------------------------------------+ //| Define o valor do desvio padrão | //+------------------------------------------------------------------+ void CChannel::StdDev(double std) { if(m_std_dev == std)return; m_std_dev=std; if(m_handle!=INVALID_HANDLE) IndicatorRelease(m_handle); m_handle=iBands(ExpertSymbol(),Timeframe(),m_period,0,m_std_dev,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Regras de abertura da posição comprada | //+------------------------------------------------------------------+ void CChannel::InitBuy(const MarketEvent &event) { if(IsTrackEvents(event))return; // Habilita a lógica somente na abertura de uma nova barra if(positions.open_buy > 0)return; // Não abrir mais do que uma posição comprada double bands[]; if(CopyBuffer(m_handle, UPPER_BAND, 1, 1, bands) == 0)return; if(Close[1]>bands[0]) Trade.Buy(1.0,ExpertSymbol()); } //+------------------------------------------------------------------+ //| Regras de fechamento da posição comprada | //+------------------------------------------------------------------+ void CChannel::SupportBuy(const MarketEvent &event,CPosition *pos) { if(IsTrackEvents(event))return; // Habilita a lógica somente na abertura de uma nova barra double bands[]; if(CopyBuffer(m_handle, BASE_LINE, 1, 1, bands) == 0)return; double b = bands[0]; double s = Close[1]; if(Close[1]<bands[0]) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Regras de abertura da posição comprada | //+------------------------------------------------------------------+ void CChannel::InitSell(const MarketEvent &event) { if(IsTrackEvents(event))return; // Habilita a lógica somente na abertura de uma nova barra if(positions.open_sell > 0)return; // Não abrir mais de uma posição comprada double bands[]; if(CopyBuffer(m_handle, LOWER_BAND, 1, 1, bands) == 0)return; if(Close[1]<bands[0]) Trade.Sell(1.0,ExpertSymbol()); } //+------------------------------------------------------------------+ //| Regras de fechamento da posição comprada | //+------------------------------------------------------------------+ void CChannel::SupportSell(const MarketEvent &event,CPosition *pos) { if(IsTrackEvents(event))return; // Habilita a lógica somente na abertura de uma nova barra double bands[]; if(CopyBuffer(m_handle, BASE_LINE, 1, 1, bands) == 0)return; double b = bands[0]; double s = Close[1]; if(Close[1]>bands[0]) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| Filtros dos eventos de entrada. Se o evento transferido não é | //| processado pela estratégia, retorna falso; se for processado | //| retorna verdadeiro. | //+------------------------------------------------------------------+ bool CChannel::IsTrackEvents(const MarketEvent &event) { //--- Nós manipulamos uma única abertura de uma nova barra no símbolo e timeframe selecionado if(event.type != MARKET_EVENT_BAR_OPEN)return false; if(event.period != Timeframe())return false; if(event.symbol != ExpertSymbol())return false; return true; } //+------------------------------------------------------------------+ //| Os parâmetros específicos da estratégia são transmitidos dentro | //| dela neste método, substituídos a partir do CStrategy | //+------------------------------------------------------------------+ bool CChannel::ParseXmlParams(CXmlElement *params) { bool res=true; for(int i=0; i<params.GetChildCount(); i++) { CXmlElement *param=params.GetChild(i); string name=param.GetName(); if(name=="Period") PeriodBands((int)param.GetText()); else if(name=="StdDev") StdDev(StringToDouble(param.GetText())); else res=false; } return res; } //+------------------------------------------------------------------+ //| O nome exclusivo completo do Expert Advisor | //+------------------------------------------------------------------+ string CChannel::ExpertNameFull(void) { string name=ExpertName(); name += "[" + ExpertSymbol(); name += "-" + StringSubstr(EnumToString(Timeframe()), 7); name += "-" + (string)Period(); name += "-" + DoubleToString(StdDev(), 1); name += "]"; return name; }
Agora, o Expert Advisor realiza mais operações. O EA contém a média móvel e o valor de desvio padrão das Bandas Bollinger. Além disso, o Expert Advisor cria o handle doindicador e os destróicom métodos adequados. Isto é devido ao uso direto de indicadores sem o uso de invólucros. O resto do código é semelhante ao Expert Advisor anterior. Ele espera que o preço de fechamento da última barra esteja acima (para comprar) ou abaixo (para vender) das bandas de Bollinger, abrindo uma nova posição.
Note que no Expert Advisor usamos acesso direto às barras através de classes especiais de timeseries. Por exemplo, este método é usado para comparar o último preço de fechamento da barra com a Banda Bollinger superior na seção Buy (o método InitBuy):
double bands[]; if(CopyBuffer(m_handle, UPPER_BAND, 1, 1, bands) == 0)return; if(Close[1] > bands[0]) Trade.Buy(1.0, ExpertSymbol());
Além dos métodos já conhecidos, o Expert Advisor contém os métodos substituídos ExpertNameFull e ParseXmlParams. O primeiro determina o nome exclusivo do Expert Advisor, que é exibido no painel de usuário como o nome do EA. O segundo método carrega o indicador Bollinger definindo o arquivo XML. O painel do usuário e as configurações do EA armazenados no arquivos XML serão discutidos no próximo artigo. O resto da operação do EA é semelhante ao anterior. Esse é o objetivo da abordagem proposta: a unificação completa do desenvolvimento de um Expert Advisor.
Carregando Estratégias Personalizadas ao Mecanismo de Negociação
Uma vez que todas as estratégias têm sido descritas, precisamos criar suas instâncias, inicializá-las com os parâmetros necessários e adicioná-las no mecanismo de negociação. Qualquer estratégia carregada no motor necessita alguns atributos (propriedades concluídas) que devem retornar. Estes atributos incluem as seguintes propriedades:
- O identificador exclusivo da estratégia (é o número mágico). Os IDs de estratégia devem ser únicos, mesmo se eles sejam criados como instâncias da mesma classe. Para especificar um número único, utilize o método Set da estratégia ExpertMagic().
- Timeframe da estratégia (ou o seu período de funcionamento). Mesmo se uma estratégia funciona em vários períodos ao mesmo tempo, você ainda precisa especificar o timeframe de trabalho. Neste caso por exemplo, ele pode ser o timeframe o mais utilizado. Para especificar o período, use o método Set de Timeframe.
- Símbolo da estratégia (ou o seu instrumento funcional). Se uma estratégia trabalha com vários símbolos (uma estratégia multi-moedas), você ainda precisa especificar o símbolo de trabalho. Este pode ser um dos símbolos utilizados pela estratégia.
- Nome da estratégia. Além dos atributos acima, cada estratégia também deve ter o seu próprio nome string. O nome do Expert Advisor é especificado usando o método Set de ExpertName. Essa propriedade é necessária, porque ele é usada na criação automática das estratégias a partir do arquivo Strategies.xml. A mesma propriedade é utilizada para apresentar a estratégia no painel do usuário que será descrita no quarto artigo.
Se um destes atributos não for especificado, o mecanismo de negociação irá recusar-se a carregar o algoritmo e retornará uma mensagem de aviso indicando o parâmetro ausente.
O motor de negociação consiste em duas partes principais:
- Um módulo externo para o gerenciamento de estratégias que é o CStrategyList. Este módulo é um gerente de estratégias e contém algoritmos usados para controlá-las. Vamos discutir este módulo na próxima parte da série.
- Um módulo interno de estratégias que é o CStrategy. Este módulo define as funções básicas da estratégia. Ele foi descrito em detalhes neste artigo e no anterior: "Expert Advisor Universal: Modelo de Evento e o Protótipo da Estratégia de Negociação (Parte 2)".
Cada instância do CStrategy deve ser carregado no gerenciador de estratégias CStrategyList. O gerente de estratégias permite o carregamento de duas maneiras:
- Automaticamente usando o arquivo de configuração Strategies.xml. Por exemplo, você pode descrever um conjunto de estratégias e seus parâmetros neste arquivo. Então, quando você executar um Expert Advisor no gráfico, o gerente de estratégia irá criar instâncias exigidas das estratégias, irá inicializar seus parâmetros e adicionar à sua lista. Este método será descrito em detalhe no artigo seguinte.
- Manualmente adicionando a descrição do módulo de execução. Neste caso, o objeto de estratégia adequado é criado com um conjunto de instruções na seção OnInit de um Expert Advisor, então ele é inicializado com os parâmetros necessários e adicionado ao gerente de estratégia CStrategyList.
Aqui é a descrição do processo de configuração manual. Nós criamos o arquivo Agent.mq5 com o seguinte conteúdo::
//+------------------------------------------------------------------+ //| Agent.mq5 | //| Copyright 2015, Vasiliy Sokolov. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Vasiliy Sokolov." #property link "https://www.mql5.com" #property version "1.00" #include <Strategy\StrategiesList.mqh> #include <Strategy\Samples\ChannelSample.mqh> #include <Strategy\Samples\MovingAverage.mqh> CStrategyList Manager; //+------------------------------------------------------------------+ //| função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- Configurar e adicionar à lista de estratégias CMovingAverage CMovingAverage *ma=new CMovingAverage(); ma.ExpertMagic(1215); ma.Timeframe(Period()); ma.ExpertSymbol(Symbol()); ma.ExpertName("Moving Average"); ma.FastMA.MaPeriod(10); ma.SlowMA.MaPeriod(23); if(!Manager.AddStrategy(ma)) delete ma; //--- Configurar e adicionar à lista de estratégias CChannel CChannel *channel=new CChannel(); channel.ExpertMagic(1216); channel.Timeframe(Period()); channel.ExpertSymbol(Symbol()); channel.ExpertName("Bollinger Bands"); channel.PeriodBands(50); channel.StdDev(2.0); if(!Manager.AddStrategy(channel)) delete channel; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Função de desinicialização do Expert | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { Manager.OnTick(); } //+------------------------------------------------------------------+ //| Função BookEvent | //+------------------------------------------------------------------+ void OnBookEvent(const string &symbol) { Manager.OnBookEvent(symbol); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { Manager.OnChartEvent(id,lparam,dparam,sparam); }
A partir deste perfil, vemos que a configuração da estratégia é realizada na função OnInit. Se você esquecer de especificar um dos parâmetros necessários da estratégia, o gerente de estratégia irá se recusar a adicionar a estratégia na sua lista. Neste caso, o método AddStartegy retornará falso e a instância da estratégia criada terá de ser excluída. O gerente de estratégia gera uma mensagem de aviso para ajudar a compreender o potencial problema. Vamos tentar chamar tal mensagem. Para fazer isso, comente a instrução que define o número mágico:
//+------------------------------------------------------------------+ //| função de inicialização do Expert | //+------------------------------------------------------------------+ int OnInit() { //--- Configurar e adicionar à lista de estratégias CMovingAverage CMovingAverage *ma=new CMovingAverage(); //ma.ExpertMagic(1215); ma.Timeframe(Period()); ma.ExpertSymbol(Symbol()); ma.ExpertName("Moving Average"); ma.FastMA.MaPeriod(10); ma.SlowMA.MaPeriod(23); if(!Manager.AddStrategy(ma)) delete ma; return(INIT_SUCCEEDED); }
Após o início do módulo de execução, a seguinte mensagem será exibida no terminal:
2016.01.20 14:08:54.995 AgentWrong (FORTS-USDRUB,H1) WARNING;CStrategyList::AddStrategy;The strategy should have a magic number. Adding strategy Moving Average is impossible;2016.01.20 14:09:01
A mensagem traduzida acima seria: "A estratégia deveria ter um número mágico. É impossível adicionar a estratégia Média Móvel". Fica evidente a partir da mensagem que o métodoCStrategyList::AddStartegy não poderia adicionar a estratégia, pois não foi definido o seu número mágico.
Além de estratégias de configuração, o arquivo Agent.mq5 inclui o processamento dos eventos de negociação a ser analisado. Este processamento inclui o rastreamento dos eventos e os transmite aos métodos apropriados da classe CStrategyList.
Uma vez que o arquivo executável é criado, ele pode ser compilado. O código-fonte das estratégias analisadas está disponível no diretório anexo compactado Include\Strategy\Samples. Um Expert Advisor compilado estará pronto para uso com a lógica das duas estratégias de negociação.
Conclusão
Analisamos os exemplos de estratégias personalizadas e o princípio das classes que fornecem acesso as cotações através de indexadores simples. Também, discutimos as classe que implementam o registro e exemplos de indicadores orientados a objeto. O conceito proposto de construção deste Expert Advisor torna mais fácil a formalização da lógica do sistema de negociação. Tudo que é necessário para definir as regras em vários métodos substituídos.
Na quarta parte da série, "Expert Advisor Universal: Negociação em Grupo e Gestão de uma Carteira de Estratégias (Parte 4)" vamos descrever os algoritmos, usando o que podemos acrescentar como um número ilimitado de lógicas de negociação para um EA executável no módulo ex5. Na quarta parte também iremos considerar um simples painel de usuário, onde você poderá gerenciar os Expert Advisors dentro do módulo executável, por exemplo, mudar os modos de negociação ou comprar e vender dentro de interesses específicos.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2170
- 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