English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5

Escrevendo um Expert Advisor utilizando a abordagem de programação orientada a objeto do MQL5

MetaTrader 5Exemplos | 24 janeiro 2014, 09:04
6 029 0
Samuel Olowoyo
Samuel Olowoyo

Introdução

No primeiro artigo fizemos uma jornada através das etapas básicas para criar, depurar e testar um consultor especialista em MQL5.

Tudo que fizemos foi muito simples e interessante; no entanto, a nova linguagem do MQL5 tem muito mais a oferecer. Nesse artigo consideramos a abordagem orientada a objeto para fazer o que fizemos no primeiro artigo. A maior parte das pessoas acha que isso é difícil, mas quero assegurá-lo que quando você terminar de ler esse artigo, você será capaz de escrever o seu próprio consultor especialista que é baseado em orientação a objeto.

Não vamos repetir algumas das coisas que aprendemos no primeiro artigo, então sugiro que você primeiramente leia todo o artigo se você já não o fez.


1. O paradigma orientado a objeto

Uma das coisas que fazem o novo MQL5 muito mais poderoso e robusto que o MQL4 é a sua abordagem OOP (programação orientada a objeto).

É recomendado no OOP que um objeto não deve expor nenhum de seus detalhes de implementação. Dessa forma, sua implementação pode ser modificada sem modificar o código que utiliza o objeto. Isso significa que uma classe permite ao programador esconder (mas também previne mudanças) como a classe que ele escreveu é implementada.

Para deixar as coisas mais claras, vamos nos ater um pouco nos termos "classe" e "objeto" mencionados a pouco.

  • CLASSE. Uma classe é como um conceito expandido de estrutura de dados mas em vez de apenas manter os dados, ela mantém ambos dados e funções. Uma classe pode conter várias variáveis e funções, que são chamadas de membros da classe. É um encapsulamento de membros de dados e funções que manipulam os dados. Uma classe é muito mais poderosa, no sentido que você pode reunir todas as funções do seu consultor especialista em uma classe. Você apenas fará referência às funções a qualquer momento que precisar delas no código do seu CE. A propósito, é disso que esse artigo trata.
  • OBJETO. Um objeto é uma instância de uma classe. Uma vez que uma classe foi criada, para utilizar a classe, precisamos declarar uma instância da classe. Isso é chamado um objeto. Em outras palavras, para criar um objeto você precisa de uma classe.

1.1. DECLARANDO UMA CLASSE

Uma classe, basicamente, contém a descrição dos membros (propriedades e funções/métodos) de um objeto que você quer criar a partir da classe. Vamos observar um exemplo…</span.

Se queremos criar um objeto que possuirá portas, assentos, pneus, peso, etc. e que também possa dar partida, mudar marcha, parar e buzinar; então precisamos escrever uma classe para ele. As portas, assentos, peso, partida, mudar marcha, parar e buzinar serão os membros da classe.

Claro, você observará que, esses membros são categorizados; alguns são apenas o que o nosso objeto possuirá (propriedades) enquanto que outros são o que o nosso objeto fará (ações – funções/métodos). Para declarar a nossa classe, precisamos pensar em um nome muito bom e descritivo para ela. Nesse caso, vamos chamar a nossa classe de CARRO. Nossa classe CARRO possuirá as propriedades e funções afirmadas acima como seus membros.

Pra declarar a classe, começamos digitando a palavra chave classe seguido pelo nome da classe seguido por um par de chaves que contém os membros da classe.

Então, o formato básico de uma classe é como mostrado abaixo:

class class_name 
{
  access_keyword_1:
    members1;

  access_keyword_2:
    members2;
  ...
};

Aqui, class_name é um identificador válido para a classe que queremos escrever, members1 e members2 são os membros de dados da classe.

O access_keyword especifica o acesso direto aos membros da nossa classe. Um access_keyword pode ser privado, protegido ou público. Lembre-se que você está tentando escrever uma classe que pode ser utilizada por nós mesmos e outros sem de fato expor os detalhes de implementação. é por isso que direitos de acesso são necessários.

Podem haver alguns membros da nossa classe que não queremos acessar do lado de fora da nossa classe. Esses são declarados dentro da seção de acesso privado utilizando a palavra chave privado ou protegido. Outros membros que queremos acessar do lado de fora da nossa classe serão então declarados dentro da seção de acesso público utilizando a palavra chave público. Agora, a nossa nova classe CARRO se parecerá como abaixo:

class CAR 
{
  private:
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

A nossa classe CARRO é declarada utilizando a palavra chave classe. Essa classe contém oito membros com quatro membros possuindo acesso provado e quarto membros possuindo acesso público. Os quatro membros na seção privada são membros de dados. Três são do tipo de dados inteiro(int) e um do tipo de dados duplo. Esses membros não podem ser acessados por nenhuma outra função que seja declarada fora dessa classe.

Os quatro membros na seção pública são membros de função. Dois retornam o tipo de dados bool e dois retornam o tipo void. Esses são os membros que são acessíveis a qualquer objeto dessa classe sempre que é criada por qualquer um utilizando a nossa classe. Uma vez que um objeto da nossa classe é criado, esses membros serão prontamente disponíveis para o uso.

Como você justamente observará que as palavras chave de acesso (privado, público, protegido) são sempre seguidas por dois pontos. A declaração da classe também terminou com um ponto e vírgula. Os membros são declarados utilizando seus tipos de dados corretos.

Deve-se observar que uma vez que você declara uma classe, é dado direito de acesso privado a todos os membros da classe a não ser que explicitamente especificado como fizemos acima. Por exemplo, na declaração de classe abaixo:

class CAR 
{
    int        doors;
    int        sits;
    int        tyres;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();
  ...
};

Todos os quatro membros declarados acima da palavra chave de acesso público automaticamente possuem acesso privado.

Para a nossa classe ser utilizada, um objeto da classe deve primeiramente ser criado. Agora, vamos criar um objeto que é um tipo da nossa classe. Para fazer isso utilizaremos o nome da nossa classe seguido pelo nome que queremos dar ao objeto.

CARRO Honda;

Ou podemos criar outro objeto:

CARRO Toyota;

Honda ou Toyota é agora um tipo de CARRO e pode agora ter acesso a todas as funções membro da nossa classe CARRO contanto que as funções membro sejam declaradas dentro da seção de acesso público. Vamos voltar a isso mais tarde.

Você pode ver que podemos criar quantos objetos dessa classe quisermos. Esse é um dos objetivos de programação orientada a objeto.

Nesse ponto vamos considerar, em detalhes, o formato de uma classe em MQL5.

class class_name 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

Isso é uma declaração de uma classe onde class_name é o nome da classe. Essa classe possui nove membros, mas desses nove, dois são membros especiais.

O construtor:
O construtor (representado como class_name()) é uma função especial que é chamada automaticamente quando um novo objeto do tipo da classe é criado. Então, nesse caso, quando você cria um objeto do tipo dessa classe.

Objeto class_name;

O construtor, class_name(), é chamado automaticamente. O nome do construtor deve combinar com o nome da classe, que é a razão de termos nomeado o construtor como class_name(). Em MQL5, um construtor não toma nenhum parâmetro de entrada e não possui tipo de retorno. Alocações de memória e inicialização dos membros da classe são normalmente feitos quando o construtor é chamado. Construtores não podem ser chamados explicitamente como se fossem funções de membros normais. Eles são apenas executados quando um novo objeto daquela classe é criado. Uma classe em MQL5 pode apenas possuir um construtor.

O destruidor:
O segundo membro especial é representado como ~class_name(). Esse é o destruidor da classe escrito com um til (~) antes do nome da classe, é chamado automaticamente quando um objeto de classe é destruído. Todos os membros da classe que precisam ser desinicializados são desinicializados nesse estágio e não importa realmente se você declarou explicitamente o destruidor ou não.

Membros de dados:
Membros de uma classe podem ser qualquer tipo de dado legal, o tipo de classe ou o tipo de estrutura. Em outras palavras, quando declaramos variáveis de membros de uma classe, você pode utilizar qualquer tipo de dado legal (int, duplo, cadeia, etc.), um objeto de outra classe ou um tipo de uma estrutura (por exemplo, o MqlTradeRequest do MQL5, etc.).

Membros da função:
Esses são os membros da classe que são utilizados para modificar os membros de dados e executar as funções principais/métodos da classe. O tipo de retorno dos membros da função podem ser de qualquer tipo de retorno legal (bool, void, duplo, cadeia, etc.).

Privado:
Membros declarados dentro dessa seção são apenas acessíveis pelos membros da função da classe. Eles não podem ser acessados por nenhuma outra função fora da classe.

Protegido:
Membros declarados dentro dessa seção são acessíveis aos membros da função da classe e também podem ser acessados pelos membros da função de outras classes que são derivadas dessa classe. Isso significa que podemos também criar uma nova classe a partir dessa classe. Nesse caso, a nova classe derivada dessa classe (que agora se tornará a classe base) será agora capaz de acessar os membros protegidos da classe base. Esse é o conceito de herança em OOP. Discutiremos isso em breve, apenas relaxe...

Público:
Membros declarados dentro dessa seção estão disponíveis para uso fora da classe por um objeto da classe. Este é o lugar onde declarar algumas das funções que serão necessárias para utilizar a classe em outros programas.

Agora que olhamos para o formato básico de uma classe, espero que você não esteja entediado ainda, porque temos alguns outros aspectos interessantes das classes que precisamos olhar antes de finalmente criar uma classe empacotadora para o nosso consultor especialista.

1.2. HERANÇA

Digamos que queremos fazer uma outra classe a partir dessa classe inicial base_class. O formato para derivar uma nova classe a partir de uma classe inicial é como a seguir:

A classe base:

class base_class 
{
  private:
    members1;
    members2;
    members3;

  public:
    class_name()  //Constructor;
    ~class_name() //Destructor;
    Members4();
    Members5();

  protected:
    members6;
    members7;
};

A classe derivada:

class new_class : access_keyword base_class 
{
  private:
    members8;

  public:
    new_class()  //Constructor;
    ~new_class() //Destructor;
    Members9();
};

Algumas explicações aqui antes de procedermos para explicar os detalhes. A classe new_class é derivada de uma classe base_class utilizando os dois pontos e uma access_keyword como mostrado acima. Agora, a new_class derivada/feita da base_class pode acessar (ou herdar) ambos os membros públicos e protegidos de uma base_class mas não pode acessar (ou não herdar) os membros privados da base_class. A new_class também pode implementar os métodos/funções do novo membro diferentes da base_class. Em outras palavras, a new_class pode também possuir seus próprios membros de dados e função separadamente daqueles que ela herda da base_class.

Se a palavra chave de público é utilizada ao criar a classe derivada, significa que os membros públicos e protegidos da classe base serão herdados como membros públicos e protegidos da classe derivada. Se a palavra chave protegido é utilizada, membros públicos e protegidos da classe base serão herdados como membros protegidos da classe derivada. Se a palavra chave privado é utilizada, os membros públicos e protegidos da classe base serão herdados como membros protegidos da classe derivada.

É importante observar que quando um novo objeto da new_class (a classe derivada) é criado, o construtor da base_class é chamado primeiramente antes do construtor da new_class; enquanto que quando o objeto é destruído, o destruidor da new_class (a classe derivada) é chamado primeiro antes do destruidor da base_class.

Para entender melhor esse conceito de herança, vamos voltar para a nossa classe inicial CARRO.

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool       start();
    void       changegear();
    void       stop();
    bool       horn();

  private:
    int        tyres;
};

Podemos derivar outra classe SALÃO a partir dessa classe. Observe que declarei três dos membros de dados da classe CARRO como protegidos. Isso é para permitir que a nossa nova classe SALÃO herde esses membros.

Também quero que você compreenda que a ordem na qual você coloca as palavras-chave de acesso não importa. O que importa é que todos os membros declarados sob a palavra-chave de acesso pertencem àquela palavra-chave.

class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void       runathighspeed();
};

Nossa classe derivada SALÃO possui dois membros e ao mesmo tempo herda sete membros (membros públicos e protegidos) da classe base CARRO. Isso significa que uma vez que o objeto SALÃO é criado, ele será capaz de acessar as funções do membro público de CARRO que são partida(), mudarmarcha(), parar() e buzina() juntamente com sua própria função de membro público correremvelocidadealta(). Esse é o conceito de herança.

Assim como algumas características/comportamentos (métodos) do nosso pai/pais (classe base) aparecem em nós, seus filhos (classe derivada), porque herdamos esses comportamentos (métodos/funções) deles tanto geneticamente quanto de outro modo. Desculpe, eu não sou da área médica, mas acredito que você capta a imagem que eu estou tentando mostrar. A propósito, o MQL5 não suporta herança múltipla, então não há necessidade de falar sobre isso.

Ah sim!!! Espero que o tecido preto cobrindo a coisa mística chamada OOP ou CLASSE esteja sendo removido aos poucos... não fique cansado, se você sentir nesse momento que ainda não está muito esclarecido quanto o que estamos discutindo, você pode precisar relaxar, tomar uma xícara de café e então voltar e começar do início. Não é tão misterioso quanto você pensa...

Se você está agora de volta a esse ponto, presumo que está seguindo a minha explicação. Quero que me diga quantas classes mais pode derivar da nossa classe base CARRO? Por favor preciso que responda. Estou falando sério. Nomeie-as e escreva suas declarações e envie-as para mim. Se você conseguir nomear todas, levarei você para almoçar... (estou brincando?).

Agora que você está pronto para mais, vamos continuar...

É verdade que quando escrevo, escrevo como o meu pai. A escrita à mão dele é muito elegante e altamente estilosa assim como a minha. Acho que é uma coisa que eu herdei dele, mas adivinhe; ele usa a mão esquerda para escrever enquanto eu uso a minha mão direita e quando você vê as escritas dificilmente pode notar a diferença pois elas são muito similares. Qual é o problema aqui? Eu herdei a boa escrita a mão do meu pai, mas não escrevo com a minha mão esquerda como o meu pai. Isso quer dizer que mesmo que seja lá o que eu herdar e pareça similar, mas a forma que eu faço o meu é diferente do meu pai. Isso faz sentido para você? Essa é uma ideia chamada de polimorfismo em OOP.

Uma classe derivada (eu mesmo, como no exemplo acima) herda uma função de membro (escreverbem() – para a minha escrita a mão) de uma classe base (meu pai) mas ela implementa a função (escreverbem() ) em uma forma diferente da classe base (meu pai).

De volta a nossa classe CARRO, e a classe derivada SALÃO:

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    bool               start();
    virtual void       changegear(){return(0);}
    void               stop();
    bool               horn();

  private:
    int        tyres;
};
class SALOON : public CAR 
{
  private:
    int        maxspeed;

  public:
    void               runathighspeed();
    virtual  void       changegear(){gear1=reverse; gear2=low; gear3=high;}
  };

class WAGON : public CAR 
{
  private:
    bool               hasliftback;

  public:
   virtual  void       changegear(){gear1=low; gear2=high; gear3=reverse;}
};

Vamos observar algumas mudanças que fizemos aqui. Primeiro, declaramos uma nova classe derivada de CARRO chamada VAGÃO com dois membros. Também modificamos a função mudarmarcha() para se tornar uma função virtual na classe base. Por que fizemos mudarmarcha() uma função virtual. É simplesmente porque queremos qualquer classe que herde a função da classe base para ser capaz de implementar ela de sua própria forma.

Em outras palavras, funções do membro virtual de uma classe são funções do membro que podem ser substituídas ou implementadas de forma diferente em qualquer classe derivada da classe em que elas forem declaradas. O corpo da função do membro pode então ser substituído com um novo conjunto de implementação na classe derivada. Mesmo assim, podemos não utilizar a palavra virtual novamente nas classes derivadas, é uma boa prática de programação sempre utilizar ela nas classes derivadas.

Dos exemplos acima, as classes SALÃO e VAGÃO implementam a função mudarmarcha() de suas próprias formas.

1.3. DEFININDO MÉTODOS DE CLASSE (FUNÇÕES DO MEMBRO)

Já que sabemos, até certo ponto, como declarar classes; vamos seguir em frente discutindo como definir as funções do membro de uma classe. Após termos declarado a classe, a próxima coisa é definir as funções do membro das nossas classes. Vamos olhar para a nossa classe CARRO novamente

class CAR 
{
  protected:
    int        doors;
    int        sits;
    double     weight;

  public:
    void       CAR() // Constructor
    bool       start();
    void       changegear();
    void       stop();
    bool       horn(){press horn;}

  private:
    int        tyres;
};

 void CAR::CAR()
{
 // initialize member variables here
}

bool CAR::start()
{
 // car start procedure here
}

void CAR::changegear()
{
// car changegear procedure here
}

void CAR::stop()
{
// car stop procedure here
}

Ao definir as funções do membro, utilizamos um operador de dois pontos duplos (::) chamado o operador de escopo. Isso é escrito da mesma forma que funções normais, a única diferença é o nome da classe e o operador de escopo que é adicionado. Você também observará que uma das funções já foi definida dentro da classe (função de membro buzina()). Uma função de membro pode ser definida na declaração da classe ou fora da declaração da classe como você viu aqui.

Acho que será importante se pudermos revisar o conceito de funções um pouco antes de procedermos.

1.4. FUNÇÕES

A propósito, o que é uma função?

Algumas vezes em uma casa quando você tem três crianças, em vez de apenas uma delas fazer todos as tarefas da casa; uma foi convidada para lavar os pratos todos os dias após o jantar, uma foi convidada para varrer enquanto que a terceira foi dada a tarefa de arrumar as camas toda a manhã.

Há algumas tarefas para serem feitas na casa, em vez de dar todas as tarefas para uma criança, decidimos dividi-las entre as três. Isso tornará a tarefa muito fácil e leve para cada uma delas em vez de ser um fardo para apenas uma delas. Também, se uma das crianças não fez a tarefa dele/dela, sabemos rapidamente qual delas punir. Essa é a ideia por trás de funções.

A maioria das vezes queremos escrever um código que fará muitas tarefas. É aí que as funções entram. Podemos decidir separar a tarefa em tarefas menores e então escrever uma função para realizar cama uma das tarefas menores. Uma função é um bloqueio de código que realiza ou implementa um conjunto de operações. É um grupo de afirmações que é executado sempre que é chamada de algum ponto em um programa.

Uma função pode ser definida como a seguir:

Return_type function_name (parameters1,parameters2,…)
{
  Expressions; //(actions to carry out by the function)
}
  • Return_type: o tipo de dado retornado pela função (deve ser um tipo de dado válido ou void se não retornar com nada);
  • Function_name: o nome da função (deve ser um nome válido) que será utilizado para chamar a função;
  • Parâmetros: parâmetros são variáveis de tipos de dados válidos que agirão dentro da função como variável local. Se uma função possui mais de um parâmetro, eles são separados por vírgulas;
  • Expressões: o corpo da função que contém bloco de afirmações.

Exemplo de uma função:

int doaddition (int x, int y)
{
 return (x+y);
}

O tipo de retorno da função é inteiro (int), doaddition é o nome da função e int x e int y são os parâmetros. O que a função faz é adicionar dois parâmetros de entrada quaisquer fornecidos a ela e retornar o resultado. Então se fornecermos a função com duas variáveis inteiras 2 e 3 a função fará a adição e retornar 5 como resultado.

int doaddition(2,3) // returns 5

Para mais informações sobre funções, por favor consulte o manual de referência do MQL5.

Agora chega das teorias e vamos trabalhar.

A essência desse artigo é ensinar você como você pode escrever uma classe para o seu consultor especialista utilizando a abordagem orientada a objeto apresentada no MQL5.

É agora hora de agir...

2. Escrever um Expert Advisor

Nesse momento faremos referência ao Consultor Especialista que criamos no primeiro artigo. Se você não leu o artigo, por favor o faça agora para que a maior parte das coisas que discutiremos a partir desse momento não sejam mais estranhas para você. No entanto, eu posso ainda revisar algumas coisas que podem ser necessárias.

Antes que você possa escrever a sua classe, você precisa sentar e desenvolver a sua estratégia de negócios antes. Já fizemos isso no primeiro artigo. A próxima coisa é selecionar aquelas funcionalidades que queremos delegar à nossa classe. Essas funcionalidades determinarão as variáveis de membro da nossa classe. Só uma revisão da nossa estratégia de negociação do primeiro artigo.

O que o nosso EA fará:

  • Ele irá monitorar um indicador particular e, quando uma certa condição for alcançada (ou certas condições foram alcançadas), ele colocará uma negociação (tanto uma posição curta/vendida ou longa/comprada), dependendo da condição presente que foi alcançada.

O mencionado acima é chamado de estratégia de negociação. Antes que você possa escrever um CE, você precisa primeiro desenvolver a estratégia que você deseja automatizar no EA. Então nesse caso, deixe-nos modificar a afirmação acima de forma que ela reflita a estratégia que nós queremos desenvolver em um EA.

  • Nós utilizaremos um indicador chamado média móvel com um período de 8 (você pode escolher qualquer período, mas para os propósitos da nossa estratégia, nós usaremos 8).
  • Queremos que o nosso EA ofereça uma negociação longa (compra) quando a média móvel-8 (para o propósito neste assunto, eu irei me referir a ela como MM-8) estiver aumentando e o preço estiver acima dela, e oferecerá uma negociação curta (venda) quando a MM-8 estiver diminuindo e o preço estiver abaixo dela.
  • Também utilizaremos outro indicador chamado movimento direcional médio (ADX) com período 8 também para nos ajudar a determinar se o mercado está em tendência ou não. Estamos fazendo isso porque apenas queremos entrar na negociação quando o mercado estiver em tendência e relaxar quando o mercado estiver variando (isto é, sem tendência). Para alcançar isso, apenas ofereceremos a nossa negociação (comprar ou vender) quando as condições acima forem atendidas e o valor do ADX for maior que 22. Se o ADX for maior do que 22 porém diminuindo, ou o ADX for menor do que 22, não negociaremos, mesmo que a condição B tenha sido atendida.
  • Também queremos nos proteger definindo uma ordem para parar perda (ou stop loss) de 30 pontos, e para o nosso alvo de lucro; definiremos como objetivo um lucro de 100 pontos.
  • Também queremos que nosso EA procure por oportunidades de compra/venda somente quando uma nova barra tenha sido formada e também queremos nos certificar que abrimos uma posição de compra, se as condições de compra forem atendidas e já não tivermos uma em aberto, e abrir uma posição de venda quando as condições de venda forem atendidas e já não tivermos uma em aberto.

Adicionalmente, queremos nos certificar que seremos capazes de controlar o porcentual da nossa margem livre que pode ser utilizada na colocação de uma negociação e também para assegurar que verifiquemos a margem livre disponível antes de colocar qualquer negociação. O nosso EA apenas colocará uma negociação se a margem disponível for o suficiente para a negociação.

Agora você compreende o que queremos fazer. As funções que queremos delegar para a nossa classe são:

  • Verificar condições de compra e venda;
  • Colocar compra/venda dependendo do resultado das condições verificadas.

Basicamente, isso é tudo que queremos que o CE faça. Essas duas funcionalidades são as funções principais mas ainda há mais. Por exemplo, ao verificar posições de compra/venda, os indicadores precisam ser utilizados. Isso significa que obter os valores dos indicadores também deve estar em nossa classe. Então, incluímos:

  • Pegue todos os nomes de indicador (na seção OnInit do CE);
  • Pegue todos os buffers de indicador (na seção OnTick do CE);
  • Libere todos os nomes de indicador (na seção OnDeinit do CE).

Ao pegar os valores do indicador, a nossa classe precisará saber os períodos de MA e ADX, o período do gráfico e o símbolo (par de moeda corrente com o qual estamos trabalhando), então devemos também incluir:

  • Pegue os períodos de ADX e MA, e outros parâmetros importantes como o período do gráfico e símbolo.

Também para verificar a margem livre antes de colocar a negociação, incluiremos.

  • Verifique a margem livre/porcentagem da conta a ser utilizada para negociação.

Com isso nós já possuímos uma ideia de quais variáveis e funções devem estar em nossa classe.

Tudo bem, eu fiz o pensamento para você; é a hora de escrever um pouco de código.

2.1. Escrevendo uma classe

Vamos começar iniciando o MetaEditor (acredito que você já sabe disso). Uma vez que o MetaEditor está aberto, vamos iniciar um novo documento do MQL clicando na barra de ferramentas Novo ou Ctrl+N. Na janela do assistente, selecione "Incluir" e clique no botão PRÓXIMO.

Figura 1. Iniciando um novo documento do MQL5

Figura 1. Iniciando um novo documento do MQL5

Digite o nome do arquivo como mostrado abaixo e clique em terminar:

Figura 2. Nomeando um novo documento

Figura 2. Nomeando um novo documento

Selecionamos incluir porque nossa classe será um arquivo incluir que será incluso no código do nosso EA uma vez que estivermos prontos para utilizá-lo. É por isso que não temos espaço para inserir parâmetros de entrada.

Como de costume, o editor fornece você um esqueleto do que ele acha que você quer fazer.


Para começar, por favor, exclua tudo abaixo da linha de código "#property link …". Você deveria agora ter algo como isso.

//+------------------------------------------------------------------+
//|                                              my_expert_class.mqh |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"

Agora vamos escrever a declaração da nossa classe, vamos chamar a nossa classe de MyExpert.

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{

Vamos analisar a declaração da classe. A declaração começa com o nome da classe. Em seguida declaramos os membros privados da classe.

Os membros privados:

//+------------------------------------------------------------------+
//| CLASS DECLARATION                                                |
//+------------------------------------------------------------------+
class MyExpert
{
//--- private members
private:
   int               Magic_No;   // Expert Magic Number
   int               Chk_Margin; // Margin Check before placing trade? (1 or 0)
   double            LOTS;       // Lots or volume to Trade
   double            TradePct;   // Percentage of Account Free Margin to trade
   double            ADX_min;    // ADX Minimum value
   int               ADX_handle; // ADX Handle
   int               MA_handle;  // Moving Average Handle
   double            plus_DI[];  // array to hold ADX +DI values for each bars
   double            minus_DI[]; // array to hold ADX -DI values for each bars
   double            MA_val[];   // array to hold Moving Average values for each bars
   double            ADX_val[];  // array to hold ADX values for each bars
   double            Closeprice; // variable to hold the previous bar closed price 
   MqlTradeRequest   trequest;    // MQL5 trade request structure to be used for sending our trade requests
   MqlTradeResult    tresult;     // MQL5 trade result structure to be used to get our trade results
   string            symbol;     // variable to hold the current symbol name
   ENUM_TIMEFRAMES   period;      // variable to hold the current timeframe value
   string            Errormsg;   // variable to hold our error messages
   int               Errcode;    // variable to hold our error codes

Como explicado anteriormente, essas variáveis de número privado não são acessíveis por qualquer função fora da classe. A maior parte das variáveis são muito claras em suas declarações então não vou perder tempo falando sobre elas.

No entanto, você lembrará em nossa discussão, que afirmamos que variáveis de membro podem ser qualquer tipo de dado legal, estrutura ou classe.

Acredito que você pode ver isso em ação aqui com a declaração dos tipos MqlTradeRequest e MqlTradeResults.

O construtor

//--- Public member/functions
public:
   void              MyExpert();                                  //Class Constructor

O modo construtor não toma nenhum parâmetro de entrada; por favor mantenha isso em mente quando escrever a sua própria classe.

Funções de membro

//--- Public member/functions
public:
   void              MyExpert();                                 //Class Constructor
   void              setSymbol(string syb){symbol = syb;}         //function to set current symbol
   void              setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period
   void              setCloseprice(double prc){Closeprice=prc;}   //function to set prev bar closed price
   void              setchkMAG(int mag){Chk_Margin=mag;}          //function to set Margin Check value
   void              setLOTS(double lot){LOTS=lot;}               //function to set The Lot size to trade
   void              setTRpct(double trpct){TradePct=trpct/100;}   //function to set Percentage of Free margin to use for trading
   void              setMagic(int magic){Magic_No=magic;}         //function to set Expert Magic number
   void              setadxmin(double adx){ADX_min=adx;}          //function to set ADX Minimum values

Definimos essas funções de membro para nos permitir configurar as variáveis importantes que será necessárias pela nossa classe para realizar a sua função. Sem utilizar essas funções, essas variáveis não estarão disponíveis para a nossa classe utilizar. Como você também notará, já declaramos a variável correspondente em nossas classes que manterá esses valores uma vez que eles estiverem definidor por essas funções.

Outra coisa para observar é que definimos essas funções de membro dentro da declaração da classe. Como expliquei anteriormente, é permitido. Significa que não precisaremos definir elas novamente quando definindo outras funções de membro como você verá muito em breve.

Assim como funções normais, elas possuem parâmetros do tipo de dado correto dependendo dos valores de retorno de cada função. Acredito que isso não deva ser estranho para você.

void              doInit(int adx_period,int ma_period);         //function to be used at our EA intialization
void              doUninit();                                  //function to be used at EA de-initializatio
bool              checkBuy();                                  //function to check for Buy conditions
bool              checkSell();                                 //function to check for Sell conditions
void              openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,
                         double TP,int dev,string comment="");   //function to open Buy positions
void              openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,
                          double TP,int dev,string comment="");  //function to open Sell positions

Apenas declaramos essas funções de membro mas não definimos elas. É porque faremos isso mais tarde. Essas são as funções que manipularão a maior parte dos valores armazenados nas variáveis de membro da nossa classe e ao mesmo tempo elas formam funções para o papel principal da nossa classe. Vamos discuti-las mais tarde.

Os membros protegidos

Esses membros serão herdados por qualquer classe que é derivada da nossa classe. Não é realmente necessário se você não pretende derivar nenhuma outra classe a partir dessa classe. Você pode também colocá-los como números privados. Estou apenas fazendo isso para fazer você entender os vários problemas que discutimos anteriormente sobre as classes.

//--- Protected members
protected:
   void              showError(string msg, int ercode);   //function for use to display error messages
   void              getBuffers();                       //function for getting Indicator buffers
   bool              MarginOK();                         //function to check if margin required for lots is OK

Essas três funções são também muito importantes apesar de internas à nossa classe. A showError exibirá os nossos erros e getBuffers será utilizada para conseguir os buffers indicadores. MarginOK verifica se há margem livre o suficiente para abrir uma posição.

Uma vez que você terminou com a declaração da classe, não esqueça o ponto e vírgula. É muito importante.

};   // end of class declaration

A próxima coisa a fazer imediatamente após declarar a classe é definir as funções de membro que não foram definidas na seção de declaração.

//+------------------------------------------------------------------+
// Definition of our Class/member functions
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|  This CLASS CONSTRUCTOR
//|  *Does not have any input parameters
//|  *Initilizes all the necessary variables                                          
//+------------------------------------------------------------------+
void MyExpert::MyExpert()
  {
//initialize all necessary variables
   ZeroMemory(trequest);
   ZeroMemory(tresult);
   ZeroMemory(ADX_val);
   ZeroMemory(MA_val);
   ZeroMemory(plus_DI);
   ZeroMemory(minus_DI);
   Errormsg="";
   Errcode=0;
  }

Esse é o construtor da nossa classe. Aqui utilizamos os dois pontos duplos (::) (o operador de escopo) entre o nome da classe e o nome da função do membro. O que estamos tentando dizer é isso:

Apesar de estarmos definindo essa função de membro fora da declaração da classe, ainda é o escopo da classe. É um membro da classe cujo nome vem antes dos dois pontos duplos (operador de escopo).

Ele não possui nenhum parâmetro de entrada. É nesse ponto que inicializamos a maior parte das variáveis de membro necessárias e utilizamos a função ZeroMemory para conseguir isso.

void ZeroMemory(
void & variável // restabelecer a variável
);

Essa função restabelece os valores das variáveis passados para ela. Nesse caso utilizamos ela para redefinir os valores dos nossos tipos de estrutura (MqlTradeRequest e MqlTradeResult) e nossos arranjos.

A função showError:

//+------------------------------------------------------------------+
//|  SHOWERROR FUNCTION
//|  *Input Parameters - Error Message, Error Code                                                               
//+------------------------------------------------------------------+
void MyExpert::showError(string msg,int ercode)
  {
   Alert(msg,"-error:",ercode,"!!"); // display error
  }

Isso é uma função de membro protegida que é utilizada para exibir todos os erros encontrados durante as operações de qualquer objeto da nossa classe. Precisa de dois argumentos/parâmetros – Descrição de erro e código do erro.

A função getBuffers:

//+------------------------------------------------------------------+
//|  GETBUFFERS FUNCTION                                                                
//|  *No input parameters
//|  *Uses the class data members to get indicator's buffers
//+------------------------------------------------------------------+
void MyExpert::getBuffers()
  {
   if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0
      || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0)
     {
      Errormsg="Error copying indicator Buffers";
      Errcode = GetLastError();
      showError(Errormsg,Errcode);
     }
  }

Essa função é utilizada para copiar todos os buffers indicadores para os arranjos que especificamos nas variáveis de membro utilizando o nome do indicador respectivo.

A função CopyBuffer foi explicada no primeiro artigo. A função getBuffers não possui nenhum parâmetro de entrada porque estamos utilizando os valores das variáveis de membro da classe.

Utilizamos a nossa função de erro interno para exibir qualquer erro que possa ocorrer no processo de copiar os buffers.

A função MarginOK:

//+------------------------------------------------------------------+
//|  MARGINOK FUNCTION
//| *No input parameters
//| *Uses the Class data members to check margin required to place a trade
//|  with the lot size is ok
//| *Returns TRUE on success and FALSE on failure
//+------------------------------------------------------------------+
bool MyExpert::MarginOK()
  {
   double one_lot_price;                                                        //Margin required for one lot
   double act_f_mag     = AccountInfoDouble(ACCOUNT_FREEMARGIN);                //Account free margin
   long   levrage       = AccountInfoInteger(ACCOUNT_LEVERAGE);                 //Leverage for this account
   double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE);  //Total units for one lot
   string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE);        //Base currency for currency pair
                                                                                //
   if(base_currency=="USD")
     {
      one_lot_price=contract_size/levrage;
     }
   else
     {
      double bprice= SymbolInfoDouble(symbol,SYMBOL_BID);
      one_lot_price=bprice*contract_size/levrage;
     }
// Check if margin required is okay based on setting
   if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct))
     {
      return(false);
     }
   else
     {
      return(true);
     }
  }
  

Essa função está na verdade fazendo dois trabalhos. Ela verifica para se certificar que possuímos margem livre o suficiente para colocar a negociação e também verifica para se certificar que não utilizaremos mais do que uma porcentagem especificada da margem livre disponível para colocar a negociação. Dessa forma podemos controlar o quanto de dinheiro utilizamos para cada negociação.

Utilizamos a função AccountInfoDouble() juntamente com o identificador ENUM_ACCOUNT_INFO_DOUBLE para conseguir a margem livre para a conta. Também utilizamos a função AccountInfoInteger() juntamente com o identificador ENUM_ACCOUNT_INFO_INTEGER para conseguir a alavancagem para a conta. A AccountInfoInteger() e AccountInfoDouble() são funções da conta utilizadas para conseguir detalhes da conta atual utilizando o EA.

double  AccountInfoDouble(
   int  property_id      // identifier of the property
   );

Também utilizamos as funções de propriedade dos símbolos SymbolInfoDouble() e SymbolInfoString() para conseguir o tamanho do contrato e moeda corrente base para o símbolo atual (par de moeda corrente) respectivamente. A função SymbolInfoDouble() toma o nome do símbolo e um identificador ENUM_SYMBOL_INFO_DOUBLE como parâmetros enquanto a função SymbolInfoString() toma o nome do símbolo e um indicador ENUM_SYMBOL_INFO_STRING como parâmetros. Os resultados para essas funções estão armazenados nas variáveis declaradas para cada tipo de dado.

double  SymbolInfoDouble(
   string  name,        // symbol
   int     prop_id      // identifier of the property
   );

O cálculo que fizemos aqui é muito simples.

Para conseguir a margem necessária para colocar a negociação, consideramos duas situações:

  1. A moeda corrente base é USD (USD/CAD, USD/CHF, USD/JPY, etc.).

Margem necessária = Tamanho do contrato por lote / alavancagem.

2. A moeda corrente não é USD (EUR/USD, etc.).

Margem necessária = preço atual do símbolo * tamanho do contrato por lote/alavancagem.

Agora podemos decidir verificar se a margem necessária para negociar o tamanho ou volume de lote necessário é maior do que a porcentagem de margem livre que você quer utilizar para uma negociação. Se a margem necessária é menor a função retorna VERDADEIRA e a negociação é colocada, caso contrário, ela retorna FALSA e a negociação não será colocada.

A função doInit:

//+-----------------------------------------------------------------------+
// OUR PUBLIC FUNCTIONS                                                   |
//+-----------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| DOINIT FUNCTION
//| *Takes the ADX indicator's Period and Moving Average indicator's 
//| period as input parameters 
//| *To be used in the OnInit() function of our EA                                                               
//+------------------------------------------------------------------+
void MyExpert::doInit(int adx_period,int ma_period)
  {
//--- Get handle for ADX indicator
   ADX_handle=iADX(symbol,period,adx_period);
//--- Get the handle for Moving Average indicator
   MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE);
//--- What if handle returns Invalid Handle
   if(ADX_handle<0 || MA_handle<0)
     {
      Errormsg="Error Creating Handles for indicators";
      Errcode=GetLastError();
      showError(Errormsg,Errcode);
     }
// Set Arrays as series
// the ADX values arrays
   ArraySetAsSeries(ADX_val,true);
// the +DI value arrays
   ArraySetAsSeries(plus_DI,true);
// the -DI value arrays
   ArraySetAsSeries(minus_DI,true);
// the MA values arrays
   ArraySetAsSeries(MA_val,true);
  }

Essa é uma função pública que pretendemos utilizar na função OnInit() do nosso EA que escreveremos em breve e ela fará duas coisas.

Primeiramente, ela configurará os nomes para os nossos indicadores e também realizar a ação de arranjo configurado como série nas variáveis do arranjo. Ela possui dois parâmetros que serão fornecidos de dentro do código do nosso EA.

A função doUninit:

//+------------------------------------------------------------------+
//|  DOUNINIT FUNCTION
//|  *No input parameters
//|  *Used to release ADX and MA indicators handleS                                                                |
//+------------------------------------------------------------------+
void MyExpert::doUninit()
  {
//--- Release our indicator handles
   IndicatorRelease(ADX_handle);
   IndicatorRelease(MA_handle);
  }
  

Essa função é também uma função de membro público que será utilizada na função UnDeInit para o nosso EA liberar todos os nomes dos indicadores que utilizamos. Ele não possui nenhum parâmetro de entrada.

A função checkBuy:

//+------------------------------------------------------------------+
//| CHECKBUY FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Buy setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Buy conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkBuy()
  {
/*
    Check for a Long/Buy Setup : MA increasing upwards, 
    previous price close above MA, ADX > ADX min, +DI > -DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Buy Conditions
   bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards
   bool Buy_Condition_2=(Closeprice>MA_val[1]);         // previous price closed above MA
   bool Buy_Condition_3=(ADX_val[0]>ADX_min);          // Current ADX value greater than minimum ADX value
   bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]);       // +DI greater than -DI
//--- Putting all together   
   if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

Essa função será utilizada para verificar se a má condição de compra foi configurada ou não. É por isso que seu tipo de retorno é bool. Significa que ela retornará um VERDADEIRO ou um FALSO. É aqui que definimos a nossa estratégia de negociação de compra. Se uma condição de compra é atendida baseado na estratégia que definimos, ela retornará VERDADEIRA; no entanto, se a condição de compra não for atendida ela retornará FALSA. Quando utilizando essa função em nosso código, então colocaremos uma compra se ela retornar VERDADEIRA.

A primeira coisa que fizemos aqui foi chamar a função de membro interna getBuffers(), que copiará todos os valores de arranjo necessários pela função checkBuy para as variáveis do arranjo correspondente.

As condições codificadas aqui já foram explicadas no primeiro artigo.

A função checkSell:

//+------------------------------------------------------------------+
//| CHECKSELL FUNCTION
//| *No input parameters
//| *Uses the class data members to check for Sell setup based on the
//|  the defined trade strategy 
//| *Returns TRUE if Sell conditions are met or FALSE if not met
//+------------------------------------------------------------------+
bool MyExpert::checkSell()
  {
/*
    Check for a Short/Sell Setup : MA decreasing downwards, 
    previous price close below MA, ADX > ADX min, -DI > +DI
*/
   getBuffers();
//--- Declare bool type variables to hold our Sell Conditions
   bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]);  // MA decreasing downwards
   bool Sell_Condition_2=(Closeprice <MA_val[1]);                         // Previous price closed below MA
   bool Sell_Condition_3=(ADX_val[0]>ADX_min);                            // Current ADX value greater than minimum ADX
   bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]);                        // -DI greater than +DI

//--- Putting all together
   if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4)
     {
      return(true);
     }
   else
     {
      return(false);
     }
  }

Assim como a checkBuy, essa função será utilizada para verificar se uma condição de compra foi definida ou não. É por isso que seu tipo de retorno também é bool. Significa que ela retornará um VERDADEIRO ou um FALSO. É aqui que definimos a nossa estratégia de negociação de venda. Se uma condição de venda é atendida baseado na estratégia que definimos, ela retornará VERDADEIRA; no entanto, se a condição de venda não for atendida ela retornará FALSA.

Quando utilizando essa função em nosso código, então colocaremos uma venda se ela retornar VERDADEIRA. Assim como em checkBuy, chamamos a função interna getBuffers() primeiro. As condições codificadas aqui também já foram explicadas no primeiro artigo.

A função openBuy:

//+------------------------------------------------------------------+
//| OPENBUY FUNCTION
//| *Has Input parameters - order type, Current ASK price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=askprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Buy order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=askprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Buy order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

É a função que abre uma posição de compra sempre que é chamada em nosso EA. Ela possui, como parâmetros de entrada, a maior parte das variáveis que serão necessárias para colocar a negociação; e algumas das variáveis serão fornecidas pelo código do nosso EA. Você notará, como explicado no primeiro artigo, que utilizamos as variáveis do tipo MqlTraderequest aqui.

Não precisaremos utilizá-las no código do nosso EA. Antes de a negociação ser colocada, queremos confirmar se o usuário quer verificar a margem, se o valor de Chk_Margin (que será obtido do EA) é 1, então podemos chamar a função MarginOK() para fazer isso para nós. O resultado dessa função determina o próximo passo a se tomar. No entanto, se o usuário não quer verificar a margem, então simplesmente continuamos e colocamos a negociação.

A função openSell:

//+------------------------------------------------------------------+
//| OPENSELL FUNCTION
//| *Has Input parameters - order type, Current BID price, Stop Loss,
//|  Take Profit, deviation, comment
//| *Checks account free margin before pacing trade if trader chooses
//| *Alerts of a success if position is opened or shows error
//+------------------------------------------------------------------+
void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="")
  {
//--- do check Margin if enabled
   if(Chk_Margin==1)
     {
      if(MarginOK()==false)
        {
         Errormsg= "You do not have enough money to open this Position!!!";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
      else
        {
         trequest.action=TRADE_ACTION_DEAL;
         trequest.type=otype;
         trequest.volume=LOTS;
         trequest.price=bidprice;
         trequest.sl=SL;
         trequest.tp=TP;
         trequest.deviation=dev;
         trequest.magic=Magic_No;
         trequest.symbol=symbol;
         trequest.type_filling=ORDER_FILLING_FOK;
         // send
         OrderSend(trequest,tresult);
         // check result
         if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
           {
            Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
           }
         else
           {
            Errormsg= "The Sell order request could not be completed";
            Errcode =GetLastError();
            showError(Errormsg,Errcode);
           }
        }
     }
   else
     {
      trequest.action=TRADE_ACTION_DEAL;
      trequest.type=otype;
      trequest.volume=LOTS;
      trequest.price=bidprice;
      trequest.sl=SL;
      trequest.tp=TP;
      trequest.deviation=dev;
      trequest.magic=Magic_No;
      trequest.symbol=symbol;
      trequest.type_filling=ORDER_FILLING_FOK;
      //--- send
      OrderSend(trequest,tresult);
      //--- check result
      if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed 
        {
         Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!");
        }
      else
        {
         Errormsg= "The Sell order request could not be completed";
         Errcode =GetLastError();
         showError(Errormsg,Errcode);
        }
     }
  }

Assim como a função openBuy, essa função abre uma posição de venda sempre que é chamada em nosso EA. Ela possui, como parâmetros de entrada, a maior parte das variáveis que serão necessárias para colocar a negociação; e algumas das variáveis serão fornecidas pelo código do nosso EA.

Assim como fizemos quando abrindo uma posição de compra, antes de uma negociação ser colocada, queremos confirmar se o usuário quer verificar a margem, se o valor de Chk_Margin (que será obtido do EA) é 1, então podemos chamar a função MarginOK() para fazer isso para nós.

O resultado dessa função determina o próximo passo a se tomar. No entanto, se o usuário não quer verificar a margem, então simplesmente continuamos e colocamos a negociação.

Agora terminamos a declaração e definição da nossa classe e as funções de membro, no entanto, deixamos de fora algumas outras tarefas que pretendemos tratar no código do nosso EA. Essas incluem verificar a presença de barras disponíveis, verificar a presença de novas barras e verificar a presença de posições abertas disponíveis. Elas serão tratadas no código do nosso EA.

Para ver uma lista de todas as funções e métodos da nossa classe, clique no comando/menu das funções no MetaEditor como mostrado abaixo. A função exibe todas as funções de membro incluindo o destruidor que não declaramos explicitamente em nosso código.

Os membros protegidos são apontados por setas verdes enquanto que o construtor e o destruidor são apontados por setas azuis.

Funções de membro da classe

Figura 3. Nossas funções de membro da classe mostrando o destruidor da classe

Então o que vem em seguida?

Eu ouvi você dizer, depurar? Talvez você esteja certo. É sempre bom testar e ver se o seu código possui erros caso contrário você ficará decepcionado quando o liberar para o público. O problema aqui é que isso é apenas um arquivo incluso, não é um código de consultor especialista ou um script ou código indicador que pode ser anexado ao gráfico. Nesse ponto você tem duas opções (pela minha experiência).

  • Ou você arrisca apertar o botão de depuração no seu editor de forma que o depurador relatará qualquer erro em seu código com a exceção de um erro 'nenhum arquivo executável produzido', que será exibido porque um arquivo .mqh não pode ser compilado em um arquivo .ex5. OU
  • Vá em frente e escreva o código para o EA que utilizará a sua classe. Uma vez que você começar a depurar o EA, o arquivo incluído será verificado juntamente com ele. De fato, essa é a melhor forma e mais aceitável de se fazer isso.

Figura 4. Arquivos .mqh não podem ser compilados

2.2. ESCREVENDO UM EXPERT ADVISOR

Acho que o seu editor ainda está aberto. Comece um novo documento novamente mas dessa vez selecione Expert Advisor. (Por favor veja o primeiro artigo para detalhes). Mas dessa vez, nomeie o seu EA de ‘my_oop_ea’.

É aqui que você deveria estar agora:


Agora estamos prontos para escrever o nosso EA baseado em OOP.

A primeira coisa que faremos aqui é incluir a classe que acabamos de escrever utilizando o pré-processador command da diretiva #include. Inclua a classe imediatamente após o último comando de propriedade do pré-processador.

//+------------------------------------------------------------------+
//|                                                    my_oop_ea.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
// Include  our class
#include 

Há duas formas de incluir um arquivo.

// Include  using angle brackets
#include 
// Include  using quotations
#include "my_expert_class.mqh"

Quando utilizamos um parênteses em ângulo (), significa que o arquivo a ser incluído será tomado do diretório incluso padrão (isto é, incluir a pasta dentro do diretório do MQL5). O diretório atual (que é a pasta do especialista dentro do diretório do MQL5 não será considerada como um local possível para o arquivo). No entanto, se o arquivo é cercado por aspas (" ... "), será considerado que o arquivo está no diretório atual (que é a pasta do especialista) e o diretório padrão (pasta inclusa) não será verificado.

Se a sua classe é salva na pasta inclusa (diretório padrão) e você usa as aspas em vez de parênteses em ângulo ou vice-versa, você receberá um erro quando compilando o código.


Figura 5. Uma mensagem de erro exibida quando o arquivo incluso não pode ser encontrado

PARÂMETROS DE ENTRADA DO EA

//--- input parameters
input int      StopLoss=30;      // Stop Loss
input int      TakeProfit=100;   // Take Profit
input int      ADX_Period=14;    // ADX Period
input int      MA_Period=10;     // Moving Average Period
input int      EA_Magic=12345;   // EA Magic Number
input double   Adx_Min=22.0;     // Minimum ADX Value
input double   Lot=0.2;          // Lots to Trade
input int      Margin_Chk=0;     // Check Margin before placing trade(0=No, 1=Yes)
input double   Trd_percent=15.0; // Percentage of Free Margin To use for Trading

A maior parte dos parâmetros de entrada não são novos. Vamos discutir os novos.

Introduzimos uma variável inteira para manter o valor de 1 se quisermos utilizar a verificação de margem ou 0 se não quisermos. Também declaramos outra variável para manter a porcentagem máxima de margem livre a ser utilizada na abertura de uma posição. Esses valores posteriormente serão utilizados em nosso objeto de classe quando criado.

Imediatamente após os parâmetros de entrada, definimos outros dois parâmetros (STP e TKP) que queremos que sejam capazes de manipular (para lidar com preços de 5 e 3 dígitos) já que não podemos modificar os valores das variáveis de entrada. Então criamos um objeto da nossa classe para uso dentro do código do nosso EA.

//--- Other parameters
int STP,TKP;   // To be used for Stop Loss & Take Profit values
// Create an object of our class
MyExpert Cexpert;

Como explicado anteriormente, para criar um objeto ou classe, você utiliza o nome da classe seguido pelo nome do objeto que você deseja criar. Aqui criamos um objeto Cexpert que é um tipo do MyExpert. O Cexpert pode agora ser utilizado para acessar todas as funções de membro públicas da classe MyExpert.

SEÇÃO DE INICIALIZAÇÃO DO EA

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {

//--- Run Initialize function
   Cexpert.doInit(ADX_Period,MA_Period);
//--- Set all other necessary variables for our class object
   Cexpert.setPeriod(_Period);     // sets the chart period/timeframe
   Cexpert.setSymbol(_Symbol);     // sets the chart symbol/currency-pair
   Cexpert.setMagic(EA_Magic);    // sets the Magic Number
   Cexpert.setadxmin(Adx_Min);    // sets the ADX miniumm value
   Cexpert.setLOTS(Lot);          // set the Lots value
   Cexpert.setchkMAG(Margin_Chk); // set the margin check variable
   Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade
//--- Let us handle brokers that offers 5 digit prices instead of 4
   STP = StopLoss;
   TKP = TakeProfit;
   if(_Digits==5 || _Digits==3)
     {
      STP = STP*10;
      TKP = TKP*10;
     }  
//---
   return(0);
  }

Nesse ponto chamados a função doInit da nossa classe e passamos as variáveis do período ADX e MA para ela. Em seguida configure todas as outras variáveis que serão necessárias pelo objeto que acabamos de criar de forma que será armazenada nas variáveis de membro do objeto utilizando as funções que já descrevemos quando escrevendo a nossa classe.

A próxima linha de códigos não deve ser estranha, só decidimos ajudar os nossos valores de Stop loss e Take profit para preços de três e cinco dígitos.

SEÇÃO DE DESINICIALIZAÇÃO DO EA

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Run UnIntilialize function
   Cexpert.doUninit();
  }

Chamamos a função doUninit para a classe de forma a liberar todos os nomes de indicador que devem ter sido criados na função de inicialização do EA.

SEÇÃO DE ONTICK DO EA

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- Do we have enough bars to work with
   int Mybars=Bars(_Symbol,_Period);
   if(Mybars<60) // if total bars is less than 60 bars
     {
      Alert("We have less than 60 bars, EA will now exit!!");
      return;
     }

//--- Define some MQL5 Structures we will use for our trade
   MqlTick latest_price;      // To be used for getting recent/latest price quotes
   MqlRates mrate[];          // To be used to store the prices, volumes and spread of each bar
/*
     Let's make sure our arrays values for the Rates
     is store serially similar to the timeseries array
*/
// the rates arrays
   ArraySetAsSeries(mrate,true);

A primeira coisa que fazemos aqui é verificar o total de barras disponíveis. Se é o suficiente para o nosso EA negociar, ele irá, caso contrário ele não irá negociar até que tenhamos barras o suficiente (isto é, 60 barras). Então declaramos duas variáveis da estrutura do MQL5 (MqlTick e MqlRates). E, finalmente, utilizamos a função ArraySetAsSeries nos arranjos de taxa.

//--- Get the last price quote using the MQL5 MqlTick Structure
   if(!SymbolInfoTick(_Symbol,latest_price))
     {
      Alert("Error getting the latest price quote - error:",GetLastError(),"!!");
      return;
     }

//--- Get the details of the latest 3 bars
   if(CopyRates(_Symbol,_Period,0,3,mrate)<0)
     {
      Alert("Error copying rates/history data - error:",GetLastError(),"!!");
      return;
     }

//--- EA should only check for new trade if we have a new bar
// lets declare a static datetime variable
   static datetime Prev_time;
// lest get the start time for the current bar (Bar 0)
   datetime Bar_time[1];
// copy time
   Bar_time[0] = mrate[0].time;
// We don't have a new bar when both times are the same
   if(Prev_time==Bar_time[0])
     {
      return;
     }
//copy time to static value, save
   Prev_time = Bar_time[0]; 
   

Aqui, utilizamos a função SymbolInfoTick para conseguir a cotação de preço mais atual e usamos CopyRates para conseguir as últimas taxas para as últimas três barras (barra presente inclusive). As próximas linhas do código verificam se possuímos uma nova barra. Declaramos duas variáveis datetime, uma é a variável estática (Prev_Time) e a outra é Bar_Time.

Se possuímos uma nova barra, o tempo da barra é armazenado na variável estática Prev_Time de forma que seremos capazes de comparar seu valor com o valor de Bar_Time no próximo ponto. No próximo ponto, se a Prev_Time igualar a Bar_Time, então é ainda a mesma barra cujo tempo foi armazenado. Então o nosso EA relaxará.

Se no entanto Bar_Time não for igual a Prev_Time, então nós possuímos uma nova barra. Decidimos armazenar o tempo de início da nova barra na variável datetime estática, Prev_Time e nosso EA pode agora proceder para verificar as novas oportunidades de COMPRA ou VENDA.

//--- we have no errors, so continue
//--- Do we have positions opened already?
    bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position
    
    if (PositionSelect(_Symbol) ==true)  // we have an opened position
    {
         if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
         {
            Buy_opened = true;  //It is a Buy
         }
         else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
         {
            Sell_opened = true; // It is a Sell
         }
    }

Decidimos verificar se já abrimos uma posição. Só queremos nos certificar que abrimos uma negociação de compra quando não há compra aberta e uma negociação de venda quando não há venda aberta.

// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1
   Cexpert.setCloseprice(mrate[1].close);  // bar 1 close price
//--- Check for Buy position
   if(Cexpert.checkBuy()==true)
     {
      // Do we already have an opened buy position
      if(Buy_opened)
        {
         Alert("We already have a Buy Position!!!");
         return;    // Don't open a new Buy Position
        }
      double aprice = NormalizeDouble(latest_price.ask,_Digits);              // current Ask price
      double stl    = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss
      double tkp    = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit
      int    mdev   = 100;                                                    // Maximum deviation
      // place order
      Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev);
     }

Agora estamos de volta ao objeto que criamos, porquê? Porque fomos capazes de fazer todas as verificações necessárias que são necessárias para o nosso objeto fazer seu trabalho.

A primeira ciosa que fazemos é conseguir o preço fechado da barra anterior utilizando a nossa função de membro do objeto setCloseprice.

Então chamamos a função checkBuy para descobrir se uma condição para compra está configurada, se ela retornar VERDADEIRA, então queremos nos certificar que já não possuímos uma posição de compra já aberta. Se não possuímos uma posição de compra já aberta então preparamos as variáveis necessárias a serem utilizadas para a nossa ordem (o tipo de ordem, o preço atual de VENDA, stop loss, take profit e desvio máximo) e chamar a função openBuy. Viu como é fácil utilizar a classe que escrevemos.

//--- Check for any Sell position
   if(Cexpert.checkSell()==true)
     {
      // Do we already have an opened Sell position
      if(Sell_opened)
        {
         Alert("We already have a Sell position!!!");
         return;    // Don't open a new Sell Position
        }
      double bprice=NormalizeDouble(latest_price.bid,_Digits);                 // Current Bid price
      double bstl    = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss
      double btkp    = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit
      int    bdev=100;                                                         // Maximum deviation
      // place order
      Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev);
     }

Isso é o mesmo que fizemos acima. Uma vez que estamos verificando a existência de uma compra, chamamos a função checkSell e se ela retornar VERDADEIRA então nós não já possuímos uma posição de venda aberta, preparamos as variáveis necessárias e colocamos nossa ordem (o tipo de ordem, preço de VENDA atual, stop loss, take profit e desvio máximo) e então chamamos a função openSell.

Bem fácil, não é? Terminamos de escrever os códigos. Agora é hora de depurar o nosso código. Se você não sabe como utilizar o depurador, por favor leia o primeiro artigo para um melhor entendimento.

Quando você pressiona F5 ou pressiona o botão de depuração, o arquivo incluído (nossa classe) será incluso e verificado e se houver algum erro, ele o reportará. Uma vez que você vê o erro, você precisa voltar ao código e corrigir o erro.

Figura 6. Nosso arquivo é incluso quando depurando o código do EA principal

Se tudo estiver certo você fez bem. Agora é hora de testar o nosso EA utilizando o verificador de estratégia. Precisamos compilar o nosso EA antes de testar ele com o verificador de estratégia. Para fazer isso, clique no botão compilar ou pressione F7 no teclado do seu computador.

Figura 7. Clique no botão compilar para compilar o nosso código

Da barra de menu do terminal de negociação, vá em Visualizar --> Verificador de estratégia ou pressione CONTROL+R para iniciar o verificador de estratégia (para detalhes de como utilizar o verificador, por favor leia o primeiro artigo).

Para você ser capaz de testar o EA com o verificador de estratégia, você precisa primeiramente compilar ele. Se você não o compilar, receberá uma mensagem de erro quando selecionar o Expert Advisor na barra de configurações do verificador de estratégia (acabei de descobrir isso na nova versão do terminal).

Figura 8. O código do EA deve ser compilado antes de seu uso no verificador de estratégia

Encontre abaixo os resultados do verificador de estratégia para o nosso Expert Advisor baseado em OOP.

Figura 9. Os resultados de negociação do nosso Expert Advisor orientado a objeto

O gráfico:

Figura 10. Os resultados do gráfico para o nosso Expert Advisor orientado a objeto

O relatório/diário de atividade de negociação:


Figura 11. Os resultados da atividade de negociação para o nosso Expert Advisor orientado a objeto


O gráfico para o teste:

Figura 12. Os resultados do gráfico de negociação para o nosso Expert Advisor orientado a objeto

>Conclusão

Nesse artigo discutimos, a algum nível, os básicos de uma classe e como utilizá-la na escrita de um consultor experiente. Não nos aprofundamos muito nas áreas avançadas das classes mas o que discutimos nesse artigo é o suficiente para ajudar você a se desenvolver a um nível em que será capaz de escrever o código do seu próprio Expert Advisor orientado a objeto.

Também discutimos sobre como podemos verificar se há margens livres de forma que o nosso EA não negocie quando a margem livre disponível não foi o suficiente para a posição que queremos abrir.

Você agora concordará comigo que a linguagem do novo MQL5 tem muito mais a oferecer e você não precisa ser um guru da programação para se aproveitar dessa nova linguagem Essa é a razão principal por trás de escrever os guias de passo a passo.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/116

Arquivos anexados |
my_expert_class.mqh (17.52 KB)
my_oop_ea.mq5 (6.77 KB)
Como Encomendar um Robô de Negociação em MQL5 e MQL4 Como Encomendar um Robô de Negociação em MQL5 e MQL4
A "Freelance" é o maior serviço freelance para a encomenda de robôs de negociação em MQL4 e indicadores técnicos. Centenas de desenvolvedores profissionais estão prontos para desenvolver aplicativos de negociação personalizados para a plataforma MetaTrader 4/5.
Uma solução livre de DLL para comunicação entre os terminais MetaTrader utilizando pipes nomeados Uma solução livre de DLL para comunicação entre os terminais MetaTrader utilizando pipes nomeados
O artigo descreve como implementar a Comunicação Interprocesso entre os terminais do cliente MetaTrader 5 usando pipes nomeados. Para o uso de pipes nomeados, a classe CNamedPipes é desenvolvida. Para o teste de seu uso e medir a conexão por ele, o indicador de tick, o servidor e os scripts do cliente são apresentados. O uso de pipes nomeados é suficiente para cotas em tempo real.
O método ideal para calcular o volume da posição total pelo número mágico especificado O método ideal para calcular o volume da posição total pelo número mágico especificado
O problema do cálculo do volume de posição total do símbolo especificado e número mágico é considerado neste artigo. O método proposto requer apenas a parte mínima necessária do histórico de negócios, descobre o tempo mais próximo quando a posição total foi igual a zero, e realiza os cálculos com os negócios recentes. O trabalho com variáveis globais do terminal de cliente também é considerado.
Uma biblioteca para construção de um gráfico pelo Google Chart API Uma biblioteca para construção de um gráfico pelo Google Chart API
A construção de vários tipos de diagramas é uma parte essencial da análise da situação de mercado e o teste de um sistema de negócio. Frequentemente, a fim de construir um diagrama de boa aparência, é necessário organizar a saída de dados em um arquivo, após o qual é usado em aplicações como MS Excel. Isso não é muito conveniente e nos tira a capacidade de atualizar os dados dinamicamente. O Google Charts API fornece meios para criar gráficos em modos online, enviando uma solicitação especial para o servidor. Neste artigo, tentamos automatizar o processo de criação de tal solicitação e obter um gráfico a partir do servidor Google.