Criação de Consultores Multiespecializados com base em Modelos de Comércio

Vasiliy Sokolov | 10 fevereiro, 2014

Introdução

As capacidades técnicas do terminal MetaTrader 5 e seu verificador estratégico determinam o trabalho e verificação dos sistemas de comércio de moeda múltipla. A complexidade de desenvolvimento de tais sistemas para MetaTrader 4 condicionado, em primeiro lugar, pela incapacidade de graduação simultânea de teste de diversas ferramentas comerciais. Além disso, os recursos de linguagem limitados do MQL4 não permitiam a organização de estruturas de dados complexos e gerenciamento eficiente dos dados.

Com o lançamento do MQL5, a situação mudou. A partir de agora, MQL5 suporta a abordagem ativada por objetos, é baseado em um mecanismo de desenvolvimento de funções auxiliares, e tem até mesmo um conjunto de bases clássicas Biblioteca Padrão para facilitar as tarefas diárias de usuários - que vão desde a organização dos dados para as interfaces de trabalho até as funções padrões do sistema.

Embora as especificações técnicas do verificador de estratégia e do terminal permitam o uso de moedas múltiplas EAs, eles não têm embutido métodos para a paralelização do trabalho de uma única EA simultaneamente em vários instrumentos ou prazos. Como antes, para o trabalho de uma EA no caso mais simples, você precisa executá-la na janela do símbolo, que determina o nome do instrumento do comércio e seu prazo. Como resultado, a metodologia de trabalho aceita desde o momento da MetaTrader 4, não permite tirar o máximo proveito do testador de estratégia e do terminal MetaTrader 5.

A situação é complicada pelo fato de que apenas uma posição acumulada para cada instrumento, igual à quantidade total de ofertas em que instrumento, é permitida. Certamente, a transição para uma posição líquida é correta e rápida. A posição líquida se aproxima da representação perfeita do interesse do profissional em um mercado particular.

Entretanto, tal organização de negócios não faz o processo de comércio simples e facilmente visualizado. Anteriormente, era suficiente para uma EA selecionar sua ordem aberta (por exemplo, a ordem podia ser identificada com o número mágico), e implementar as medidas necessárias. Agora, até mesmo a ausência de uma posição líquida em um instrumento não significa que uma instância específica de um EA não está no mercado no momento!

Desenvolvedores terceirizados oferecem várias maneiras de resolver o problema com a posição líquida - que vão desde a escrita de gerentes especiais de ordens virtuais (veja o artigo Um Gerente de Ordem Virtual para controlar as ordens dentro do ambiente de posição centrada MT5) à integrar as entradas em uma posição agregada, usando o número mágico (veja O Método Ideal para o Cálculo do Volume da Posição Total especificado por um Número Mágico ou O Uso de ORDER_MAGIC para Comércio com Diferentes Consultores Especialistas em um Instrumento único).

No entanto, além de problemas com a posição agregada, existe um problema da chamada moeda múltipla, quando a mesma é requerida ao comércio EA em vários instrumentos. A solução para este problema pode ser encontrada no artigo Criação de um Consultor Especialista que Comercializa em Diversos Instrumentos.

Todos os métodos sugeridos funcionam e tem suas próprias vantagens. No entanto, a sua falha fatal é que cada um destes métodos está tentando abordar a questão a partir do seu próprio ponto de vista, oferecendo soluções que são, por exemplo, adequadas para comércio simultâneo de várias EAs num único instrumento, mas que não são adequadas para soluções de moeda múltipla.

Este artigo tem como objetivo resolver todos os problemas com uma única solução. O uso dessa solução pode resolver o problema da moeda múltipla e até mesmo testar o sistema múltiplo de interação entre diferentes ideias sobre um único instrumento. Isso parece difícil, ou mesmo impossível de atingir, mas na realidade é tudo muito fácil.

Imagine seu único EA comercializar simultaneamente em várias dezenas de estratégias comerciais, em todos os instrumentos disponíveis, e em todos os possíveis prazos! Além disso, a EA é facilmente testada no dispositivo de teste e, para todas as estratégias, incluídas na sua composição, existe um ou vários sistemas de trabalho de gestão de dinheiro.

Então, aqui estão as principais tarefas que teremos de resolver:

  1. A EA precisa comercializar com base em vários sistemas de comércio, ao mesmo tempo. Além disso, ela deve ser igualmente de fácil comércio em um único, bem como em vários, sistemas de transação;
  2. Todo o sistema de comércio implementado na EA, não deve entrar em conflito um com o outro. Cada sistema de comércio deve lidar apenas com sua própria contribuição à posição líquida total, e apenas as suas próprias ordens;
  3. Cada sistema deve ser igualmente fácil com as trocas comerciais com um único espaço de tempo do instrumento, bem como em todos os períodos de tempo de uma só vez.
  4. Cada sistema deve ser igualmente fácil com as trocas comerciais com um único instrumento no comércio, bem como em todos os instrumentos disponíveis de uma só vez.

Se examinarmos cuidadosamente a lista das tarefas que temos para lidar, vamos chegar a um banco de dados tridimensional. A primeira dimensão do banco de dados - o número de sistemas de comércio, a segunda dimensão - o número de prazos em que o TS específico necessita operar, e o terceiro - o número de instrumentos de comércio para o TS. Um cálculo simples mostra que mesmo uma EA tão simples como a Amostra MACD, quando se trabalha simultaneamente em oito principais pares de moeda, terá 152 soluções independentes: 1 EA 8 * pares * 19 prazos (prazos semanais e mensais não estão incluídos).

Se o sistema de comércio for muito maior, e o portfólio de comércio de EAs consideravelmente mais amplo, então, o número de soluções poderia facilmente ser superior a 500, e, em alguns casos, mais de 1000! é claro que é impossível configurar manualmente e depois carregar cada combinação separadamente. Portanto, é necessário construir um sistema de uma maneira que automaticamente ajustaria cada combinação, carregaria na memória do EA e então negociaria, baseado nas regras de um exemplo específico dessa combinação.


Termos e conceitos

Daqui em diante, a noção "estratégia decomércio" será substituída pelo termo mais específico modelo comercial ou simplesmente modelo. Um modelo comercial é uma categoria especial, construída de acordo com normas específicas, que descreve totalmente a estratégia comercial: indicadores utilizados no comércio, as condições comerciais de entrada e saída, os métodos de gerenciamento de dinheiro, etc. Cada modelo comercial é abstrato e não define parâmetros específicos para sua operação.

Um exemplo simples é a tática comercial, baseada no cruzamento de duas médias em movimento. Se a média em movimento rápida cruza a lenta para cima, abre-se um acordo para comprar, do contrário, para baixo, abre-se um acordo para vender. Esta formulação é suficiente para escrever um modelo de comércio que comercializa em suas bases.

No entanto, uma vez que tal modelo vai ser descrito, é necessário determinar os métodos das médias em movimento, sendo o seu período médio o período da janela de dados, e o instrumento nas quais o presente modelo vai ser comercial. Em geral, esse modelo abstrato irá conter parâmetros que deverão ser preenchidos uma vez que você precisa criar uma instância do modelo específica. é óbvio que, sob esta abordagem, um modelo abstrato pode ser um precursor de várias instâncias dos modelos, que diferem em seus parâmetros.


Rejeição completa da contabilidade da posição líquida

Muitos desenvolvedores estão tentando acompanhar a posição agregada. No entanto, podemos ver acima que nem o tamanho da posição agregada, nem sua dinâmica, são relevantes para uma determinada instância do modelo. O modelo pode ser curto, enquanto que a posição agregada pode absolutamente não existir (posição agregada neutra). Em contrapartida, a posição agregada pode ser curta, enquanto que o modelo terá uma posição longa.

De fato, vamos considerar estes casos mais detalhadamente. Suponha um instrumento de comércio com três diferentes táticas de negociação, cada qual tem o seu próprio sistema independente de gerenciamento de dinheiro. Também suponha que o primeiro dos três sistemas decidiu vender três contratos sem cobertura, ou simplesmente, fazer uma posição curta com um volume de três contratos. Após a conclusão dos negócios, a posição líquida consistirá somente dos negócios do primeiro sistema de negociação, seu volume será menos três contratos, ou simplesmente três contratos curtos sem cobertura. Após algum tempo, o segundo sistema comercial toma a decisão de comprar 4 contratos da mesma propriedade.

Como resultado, a posição líquida irá mudar, e irá constituir de 1 contrato longo. Desta vez ele irá incluir as contribuições de dois sistemas comerciais. Além disso, o terceiro sistema comercial entra em cena e faz uma posição curta com a mesma propriedade, com um volume de um contrato padrão. A posição líquida se tornará neutra porque-3 curta+ 4 longa - 1 short = 0.

Será que a ausência da posição líquida significa que todos os três sistemas comerciais não estão no mercado? De modo algum. Dois deles mantêm quatro contratos sem cobertura, o que significa que a cobertura irá ser feita ao longo do tempo. Por outro lado, o terceiro sistema mantêm 4 contratos longos, que ainda estão para serem vendidos novamente. Somente quando o pagamento integral dos quatro contratos curtos está completo, e uma venda protegida de quatro contratos longos é feita, a posição neutra significa uma verdadeira falta de posições em todos os três sistemas.

Pode-se, é claro, cada vez reconstituir toda a sequência de ações para cada um dos modelos e, assim, determinar a sua contribuição específica do tamanho da posição atual, porém existe um método muito mais simples. Este método é simples - é necessário abandonar completamente a contabilidade da posição agregada, que pode ser de qualquer tamanho e que pode depender de ambos fatores externos (por exemplo, troca manual), bem como internos (o trabalho de outros modelos da EA em um instrumento). Uma vez que não se pode depender da posição agregada atual, então como é que vamos explicar as ações de uma instância do modelo específico?

A maneira mais simples e eficaz seria equipar cada instância de um modelo com sua própria tabela de ordens, que consideraria todos as ordens - ambas, pendentes e aquelas iniciadas por um negócio ou excluídas. Ampla informação sobre as ordens são armazenadas no servidor de comércio. Conhecendo o bilhete da ordem, podemos obter quase todas as informações sobre um pedido, que vão desde o momento da sua abertura até seu volume.

A única coisa que precisamos fazer é ligar a ordem bilhete com uma instância específica do modelo. Cada instância do modelo terá que conter sua instância individual de uma categoria especial - a tabela de ordens, que conterá uma lista das ordens atuais, estabelecidas pela instância do modelo.


Projetando um modelo de negociação abstrato

Agora vamos tentar descrever a classe abstrata comum do modelo, as quais serão baseadas em táticas comerciais específicas . Uma vez que a EA deve usar vários modelos (ou ilimitada), é óbvio que esta classe deve ter uma interface uniforme através do qual um perito em poder externo vai transmitir os sinais.

Por exemplo, esta interface pode ser função Processing(). Simplificando, cada classe CModel terá sua função Processing(). Esta função será chamada a cada sinal ou a cada minuto, ou após a ocorrência de um novo evento do tipo Comércio.

Aqui está um exemplo simples de como resolver esta tarefa:

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;
        

Vamos entender como o código funciona. A classe base CModel contém uma variável protegida do tipo série chamada de m_name. A palavra-chave "protegido" permite o uso dessa variável pelos sucessores de classe, então seus descendentes já terão essa variável. Além disso, a classe base define a Processing() função virtual. Neste caso, a palavra 'virtual' indica que isto é uma cobertura ou a interface entre o Consultor Especialista e a instância específica do modelo.

Qualquer classe, herdada do CModel, terá a garantia de ter a Interface para interação Processing() . A aplicação do código desta função é delegada aos seus descendentes. Esta delegação é óbvia, uma vez que o funcionamento interno dos modelos podem diferir significativamente uns dos outros e, portanto, não há generalizações comuns que podem ser localizados a um nível CModel geral.

Mais detalhadamente está a descrição de duas classes cmodel_macd e cmodel_moving. Ambas são produzidas a partir da classe CModel, portanto, ambas tem suas próprias instâncias da função Processing() e a variável m_name. Note que a aplicação interna da função de Processamento () dos dois modelos é diferente. No primeiro modelo, ela consiste de Imprimir ("It is cmodel_macd. nome do Modelo é ", m_nome), em o segundo de Imprimir("é cmodel_moving. Modelo nome é ", m_nome). Em seguida, dois indicadores são criados, cada um deles podendo apontar para uma instância específica do modelo, para a classe do tipo cmodel_macd e o outro para cmodel_tipo de movimento.

Na função OnInit esses indicadores herdam os modelos de classes criadas dinamicamente, após o que dentro da função OnEvent() uma função Processing() é chamada, a qual está contida em cada classe. Ambos indicadores são anunciados em nível global, por isso mesmo após sair da função OnInit(), as classes criadas nele não são excluídas, mas continuam a existir em um nível global. Agora, a cada cinco segundos, a função OnTimer() irá experimentar ambos os modelos um após o outro, chamando-os na função Processing() .

Este sistema primitivo de amostragem dos modelos, o qual acabamos de criar, não tem flexibilidade e escalabilidade. O que devemos fazer se quisermos trabalhar com várias dezenas de tais modelos? Trabalhar com cada um deles separadamente é inconveniente. Seria muito mais fácil coletar todos os modelos em uma única comunidade, por exemplo, um banco de dados e depois repetir todos os elementos desse banco de dados, chamando a função Processing() de cada um desses elementos.

Mas o problema é que a organização de banco de dados exige que os dados armazenados nos mesmos sejam do mesmo tipo. No nosso caso, embora os modelos cmodel_macd e cmodel_moving sejam muito parecidos um com o outro, eles não são idênticos, o que torna automaticamente impossível usá-los em banco de dados.

Felizmente, os bancos de dados não são a única maneira de resumir os dados, existem outras generalizações mais flexíveis e escaláveis. Um deles é a técnica de listas vinculadas. Seu esquema de funcionamento é simples. Cada item que está incluso na lista geral deve conter dois indicadores. Um indicador aponta para o item anterior da lista, o segundo - para o próximo.

Além disso, conhecendo o número de índice do item, você sempre poderá se referir a ele. Quando você quiser adicionar ou excluir um item, é suficiente reconstruir seus indicadores, e os indicadores dos itens vizinhos, de modo que eles sempre se refiram uns aos outros. Conhecer a organização interna dessas comunidades não é necessário, basta entender o seu dispositivo comum.

A instalação padrão do MetaTrader 5 inclui uma classe auxiliar especial CList, que oferece a oportunidade de trabalhar com listas vinculadas. No entanto, o elemento desta lista só pode ser um objeto do tipo CObject, já que só eles tem os indicadores especiais para trabalhar com listas vinculadas. Por si só, a classe CObject é bastante primitiva, sendo simplesmente uma interface para interagir com a classe CList.

Você pode ver isso dando uma olhada em sua implementação:

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class element storage.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

Como pode ser visto, a base dessa classe são dois indicadores, para as quais as características típicas são implementadas.

Agora a parte mais importante. Devido ao mecanismo de herança, é possível incluir esta classe no modelo de comércio, o que significa que a classe do modelo de comércio pode ser inclusa em uma lista de tipo CList! Vamos tentar fazer isso.

E assim, vamos fazer a nossa classe abstrata CModel como um descendente da classe CObject :

class CModel : public CObject

Uma vez que nossas classes cmodel_moving e cmodel_average são herdadas da CModel class, elas incluem os dados e métodos de CObject class, portanto, elas podem ser inclusas na lista de CList tipo. O código-fonte que cria dois modelos comerciais condicionais, e os coloca na lista e, sequencialmente, exemplifica cada sinal, é apresentada a seguir:

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}
        

Uma vez que este programa é compilado e executado, linhas semelhantes indicando o funcionamento normal da EA, devem aparecer no diário "Especialistas".

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  
        

Vamos analisar em detalhecomo esse código funciona. Portanto, como mencionado acima, o nosso modelo básico de comércio CModel é derivado da classe CObject, que nos dá o direito de incluir os descendentes do modelo básico na lista do tipo CList:

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);
        

A organização dos dados exige que se trabalhe com indicadores. Uma vez que os indicadores de modelos específicos são criados a nível local da função OnInit() e são inseridos na lista global lista_modelos, a necessidade deles desaparece e eles podem ser destruídos de forma segura, juntamente com outras variáveis desta função.

Em geral, uma característica distintiva do modelo proposto é que a única variável global (além das próprias classes modelo) é uma lista vinculada de forma dinâmica desses modelos. Assim, desde o início, existe um apoio de um elevado grau de síntese do projeto.

Se a criação do modelo falhar por alguma razão (por exemplo, os valores dos parâmetros necessários foram coletados incorretamente), então este modelo não será adicionado à lista. Isso não afetará o trabalho global da EA, uma vez que ela vai lidar apenas com os modelos que foram adicionados com êxito à lista.

A amostragem dos modelos criados é feita na OnTick() função. Consiste de um for loop. Neste circuito é determinado o número de elementos, após o qual existe uma passagem em série desde o primeiro elemento do ciclo (i = 0) até o último (i <list_models.Total();i++):

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

O indicador para a classe base CModel é usado como um adaptador universal. Isso garante que qualquer função que é suportada por este indicador estará disponível para os modelos derivados. Nesse caso, precisamos da função somente Processing(). Cada modelo tem a sua própria versão de Processing(), a implementação interna que pode diferir funções similares de outros modelos. Sobrecarregar esta função não é necessário, ela só pode existir de uma forma: não tendo nenhum parâmetro de entrada e retornando o valor do tipo booleano.

Tarefas que caem sobre os "ombros" desta função são extensas:

  1. A função deve determinar de forma independente a situação atual do mercado com base em seus próprios modelos de comércio.
  2. Após tomada uma decisão para entrar no mercado, a função deve calcular de forma independente a quantidade necessária de garantia, envolvida no negócio (a margem), o volume de negócio, o valor de perda máxima possível ou o nível de lucro.
  3. O comportamento do modelo deve ser correlacionado com as suas ações anteriores. Por exemplo, se houver uma posição curta, por iniciativa do modelo, então construí-la ainda mais no futuro pode ser impossível. Todas estas verificações devem ser realizadas dentro da função Processing() .
  4. Cada uma dessas funções deve ter acesso aos parâmetros comuns, tais como o estado da conta. Com base nesses dados, esta função deve realizar sua própria gestão do dinheiro, usando os parâmetros embutidos em seu modelo. Por exemplo, se a gestão de dinheiro em um dos modelos é feita através dos meios da fórmula ideal f, então o seu valor deve ser diferente para cada um dos seus modelos.

Obviamente, o processamento da função Processing() , devido à magnitude das tarefas colocadas sobre ela,, vai depender do aparelho desenvolvido nas classes auxiliares, aqueles que são inclusos no MetaTrader 5, bem como aqueles que são especificamente concebidos para esta solução.

Como pode ser visto, a maioria do trabalho é delegada à instâncias específicas do modelo. O nível externo da AE dá o controle por sua vez para cada modelo, e seu trabalho está concluído. O que vai ser feito pelo modelo específico dependerá da sua lógica interna.

Em geral, o sistema de interação que construímos pode ser descrito pelo seguinte esquema:

Note que embora a classificação de modelos, conforme apresentado no código acima, ocorre dentro da função OnTick() , ela não tem de ser necessariamente assim. O ciclo de classificação pode ser facilmente colocado em qualquer outra função desejada, tal como a OnTrade() ou OnTimer().

A tabela de ordens virtuais - a base do modelo

Uma vez que nós combinamos todos os modelos comerciais em uma única lista, é hora de descrever o processo de comércio. Vamos voltar para a classe CModel e tentar completá-la com dados e funções adicionais, que podem ser baseadas no processo comercial.

Como mencionado acima, o novo modelo de posição líquida define as diferentes regras para trabalhar com ordens e negócios. Em MetaTrader 4, cada negócio é acompanhado por sua ordem que existiu na aba "Comércio", a partir do momento da sua emissão até o término da ordem ou o fechamento do negócio, iniciado por ele.

No MetaTrader 5 as ordens pendentes só existem até o momento da conclusão efetiva do negócio. Após o negócio ou a entrada do mercado sobre ele ser implementada, estas ordens passam para o histórico de ordens que é armazenado no servidor de comércio. Esta situação gera incerteza. Suponha que a EA colocou uma ordem, a qual foi executada. A posição agregada mudou. Depois de algum tempo, a EA precisa fechar sua posição.

Fechar a ordem específica, tal como poderia ser feito no MetaTrader 4, não pode ser feita, porque há uma falta do próprio conceito de encerramento de encomendas, que pode fechar a posição ou uma parte dela. A questão é, o que parte da posição deve ser fechada. Alternativamente, podemos olhar através de todas as ordens históricas, selecionar aquelas que foram colocados pela EA, e depois correlacionar essas ordens com a atual situação do mercado e, se necessário, bloquear as suas ordens contrárias. Este método contém muitas dificuldades.

Por exemplo, como podemos determinar se as ordens já não foram bloqueadas no passado? Podemos tomar um outro caminho, supondo que a posição atual pertence exclusivamente à EA atual. Esta opção só pode ser usada se você pretende comercializar com uma EA, negociando em uma estratégia. Todos estes métodos não são capazes de resolver elegantemente os desafios que enfrentamos.

A solução mais óbvia e simples seria armazenar todas as informações necessárias sobre as ordens do modelo atual (ou seja, as ordens que não são bloqueadas por negócios opostos), dentro do próprio modelo.

Por exemplo, se o modelo colocar uma ordem, o seu bilhete é registrado em uma área especial da memória deste modelo, por exemplo, pode ser organizado com a ajuda de um sistema já familiar para nós de listas vinculadas.

Conhecendo a ordem do bilhete, você pode encontrar praticamente qualquer informação sobre ele, então tudo o que precisamos é ligar a ordem do bilhete com o modelo que o publica. Deixe a ordem de bilhetes ser armazenada na classe especial CTableOrder. Além do bilhete, pode ser acomodada a informação mais importante, por exemplo, o volume das ordens, o momento da sua instalação, o número mágico, etc.

Vamos ver como esta classe está estruturada:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

Semelhante à classe CModel , a classe CTableOrders é herdada de CObject. Assim como as classes dos modelos, vamos colocar instâncias de CTableOrders na ListTableOrders lista do tipo CList.

Além de sua própria ordem do bilhete (m_tiket), a classe contém informações sobre o número mágico (ORDER_MAGIC) da EA que o publica, seu tipo, abertura preço, volume, e o nível de sobreposição estimados de ordens: paradaperda (m_sl) e entradalucro (m_tp). Nos dois últimos valores, precisamos falar separadamente. é evidente que qualquer negócio deve, mais cedo ou mais tarde, ser fechado por um negócio oposto. O negócio oposto pode ser iniciado com base na atual situação no mercado ou o encerramento parcial da posição a um preço pré-determinado, no momento da sua conclusão.

Em MetaTrader4, tais "saídas incondicionais da posição" são tipos especiais de saída: ParadaPerda e EntradaLucro. A característica distintiva do MetaTrader 4 é o fato de que esses níveis se aplicam a uma ordem específica. Por exemplo, caso ocorra uma parada numa das ordens ativas, as outras ordens abertas neste instrumento não serão afetadas.

Em MetaTrader 5, isto é um pouco diferente. Embora para cada uma das ordens estabelecidas, entre outras coisas, você pode especificar um preço de ParadaPerda e RetiradaLucro, estes níveis não vão agir contra uma ordem específica em que esses preços foram estabelecidos, mas em relação a toda a posição sobre este instrumento.

Suponha que haja uma posição aberta para COMPRAEURUSD de 1 lote padrão, sem os níveis de ParadaPerda e RetiradaLucro. Algum tempo depois, uma outra ordem é publicada EURUSD para comprar 0,1 lote, com os níveis estabelecidos de ParadaPerda e RetiradaLucro - cada um a uma distância de 100 pontos em relação ao preço atual. Depois de algum tempo, o preço atinge o nível de ParadaPerdaou o nível de RetiradaLucro. Quando isto ocorre, a posição inteira com um tamanho de lote de 1,1 no EURUSDserá fechada.

Em outras palavras, a ParadaPerda e RetiradaLucro só podem ser definidas em relação à posição agregada, e não contra uma ordem particular. Com base nisso, torna-se impossível usar essas ordens no sistema multi EAs. Isso é óbvio, porque se um sistema vai publicar seus próprios ParadaPerda e RetiradaLucro, isso se aplicará a todos os outros sistemas, os interesses dos quais já estão incluídos na posição agregada do instrumento!

Consequentemente, cada um dos subsistemas comerciais EA deve usar somente sua própria interna ParadaPerda e RetiradaLucro para cada pedido individualmente. Além disso, este conceito pode ser derivado do fato de que até dentro do mesmo sistema comercial, diferentes ordens podem ter diferentes níveis de ParadaPerda e RetiradaLucro, e como já foi mencionado acima, em MetaTrader 5, estes resultados não podem ser designados para ordens individuais.

Se colocarmos dentro das ordens virtuais, os níveis sintéticos de ParadaPerda e RetiradaLucro, a EA será capaz de bloquear de forma independente os pedidos existentes uma vez que o preço atinja ou exceda estes níveis. Depois de bloquear essas ordens, elas podem ser facilmente removidas da lista de pedidos ativos. A forma como isso é feito é descrita abaixo.

A classe CTableOrders, além de seus próprios dados, contém uma função Add() muito importante. Esta função recebe o bilhete de ordem, que deve ser gravado na tabela. Além do bilhete de ordem, esta função recebe os níveis do ParadaPerda e RetiradaLucrovirtual. Primeiro, a () funçãoSoma tenta alocar a ordem entre as ordens históricas, que são armazenados no servidor. Se ele é capaz de fazer isso, ele insere a informação sobre o bilhete na instância da classe histórico_ordem_informação, e em seguida começa a introduzir a informação através dele para o novo elemento TableOrders. Além disso, este elemento é adicionado à lista de ordens. Se a seleção da ordem não pode ser concluída, então, talvez estejamos lidando com uma ordem pendente, por isso temos que tentar alocar esta ordem a partir das ordens atuais, através da OrderSelect() função. No caso de uma seleção bem sucedida desta ordem, as mesmas medidas devem ser tomadas para a ordem histórica.

Neste momento, antes da introdução da estrutura, a qual descreve o evento Comércio, trabalhar com pedidos pendentes para vários sistemas EAs é difícil. Certamente que, após a introdução desta estrutura, torna-se possível criar EAs, de acordo com as ordens pendentes. Além disso, se uma tabela de ordens está presente, virtualmente qualquer estratégia comercial com ordens pendentes pode ser movida para o desempenho no mercado. Por estas razões, todos os modelos comerciais apresentados no artigo terão execução de mercado (ORDEM_TIPO_COMPRA ou ORDEM_TIPO_VENDA).


CModel - a classe base do modelo de comércio

E assim, quando a tabela de ordens é totalmente projetada, vem o momento de descrever a versão completa do modelo básico CModel:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};
        

Os dados desta classe contêm as constantes fundamentais de qualquer modelo comercial.

Este é o número mágico (m_magic), o símbolo em que o modelo será lançado, (m_symbol) o prazo (m_timeframe), e o nome do modelo mais comercializado (m_nome).

Além disso, o modelo inclui a, já conhecida para nós, classe da tabela de pedidos, (CTableOrders * tabela) e a lista, na qual as instâncias dessa tabela serão mantidas, uma cópia para cada ordem (CList*ListTableOrders). Uma vez que todos os dados serão criados dinamicamente, conforme a necessidade, o trabalho com esses dados será realizada através de indicadores.

Isto é seguido pela variável m_delta. Esta variável deve manter um coeficiente especial para o cálculo do lote atual nas fórmulas para administrar o dinheiro. Por exemplo, para a fórmula fracionada fixa de capitalização, esta variável pode armazenar uma parte da conta que pode ser posta em risco. Por exemplo,para o risco de 2% da conta, esta variável deve ser igual a 0.02. Para os métodos mais agressivos, por exemplo, para o método ideal, festa variável poderia ser maior.

O que é importante nesta variável é que ela permite a seleção individual do risco para cada modelo, o qual faz parte de uma única EA. Se a fórmula de capitalização não é usada, então seu preenchimento não é obrigatório.Por padrão, ele é igual a 0.0.

Em seguida vem a inclusão de todas as classes de comércio auxiliares, que são projetadas para facilitar o recebimento e processamento de todas as informações necessárias, que vão desde informações de conta e informações sobre a posição. Entende-se que os derivados de modelos comerciais específicos precisam usar ativamente estas classes auxiliares, e não as características normais do tipo OrderSelect ou OrderSend.

A variável m_timing precisa ser descrita separadamente. Durante o processo de trabalho da AE, é necessário chamar certos eventos em determinados intervalos de tempo. A função OnTimer() não é adequada para isso, uma vez que diferentes modelos podem existir em diferentes intervalos de tempo.

Por exemplo, alguns eventos necessitam ser chamados em cada nova barra. Para o modelo comercializar em um gráfico de hora em hora, tais eventos devem ser chamados a cada hora, para um modelo comercializar em um gráfico diário - cada nova barra diária. é evidente que estes modelos têm configurações diferentes de tempo e cada um deve ser armazenado, respectivamente, no seu próprio modelo. A estrutura t_período, inclusa na classe CModel, permite que você armazene estas configurações separadamente, cada uma em seu modelo.

Aqui está com o que a estrutura se parece:

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

Como pode ser visto, ela inclui a habitual listagem de prazos. Para ver se ocorreu uma nova barra, é preciso comparar o tempo da última barra com o tempo que foi gravado na estrutura t_período. Se os tempos não são iguais, então, a ocorrência de um nova barra e o tempo na estrutura precisam ser atualizados para os momentos da barra atual, e retornar um resultado positivo (verdadeiro). Se o tempo da última barra e a estrutura são idênticos, isto significa que a nova barra ainda não ocorreu, e um resultado negativo deve ser devolvido (falso).

Aqui está uma função que funciona com base no algoritmo descrito:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

Neste momento não há nenhuma possibilidade de uma ordenação sequencial das estruturas. Essa classificação pode ser necessária quando você precisa criar várias instâncias em um ciclo do mesmo modelo comercial, comercializando em prazos diferentes. Então eu tive que escrever uma função especial classificadora a respeito da estrutura t_period.

Aqui está o código fonte desta função:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

Todas essas funções estão convenientemente combinadas em um único arquivo na pasta \\Include. Vamos nomeá-la Time.mqh.

Isto é o que será incluso em nossa classe base CModel:

#incude <Time.mqh>

Além de funções simples obter/pôr do tipo Nome(), Prazo() e Símbolo(), a classe CModel contém funções complexas do tipo Init(), GetMyPosition(), Apagar(), CloseAllPosition() и Processamento(). A designação da última função já deve ser familiar para você. Vamos discutir em maior detalhe a sua estrutura interna mais tarde, mas por agora vamos começar com uma descrição das principais funções da classe base. CModel.

A função CModel::Adicionar() cria dinamicamente uma instância de classe CTableOrders, e em seguida, a preenche usando a função apropriada CTabeOrders::Adicionar() . O seu princípio de funcionamento foi descrito acima. Após ser preenchido, este item é incluso na lista geral de todos os pedidos do modelo atual (ListTableOrders.Add (t)).

A funçãoCModel::Delete() , por outro lado, excluiu o elemento do tipo CTableOrders a partir da lista de ordens ativas. Para fazer isso você deve especificar o bilhete das ordens que deve ser eliminado. Os princípios de seus trabalhos são simples. A função sequencialmente classifica através de toda a tabela de pedidos em busca da ordem com o bilhete certo. Se ela encontra tal ordem, ela a exclui.

A função CModel::GetNumberOrders() conta o número de ordens ativas. Ela preenche a estrutura especial n_orders:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

Como pode ser visto, depois que ela é chamada, podemos descobrir quantos tipos específicos de ordens foram definidas. Por exemplo, para obter o número de todas as ordens curtas, você deve ler todos os valores de ordens_curtasda instância n_ordens.

A CModel::SendOrder() função é a função básica e única para o envio de ordens fatual para o servidor comercial. Ao invés de cada modelo particular ter o seu próprio algoritmo para o envio de ordens para o servidor, a função SendOrder() define o procedimento geral para estas submissões. Independentemente do modelo, o processo de colocação de ordens é associado com os mesmos controles, que são eficientemente realizadas em um local centralizado.

Vamos nos familiarizar com a fonte código desta função:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!!
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

A primeira coisa que essa função faz é verificar a possibilidade de executar o volume declarado do servidor de comércio. Isto é feito usando a função CheckLot() . Podem existir algumas restrições comerciais sobre o valor da posição. Elas precisam ser levadas em consideração.

Considere o seguinte caso: há um limite de tamanho das posições de comércio de 15 lotes padrões em ambos os sentidos. A posição atual é longa e equivale a 3 lotes. O modelo de comércio, baseado no seu sistema de gestão de dinheiro, quer abrir uma posição longa com um volume de 18.6 lotes.A funçãoCheckLot() retornará o volume corrigido do negócio. Neste caso, será igual a 12 lotes (uma vez que de 15 lotes, 3 já estão ocupados por outros negócios). Se a posição atual aberta foi curta ao invés de longa, então a função teria que retornar 15 lotes ao invés de 18.6. Este é o volume máximo possível de posições.

Depois de publicar 15 lotes de compra, a posição líquida, neste caso, será de 12 lotes (3 - venda, 15 - compra). Quando um outro modelo substitui sua posição curta inicial de 3 lotes, comprar a posição agregada vai se tornar o máximo possível de - 15 lotes. Outros sinais de compra não serão processados ​até que o modelo substitua alguns ou todos os seus 15 lotes de compra. Um volume possível para o negócio solicitado foi superado, a função retorna uma constante EMPTY_VALUE, este sinal deve ser passado.

Se a verificação da possibilidade do volume do conjunto ter sido bem sucedida, então cálculos são feitos com o valor da margem desejada. Pode não haver fundos suficientes na conta para o volume indicado. Para estes fins, existe uma função CheckMargin() . Se a margem não é suficiente, ele vai tentar corrigir o volume de ordens para que a margem livre atual permita que ele abra. Se a margem não é suficiente até mesmo para abrir a quantidade mínima, estamos em um estado de Chamada de-Margem.

Se atualmente não há posições, e a margem não é usada, isso significa apenas uma coisa - chamada de margem-técnica - um estado onde é impossível abrir um negócio. Sem adição de dinheiro para a conta, somos incapazes de continuar. Se alguma margem ainda está em uso, então ficamos com mais nada a não ser esperar até que a transação que usa esta margem seja fechada. Em qualquer caso, a falta de margem retornará uma constante EMPTY_VALUE.

Uma característica distintiva desta função é a capacidade de dividir a ordem atual em vários negócios independentes. Se os modelos comerciais utilizam um sistema de capitalização da conta, então, a quantidade exigida pode facilmente ultrapassar todos os limites concebíveis (por exemplo, o sistema de capitalização poderá exigir a abertura de um negócio com um volume de centenas e, às vezes, milhares de lotes padrões). é evidente que é impossível garantir tal quantidade para um único negócio. Tipicamente, as condições comerciais determinam o tamanho de um negócio de no máximo cem lotes, mas alguns servidores comerciais têm outras restrições, como por exemplo, no servidor MetaQuotes Campeonato 2010 essa limitação era de 5 lotes. é claro que tais restrições devem ser tomadas em conta, e com base nisso, calcula-se corretamente o volume real do negócio.

Primeiro, o número de pedidos necessário para implementar o volume do conjunto é contado. Se o valor definido não excede o valor máximo da transação, ele requer apenas um passo para colocar este ordem. Se o volume desejado das transações é superior ao volume máximo possível, então este volume é dividido em várias partes. Por exemplo, você quer comprar 11.3 lotes de EURUSD. O tamanho máximo da transação sobre este instrumento é 5.0 lotes. Então a função OrderSend quebra este volume em três ordens: uma ordem de - 5.0 lotes, segunda ordem - 5.0 lotes, terceira ordem - 1.3 lotes.

Assim, ao invés de uma ordem, vai haver tantos quanto três. Cada um deles será listado na tabela de encomendas, e terá suas próprias configurações independentes, tais como os valores virtuais de Parada de Perda e Retirada de Lucro, o número mágico e outros parâmetros. No processamento de tais ordens não deve haver nenhuma dificuldade, já que os modelos de comércio são projetados de tal forma que eles podem lidar com qualquer número de pedidos em suas listas.

Com efeito, todas as ordens têm os mesmos valores RetiradaLucroe ParadaPerda. Cada um deles será sequencialmente ordenado pelas funções LongClose e ShortClose . Uma vez que as condições adequadas para o seu encerramento ocorram, ou que eles atinjam seus limites SL e TP, todos eles serão fechados.

Cada ordem é enviada para o servidor usando a OrderSend função da classe CTrade . O detalhe mais interessante do trabalho está oculto abaixo.

O fato é que a tarefa de uma ordem pode ser dupla. A ordem de compra ou venda pode ser enviada após a ocorrência do sinal ou pode ser um pedido para bloquear a que existiu anteriormente. A função OrderSend deve saber o tipo de ordem enviada, uma vez que esta é a função que realmente coloca todas as ordens na tabela de ordens, ou as remove da tabela após a ocorrência de certos eventos.

Se o tipo de ordem que você deseja adicionar é ADD_ORDER. Ou seja, é uma ordem independente, a qual deve ser colocada na tabela de ordens, então, a função adiciona informações sobre esta ordem na tabela de ordens. Se a ordem é apagar para substituir a ordem previamente colocada (por exemplo, na ocorrência de uma virtual parada-perda), então deve ter um tipo de DELETE_ORDER. Depois de ter sido colocada, a função OrderSend remove manualmente informações sobre a ordem, com a qual ela está vinculada a partir da lista de ordens. Para esta função, além do tipo de ordem, herda um bilhete de ordem com o qual ele está vinculado. Se isso é ADD_ORDER, então o bilhete pode ser preenchido com um zero simples.


O primeiro modelo comercial, com base no cruzamento de médias móveis

Discutimos todos os principais elementos da classe baseCModel . é hora de considerar uma classe de comércio específica.

Para estes fins, vamos primeiro criar um modelo comercial simples, com base em um indicador simples MACD.

Este modelo terá sempre a posição longa ou curta. Assim que a linha rápida cruza a linha lenta abaixo, vamos abrir uma posição curta, enquanto que uma posição longa, se houver, será fechada. No caso de cruzamento ascendente, vamos abrir uma posição longa, enquanto que uma posição curta, se houver, será fechada. Neste modelo, não usamos paradas de proteção e níveis de lucro.

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}
        

A classe base CModel não impõe quaisquer restrições sobre o conteúdo interno de seus descendentes. A única coisa que se torna obrigatória, é o uso da função de interface Processing() . Todos os problemas da organização interna desta função são delegadas a uma classe específica de modelos. Não existe nenhum algoritmo universal que poderia ser colocado dentro de uma função de Processamento(), portanto, não há razão para impor aos descendentes seu método de como um determinado modelo deve ser providenciado. No entanto, a estrutura interna de virtualmente qualquer modelo pode ser padronizada. Tal padronização vai facilitar muito a compreensão de um exteriore até mesmo seu código, e tornará o modelo mais "estereotipado".

Cada modelo deve ter seu próprio inicializador. O inicializador do modelo é responsável por carregar os parâmetros corretos, que são necessários para a sua operação, por exemplo, para que o nosso modelo funcione, precisamos selecionar os valores do indicador MACD, obter um identificador do buffer correspondente e, além disso, é claro,determinar o instrumento de comércio e prazo para o modelo. Tudo isso deve ser feito pelo inicializador.

Os inicializadores de modelos - são métodos sobrecarregados simples de suas classes. Estes métodos têm um nome comum Init. O fato é que o MQL5 não suporta os construtores sobrecarregados, então não há nenhuma maneira de criar o inicializador do modelo em seu construtor, porque a sobrecarga será necessária para osparâmetros de entrada. Embora ninguém nos restrinja de indicar no construtor do modelo os seus parâmetros básicos.

Cada modelo deve ter três inicializadores. Primeiro - o inicializador padrão. Deve configurar e carregar o modelo por padrão, sem solicitar os parâmetros. Isto pode ser muito conveniente para teste no modo "tal como". Por exemplo, para o nosso modelo, o inicializador padrão como uma ferramenta instrumentoe prazo do modelo, selecionará o gráfico atual e o prazo atual.

As configurações do indicador MACD também serão padrão: veloz EMA = 12, devagar EMA = 26, sinal MA = 9; se o modelo deve ser configurado de uma certa maneira, tal inicializador já não será adequado. Vamos precisar de inicializadores com parâmetros. é conveniente (mas não necessário) fazer dois tipos. O primeiro vai receber os seus parâmetros em uma função clássica: Init (tipo param1, tipo param2, ..., tipo paramN). O segundo descobrirá os parâmetros do modelo usando uma estrutura especial, o que protege estes parâmetros. Esta opção é por vezes preferível, pois, às vezes, o número de parâmetros pode ser grande, caso em que seria conveniente para passá-los através das estruturas.

Cada modelo tem a estrutura dos seus parâmetros. Esta estrutura pode ter qualquer nome, mas é preferível chamá-la pelo padrão modelname_param. Configurando o modelo - é um passo muito importante na utilização das oportunidades de comércio multiprazo/multissistema/multimoeda. é nesta fase que é determinado como e em que instrumento este modelo será aproveitado.

Nosso modelo de comércio tem apenas quatro funções comerciais. A função para abrir uma posição longa: LongOpen, a função para abrir uma posição curta : ShortOpen, a função para fechar uma posição longa : LongClosed, e a função para fechar uma posição curta : ShortClosed. O trabalho das funções LongOpen и ShortOpen é trivial. Ambas recebem o valor do indicador da barra anterior MACD, que é comparado com o valor das duas barras anteriores a essa. Para evitar o "redesenho", a a barra de corrente (zero) não será usada.

Se houver um cruzamento descendente, então a função ShortOpencalcula utilizando as funções inclusas no arquivo de cabeçalho mm.mqh, após a qual o lote necessário envia o seu comando para a função OrderSend . O LongClose neste momento, ao contrário, fecha todas as posições longas no modelo. Isso ocorre porque a função sequencialmente classifica todas as atuais ordens abertas na tabela de ordens do modelo. Se uma ordem longa for encontrada, então a função a fecha com uma contra-ordem. A mesma coisa embora na direção contrária, é feita pela função ShortClose() . O trabalho destas funções pode ser encontrado na listagem fornecida acima.

Vamos analisar com mais detalhes como o lote atual é calculado no modelo de comércio.

Como mencionado acima, para estes fins nós usamos funções especiais para a capitalização da conta. Além das fórmulas de capitalizações, estas funções incluem a verificação dos cálculos do lote, com base no nível da margem utilizada e pela restrição do tamanho das posições comerciais.Podem existir algumas restrições comerciais sobre o tamanho da posição. Elas precisam ser levadas em consideração.

Considere o seguinte caso: há um limite de tamanho das posições de comércio de 15 lotes padrões em ambos os sentidos. A posição atual é longa e equivale a 3 lotes. O modelo comercial, com base em seu sistema de gestão de capital, quer abrir uma posição longa com um volume de 18.6 lotes. A função CheckLot() irá retornar o valor corrigido do volume de ordens. Neste caso, será igual a 12 lotes (uma vez que de 15 lotes, 3 já estão ocupados por outros negócios). Se a atual posição aberta foi curta, ao invés de longa, então a função teria que retornar 15 lotes ao invés de 18.6. Este é o volume máximo possível de posições.

Depois de excluir 15 lotes de compra, a posição líquida, neste caso, será de 12 lotes (3 - venda, 15 - compra). Quando um outro modelo substitui sua posição curta inicial de 3 lotes, comprar a posição agregada vai se tornar o máximo possível de - 15 lotes. Outros sinais de compra não serão processados ​até que omodelo substitua alguns ou todos os seus 15 lotes de compra. Se o volume disponível para a transação solicitadaestá esgotado, então a função irá retornar uma EMPTY_VALUE constante. Este sinal deve ser passado.

Se a verificação da possibilidade do volume do conjunto ter sido bem sucedida, então cálculos são feitos com o valor da margem desejada. Fundos da conta podem ser insuficientes para o volume colocado. Para estes fins, existe uma função CheckMargin(). Se a margem não é suficiente, ele vai tentar corrigir o valor declarado da transação de modo que a margem livre atual permita que ele abra. Se a margem não é suficiente até mesmo para abrir o volume mínimo, estamos em um estado de Chamada de-Margem.

Se atualmente não há posições, e a margem não é usada, isso significa apenas uma coisa - chamada de margem-técnica - um estado onde é impossível abrir um negócio. Sem adição de dinheiro para a conta, somos incapazes de continuar. Se alguma margem ainda é usada, então não temos escolha a não ser esperar até o fechamento da posição que usa esta margem. Em qualquer caso, a falta de margem irá retornar uma constante EMPTY_VALUE.

As funções para controlar o tamanho do lote e as margens geralmente não são utilizadas diretamente. Eles são chamados de funções especiais para o gerenciamento de capital. Estas funções implementam as fórmulas para a capitalização das contas. O arquivo mm.mqh inclui apenas duas funções básicas do gerenciamento de capital, uma é calculada com base numa fração fixa da conta e a outra com base no método proposto por Ryan Jones, conhecido como o método de proporções fixas.

A finalidade do primeiro método é definir uma parte fixa da conta, a qual pode ser arriscada. Por exemplo, se o risco permitido é de 2% da conta, e a conta é igual a 10.000 dólares, então a quantidade máxima de risco é de $ 200. A fim de calcular qual lote deve ser usado para uma parada de 200 dólares, você precisa saber exatamente que a distância máxima pode ser alcançada pelo preço contra a posição aberta. Portanto, para calcular o lote através desta fórmula, é preciso determinar com precisão a Parada de Perda e o nível de preços na qual será feita a transação.

O método proposto por Ryan Jones é diferente do anterior. A sua essência é que capitalização é feita através da função definida por um caso particular de uma equação quadrática.

Aqui está a sua solução:

x=((1.0+MathSqrt(1+4.0*d))/2)*Step;

onde: x - o limite inferior da transição para o próximo nível d = (Lucro / delta) * 2.0 Passo- um passo do delta, como o 0.1 lotes.

Quanto menor o tamanho do delta, mais agressivamente a função tenta aumentar o número de posições.Para mais detalhes sobre como esta função é construída, você pode consultar o livro de Ryan Jones: The Trading Game: Playing by the Numbers to Make Millions.

Se as funções de gerenciamento de capital não são planejadas para serem usadas, é necessário chamar diretamente as funções de controle do lote e margem.

Então, nós revimos todos os elementos do nosso EA básico. é hora de colher os frutos do nosso trabalho.

Para começar, vamos criar quatro modelos. Deixe um modelo de comércio pelos EURUSD parâmetros padrões, o segundo também vai comercializar com EURUSD, mas em um prazo de 15 minutos. O terceiro modelo será lançado no gráfico GBPUSDcom parâmetros padrões. O quarto - em USDCHF num gráfico de duas horas, com os seguintes parâmetros: LentoEMA= 6 VelozEMA = 12 SinalEMA = 9. O período para testes - H1, o modo de teste - todos os ticks, o tempo a partir de 01.01.2010 até 01.09.2010.

Mas, antes de executar este modelo em quatro modos diferentes, primeiramente vamos tentar testá-lo para cada instrumento e espaço de tempo separadamente.

Aqui está a tabela na qual os principais indicadores de testes são feitos:

Sistema
Número de Negócios
Lucro, $
MACD(9,12,26)H1 EURUSD
123
1092
MACD (9,12,26) EURUSD M15
598
-696
MACD(9,6,12) USDCHF H2
153
-1150
MACD(9,12,26) GBPUSD H1
139
-282
Todos sistemas
1013
-1032

A tabela mostra que o número total de negócios para todos os modelos deve ser 1013, e o lucro total deve ser $ -1032.

Consequentemente, estes são os mesmos valores que se deve obter se testarmos estes sistemas ao mesmo tempo. Os resultados não devem diferir, embora ainda ocorram alguns desvios.

Então aqui está a prova final:


Como pode ser visto, há apenas um negócio a menos, e os lucros diferem apenas por US $ 10, o que corresponde a apenas 10 pontos de diferença com um lote de 0.1. Deve notar-se que os resultados dos testes combinados, no caso de uso do sistema de gerenciamento de dinheiro, será radicalmente diferente dovalor dos resultados dos testes de cada modelo individualmente. Isto é porque a dinâmica do equilíbrio influencia cada um dos sistemas, de modo que os valores calculados dos lotes irão variar.

Apesar do fato de que por conta própria os resultados não apresentam um interesse, nós criamos uma estrutura complicada mas muito flexível e gerenciável da EA. Vamos, brevemente, mais uma vez considerar sua estrutura.

Para isso, voltaremos para o esquema mostrado abaixo:

Referência da classe

O esquema mostra a estrutura básica de exemplo do modelo.

Uma vez que uma instância da classe do modelo de comércio é criada, chamamos a função Init () sobrecarregada. Ela inicializa os parâmetros necessários, prepara os dados, carrega os identificadores de Indicadores, se eles estão sendo usados. Tudo isso acontece durante a inicialização da EA, ou seja, dentro da função OnInit(). Observe que os dados incluem as instâncias das classes de base destinadas a facilitar o comércio. Supõe-se que os modelos comerciais precisam usar ativamente essas classes em vez das funções normais de MQL5. Depois que a classe modelo é criada e inicializada com êxito, ela cai na lista de modelos CList. Além disso, a comunicação com ela é realizada através de um adaptador universal CObject.

Após a ocorrência dos eventos OnTrade() ou OnTick(), existe uma triagem sequencial de todas as instâncias dos modelos na lista. A comunicação com eles é realizada chamando a função de processamento (). Além disso, ele chama as funções comerciais do seu próprio modelo (o grupo de funções azuis). Sua lista e nomes não são estritamente definidos, mas é conveniente usar a nomenclatura padrão, assim como LongOpened(), ShortClosed(), etc. Estas funções, com base em sua lógica embutida, escolhem o tempo para concluir o negócio e, em seguida, enviam um pedido especialmente formado para a abertura ou fechamento do negócio da função SendOrder ().

Este último faz as verificações necessárias e, em seguida, envia as ordens para o mercado. As funções de negociação dependem das funções auxiliares do modelo (grupo verde), o que por sua vez, utiliza ativamente as classes auxiliares básicas (grupo roxo). Todas as classes auxiliares são representadas como instâncias de classes na seção de dados (grupo rosa). A interação entre os grupos é indicada por setas azul-escuro.


O modelo comercial, com base no indicador de Bandas de Bollinger

Agora que a estrutura geral dos dados e métodos torna-se clara, vamos criar um outro modelo comercial, com base nos indicadores de tendência de Bandas de Bollinger. Como base para este modelo comercial, foi utilizado uma EA simples Um Consultor Especialista, com base em Bandas de Bollinger por Andrei Kornishkin. Bandas de Bollinger - são níveis iguais a um determinado tamanho de desvio padrão da média móvel simples. Mais detalhes sobre como este indicador é construído pode ser encontrado na seção de Ajuda para análise técnica, anexa ao terminal MetaTrader 5.

A essência da ideia de comércio é simples: o preço tem a propriedade de retorno, ou seja, se o preço chegou a um certo nível, então provavelmente, ele vai retornar para a direção oposta. Esta tese é comprovada por um teste em uma distribuição normal de qualquer instrumento de negociação real: a curva de distribuição normal será ligeiramente alongada. As bandas de Bollinger determinam os auges mais prováveis de níveis de preços. Uma vez que eles são atingidos (bandas de Bollinger superior ou inferior), o preço tende a voltar no sentido oposto.

Nós simplificamos um pouco a tática comercial e o indicador auxiliar não vai ser usado - (Dobrar a Média Móvel Exponencial ou DEMA). Mas vamos usar paradas rigorosas de proteção - Parada de Perda virtual. Eles farão o processo comercial mais estável e, ao mesmo tempo, nos ajudarão a entender um exemplo em que cada modelo comercial usa seu próprio nível independente de paradas de proteção.

Para o nível de paradas de proteção, usamos o preço atual mais ou menos o valor do indicador de volatilidade ATR. Por exemplo, se o valor atual da ATR é igual a 68 pontos e há um sinal para vender a um preço de 1.25720, então o Parada de Perda virtual para este negócio será igual a 1.25720 + 0.0068 = 1.26400. Da mesma forma, mas no sentido oposto, isto é feito para a compra: 1.25720 - 0.0068 = 1.25040.

O código-fonte deste modelo é fornecido abaixo:

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}
        

Como pode ser visto, o código do modelo comercial é muito semelhante ao código-fonte das táticas comerciais anteriores. A principal variação aqui é o surgimento de ordens de parada virtuais e a função cmodel_bollinger: CloseByStopSignal(), servindo essas paradas de proteção.

De fato, no caso da utilização de paradas de proteção, os seus valores devem ser simplesmente transmitidos para a função SendOrder (). E esta função irá introduzir estes níveis na tabela de ordens. Quando os preços cruzam ou alcançam estes níveis, a função CloseByStopSignal () fechará o negócio com uma contra-ordem e o removerá da lista de ordens ativas.


A combinação de modelos comerciais, instrumentos e prazos em uma única entidade

Agora que temos dois modelos de comércio, é hora de testá-los simultaneamente. Antes de fazer uma apresentação dos modelos, seria útil determinar seus parâmetros mais eficazes. Para fazer isso, nós temos que fazer a otimização de cada modelo individualmente.

A otimização irá demonstrar em que mercado e prazo o modelo é mais eficaz. Para cada modelo, serão selecionados dois dos melhores prazos e três dos melhores instrumentos. Como resultado, temos doze soluções independentes (2 modelos * 3 * 2 instrumentos de prazos), que serão testados todos juntos. Claro que o método de otimização selecionado sofre o chamado "ajuste" dos resultados, mas para os nossos propósitos isso não é importante.

Os gráficos abaixo mostram os melhores resultados da amostra:

1.1 MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . tMACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

2.4 Bollinger EURUSD H4


Bollinger EURUSD H4

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

Agora que os melhores resultados são conhecidos, ficamos com apenas um pouco mais para fazer - reunir os resultados em uma única entidade.

Abaixo está o código-fonte da função carregador, que cria os doze modelos comerciais mostrados acima, após o qual a EA sempre começa a negociar

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2.25; k_ATR=3.0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_M15,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}
        

Agora vamos testar todos os doze modelos simultaneamente:


Capitalização dos resultados

O gráfico resultante é impressionante. No entanto, o que é importante não é o resultado, mas o fato de que todos os modelos estão negociando ao mesmo tempo, todos usando suas paradas de proteção individual, e todos independentes uns dos outros. Agora vamos capitalizar o gráfico resultante. Para isso, vamos usar as funções padrões de capitalização: método proporcional fixo e o método proposto por Ryan Jones.

O assim chamado f ideal um caso especial do método proporcional fixo. A essência desse método é que para cada negócio é dado um limite de perdas, equivalente a uma percentagem da conta. Estratégias de capitalização conservadoras geralmente aplicam um limite de perda de 2%. Ou seja, em US $ 10.000, o tamanho da posição é calculado de modo que a perda, após a Parada de Perda for aprovada, não exceda US $ 200. Existe entretanto, uma certa função de crescimento do saldo final a partir do aumento do risco. Esta função tem um sino como forma. Ou seja, em primeiro lugar, com o aumento do risco, há um aumento do lucro total. No entanto, existe um limiar de risco para cada transação, após a qual o balanço total de lucros começa a diminuir. Esse limite é o assim chamado f ideal.

Este artigo não é dedicado à questão da gestão do capital. Tudo o que precisamos saber para utilizar o método proporcional fixo é o nível de paradas de proteção, e o percentual da conta que podemos arriscar. A fórmula Ryan Jones é construída de maneira diferente. Para o seu trabalho, utilizar paradas fixas de proteção não é necessário. Uma vez que o primeiro dos modelos propostos (MACD modelo) é bastante primitivo e não tem paradas de proteção, vamos usar este método para a capitalização deste modelo. Para o modelo baseado em bandas de Bollinger, vamos usar o método proporcional fixo de proporções.

Para começar a usar a fórmula de capitalização, é preciso preencher a variável m_delta, incluída no modelo básico.

Quando se utiliza a fórmula de proporções fixas, ela deve ser igual à percentagem de risco para cada transação. O uso do método de Ryan Jones é igual ao chamado incremento delta, ou seja, com a quantidade de dinheiro que precisa ser obtida para chegar a um nível mais alto do volume de posição.

Abaixo está o gráfico de capitalização:


Como pode ser observado, todos os modelos têm as suas próprias fórmulas de capitalização (o método proporcional fixo ou o método de Ryan Jones).

No exemplo dado, foram utilizados os mesmos valores de máximo risco e delta para todos os modelos. No entanto, para cada um deles, podemos selecionar os parâmetros individuais de capitalização. O ajuste fino de cada um dos modelos não está incluso no leque de questões abrangidas pelo presente artigo.


Trabalhando com ordens pendentes

Os modelos comerciais apresentados não usam os chamados pedidos pendentes. Estas são ordens que são executadas somente quando certos termos e condições estão presentes.

Na realidade, qualquer estratégia de negociação utilizando as ordens pendentes pode ser ajustada para a utilização de ordens no mercado. Pelo contrário, as ordens pendentes são necessárias para uma operação do sistema mais confiável, pois, quando há repartição do robô, ou o equipamento em que ele funciona, as ordens pendentes ainda executam paradas de proteção, ou vice-versa, entrando no mercado com base nos preços previamente determinados.

O modelo comercial proposto permite que você trabalhe com ordens pendentes de comércio, embora o processo de controle de sua apresentação, neste caso, seja muito mais complicado. Para trabalhar com essas ordens, usamos o método sobrecarregado Adicionar (COrderInfo & order_info, double stop_loss, double take_profit) de classe CTableOrders. Neste caso, a variável m_type desta classe conterá o tipo apropriado do pedido pendente, por exemplo ORDER_TYPE_BUY_STOP ou ORDER_TYPE_SELL_LIMIT.

Mais tarde, quando a ordem pendente for excluída, você precisa "pegar" o momento em que ela foi acionada para o trabalho, ou seu prazo de relevância irá expirar. Isto não é assim tão simples, já que não é o suficiente simplesmente controlar o Comércio do evento, mas também é necessário saber o que o desencadeou.

A linguagem da MQL5 está evoluindo e, atualmente, a questão da inclusão de uma estrutura de atendimento especial para ela, que explicaria o evento Trade, está sendo resolvida. Mas por enquanto, temos que ver a lista de ordens no histórico. Se uma ordem, com o mesmo bilhete de ordem pendente na tabela de encomendas é encontrado no histórico, então, alguns acontecimentos que ocorreram precisam ser refletidos na tabela.

O código do modelo básico inclui um método CModel especial :: ReplaceDelayedOrders. Este método funciona com o seguinte algoritmo. Em primeiro lugar, todas ordens ativas na tabela de ordens são verificadas. O bilhete de uma dessas ordens é comparado com o bilhete das ordens no histórico (HistoryOrderGetTicket()). Se a passagem da ordem no histórico coincide com a ordem pendente na tabela de ordens, mas o estado da ordem é executado (ORDER_STATE_PARTIAL ouORDER_STATE_FILLED), então o estado das ordens pendentes na tabela de ordens também é alterado para executado.

Além disso, se esta ordem não está relacionada com quaisquer ordens pendentes, imitando o trabalho de Parada de Perda e Retirada de Lucro, tais ordens são excluídas e seus bilhetes são inseridos nos valores da tabela apropriada (TicketSL, TicketTP). E as ordens pendentes, imitando a parada de proteção e níveis de lucro, são publicadas pelo preço previamente especificado com a ajuda de variáveis ​m_sl e m_tp. Ou seja, estes preços deve ser conhecidos no momento de chamar o método ReplaceDelayedOrders.

é importante ressaltar que o método é adaptado para lidar com a situação quando uma ordem é publicada no mercado, em que as ordens pendentes necessárias são do tipo Parada de Perda e Retirada de Lucro.

Em geral, o trabalho do método proposto não é trivial, e requer algum conhecimento para a sua utilização:

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to one of the tickets 
               // Stop Loss or Take Profit, then the order was blocked, and it needs to be
               // deleted from the table of orders
               if(ticket==t.TicketSL()||ticket==t.TicketTP())
               {
                  ListTableOrders.DeleteCurrent();
               }
            }
            // If the orders, imitating the Stop Loss and Take Profit are not found in the history,
            // then perhaps they are not yet set. Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&
                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

Usando esse método, você pode facilmente criar um modelo com base em ordens pendentes.


Conclusão

Infelizmente, é impossível considerar em um único artigo todas as nuances, desafios e benefícios da abordagem proposta. Nós não consideramos a publicação serial de dados - um método, que lhe permite armazenar, registrar e recuperar todas as informações necessárias sobre o estado atual de modelos a partir dos arquivos de dados. Além da nossa consideração, permaneceram os modelos comerciais, baseados no comércio de desenvolvimentos sintéticos. Estes tópicos são muito interessantes e, certamente, tem suas próprias soluções eficazes para os conceitos propostos.

A principal coisa que conseguimos fazer - é desenvolver uma estrutura de dados totalmente dinâmica e administrável. O conceito de listas vinculadas efetivamente os gerencia, faz táticas comerciais independentes e individualmente personalizáveis. Outra vantagem muito importante desta abordagem é que ela é completamente universal.

Por exemplo, em sua base é suficiente criar duas EAs de comércio e colocá-las no mesmo instrumento. Ambas irão automaticamente trabalhar apenas com suas próprias ordens, e apenas com o seu próprio sistema de gestão de capital. Assim, existe um suporte de compatibilidade descendente. Tudo o que pode ser feito simultaneamente em uma EA, pode ser distribuído entre vários robôs. Esta propriedade é extremamente importante quando se trabalha em posições líquidas.

O modelo descrito não é apenas uma teoria. Ele inclui um aparelho avançado de funções auxiliares, as funções de gestão do capital e verificação de requisitos marginais. O sistema de envio de ordens é resistente às chamadas cotações novas e derrapagens - cujos efeitos são muitas vezes vistos no comércio real.

O sistema de comércio determina o tamanho máximo da posição e o volume máximo de negócios. Um algoritmo especial separa os pedidos comerciais independentes em vários negócios, cada um dos quais é processado separadamente. Além disso, o modelo apresentado mostrou-se bem no Campeonato de Comércio Automatizado de 2010 - todos os negócios são realizados com precisão e de acordo com o plano de negociação. Os modelos comerciais apresentados no Campeonato são gerenciados com diferentes graus de riscos e em diferentes sistemas de gestão do dinheiro.

A abordagem apresentada é quase uma solução completa para a participação no Campeonato, bem como para o funcionamento em paralelo de vários instrumentos e prazos, em várias táticas comerciais. A única dificuldade em familiarizar-se com esta abordagem reside na sua complexidade.