English Русский 中文 Español Deutsch 日本語
preview
Multibot no MetaTrader: lançando vários robôs a partir de um único gráfico

Multibot no MetaTrader: lançando vários robôs a partir de um único gráfico

MetaTrader 5Exemplos | 25 julho 2023, 09:32
521 0
Evgeniy Ilin
Evgeniy Ilin

Conteúdo


Introdução

No mundo dos mercados financeiros, os sistemas de negociação automatizados se tornaram parte integrante do processo de tomada de decisões. Esses sistemas podem ser configurados para analisar o mercado, tomar decisões de entrada e saída e executar negociações usando regras e algoritmos predefinidos. No entanto, configurar e executar robôs em vários gráficos pode ser uma tarefa demorada. Cada robô deve ser configurado individualmente para cada gráfico, o que requer esforço adicional.

Neste artigo, mostrarei minha implementação de um modelo simples que permite criar um robô universal para vários gráficos no MetaTrader 4 e 5. Nosso modelo permitirá que você anexe o robô a um único gráfico, enquanto o restante dos gráficos será processado dentro do EA. Assim, nosso modelo simplifica muito o processo de configuração e execução de robôs em vários gráficos, economizando tempo e esforço dos traders. Neste artigo, vou considerar detalhadamente o processo de criação de tal robô em MQL5, desde a ideia até o teste.


Exposição do problema e limitações de aplicação

Essa ideia me ocorreu há pouco tempo, embora eu tenha observado decisões semelhantes de vendedores profissionais há muito tempo. Em outras palavras, não sou o primeiro nem o último a ter essa ideia nesse campo, mas como sempre, algumas condições devem surgir para que o programador comece a chegar a tais decisões. A principal razão para desenvolver tais Expert Advisors na loja MQL5 é o desejo de proporcionar conforto ao usuário. No entanto, no meu caso, minha motivação foi um pouco diferente. Primeiro, precisei testar várias estratégias simultaneamente para diversos instrumentos ou a mesma estratégia, mas para ver suas características multimoedas.

Além disso, um fator muito importante ao testar uma estratégia no testador, especialmente em modo multimoedas, é a curva geral de lucratividade, que é a base de qualquer avaliação de sistemas de negociação automática ao retroceder em dados históricos. Ao testar sistemas de negociação separadamente em um único instrumento, é bastante difícil combinar esses relatórios posteriormente. Desconheço ferramentas desse tipo, pelo menos para o MetaTrader 5. Quanto à quarta versão da plataforma, existe uma ferramenta não oficial para essas manipulações. Eu a utilizei em pelo menos um artigo, mas é claro que essa abordagem não é preferível.

Além do processo de teste, há um processo igualmente importante de negociação automática em si e a sincronização de EAs semelhantes que funcionam independentemente, cada um em seu próprio gráfico. Se houver muitos gráficos desse tipo, isso poderá exigir recursos adicionais do computador e, além disso, desacelerar ou piorar o desempenho da negociação, bem como levar a erros inesperados e outros contratempos desagradáveis que podem ter um efeito muito desagradável no resultado final da negociação. Para cada um desses Expert Advisors, é necessário pensar em identificadores exclusivos para ordens, proteção contra solicitações de alta frequência ao servidor, além de muitos outros aspectos que não são óbvios à primeira vista.

Uma questão separada e muito sensível no processo de aplicação será o processamento da parte gráfica do Expert Advisor. Atualmente, todos os criadores mais ou menos habilidosos de Expert Advisors garantem pelo menos uma variante mínima de alguma indicação no gráfico ao qual o Expert Advisor está vinculado. Assim, o EA parece mais sério e inspira mais confiança e, por fim, quase sempre a exibição de algumas informações no gráfico para o usuário permite que, às vezes, você controle mais efetivamente o processo de negociação do EA e, em alguns casos, adicione elementos para controle manual. Tudo isso é chamado de interface do usuário. Ao multiplicar esses Expert Advisors nos gráficos, a carga de atualização das informações gráficas, textuais e numéricas nas interfaces aumenta muito. É claro que, ao usar um multimodelo, temos uma interface que exige um mínimo de recursos do terminal.

É claro que esse modelo não é uma panaceia para todos os problemas, mas, mesmo assim, eu o considero muito útil em meus projetos. Uso diferentes robôs e, em geral, todas as abordagens têm o direito de existir, e acho que muitos programadores iniciantes podem achar esse modelo útil. Não é necessário copiá-lo completamente, mas é possível ajustá-lo facilmente para atender às suas necessidades, se desejar. Meu objetivo não é oferecer algo extraordinário, mas tentar mostrar e explicar uma das variantes da solução desse problema.


Diferenças entre o MT4 e MT5 quanto ao usar um multibot

O que eu gosto no mais recente MetaTrader 5 é o poder do seu testador, que oferece todas as funcionalidades necessárias para testar em vários instrumentos ao mesmo tempo, desde que você utilize a abordagem de desenvolvimento de EA mencionada anteriormente. O testador sincroniza automaticamente as cotações por tempo, fornecendo uma curva de lucratividade claramente sincronizada em uma escala de tempo. O MetaTrader 4 não possui essa funcionalidade. Eu acredito que esta é a sua maior desvantagem. No entanto, vale ressaltar que a MetaQuotes está fazendo o seu melhor para dar suporte à quarta plataforma, e sua popularidade ainda é alta. Como usuário ativo do MetaTrader 4, posso dizer que essas deficiências não são tão significativas quanto podem parecer.

A linguagem MQL4 foi recentemente atualizada para a MQL5. Isso significa que, ao escrever modelos similares ao nosso, teremos um mínimo de diferenças no código. Está em minha boa tradição tentar implementar coisas para ambas as plataformas, e assim você receberá um modelo para ambas as plataformas. Essas melhorias na antiga plataforma nos permitem usar as seguintes funções que realmente precisamos:

  • CopyClose - solicitação de preços de fechamento de barras
  • CopyOpen - solicitação de preços de abertura de barras
  • CopyHigh - solicitação de máximas de barras
  • CopyLow - solicitação de mínimas de barras
  • CopyTime - solicitação de horário de abertura de barras
  • SymbolInfoTick - solicitação das informações do tick (menor movimento de preço) do símbolo
  • SymbolInfoInteger - solicitação de dados do instrumento que podem ser descritos por números inteiros e listas numeradas
  • SymbolInfo******* - outras funções de que precisamos

Essas funções estão presentes tanto no MQL4 quanto no MQL5. Elas permitem que você obtenha dados de barras para qualquer instrumento e período. A única diferença desagradável entre o testador do terminal quatro e do terminal cinco é o fato de que essas funções no terminal quatro funcionarão apenas para o gráfico atual, no qual o teste é realizado, e outras solicitações apenas informarão que não há dados devido às peculiaridades do testador para o MetaTrader 4. Portanto, ao testar nosso modelo, você terá negociações apenas no instrumento selecionado e apenas uma das curvas de lucro para um único robô.

No quinto terminal, você já terá acesso à negociação de todos os instrumentos solicitados e à linha de lucratividade geral. Mas quanto à aplicação na negociação, apenas operando diretamente com esse robô em ambos os terminais, você obterá a funcionalidade completa desse modelo. Em outras palavras, a diferença está apenas no testador. Bem, você pode concluir que, ao criar um Expert Advisor, é melhor começar com a versão para o MetaTrader 5 e, após todos os testes necessários, você pode rapidamente criar uma versão para o 4.

É claro que há uma série de diferenças que eu não destaquei, apenas quero enfatizar a importância de algumas delas, porque essas nuances devem ser conhecidas ao construir uma estrutura competente de tal modelo. O MetaTrader 5 é definitivamente melhor do que seu antecessor, mas, mesmo assim, quero dizer que não desejo deixar de usar o 4º terminal, porque em muitas situações sua potência não é tão grande em comparação com o 5º. Ambas as ferramentas ainda são boas.


As nuances de construir um modelo universal

Para construir um modelo como esse, é importante entender como a plataforma funciona, o que é um Expert Advisor e o que é um gráfico no MetaTrader. Além disso, você deve compreender que cada gráfico é um objeto separado. Cada gráfico pode estar associado a vários indicadores e apenas um EA. Pode haver vários gráficos idênticos. Vários gráficos são normalmente usados para executar diversos EAs diferentes em um mesmo período-símbolo, ou para executar múltiplas cópias de um EA com configurações diferentes. Compreendendo essas sutilezas, chegamos à conclusão de que, para abandonar múltiplos gráficos em favor do nosso modelo, teremos que implementar tudo isso dentro do nosso modelo. Isso pode ser representado na forma de um diagrama:

objects structure

Separadamente, devemos falar sobre os ticks. A desvantagem desse modelo é que não poderemos acessar o manipulador para o aparecimento de um novo tick para cada gráfico. Teremos que aplicar os ticks do gráfico em que nosso robô está trabalhando ou usar o temporizador. No final, isso pode trazer problemas para robôs que dependem de ticks, o que inclui:

  • Escrever nossos próprios manipuladores OnTick
  • É necessário implementar esses manipuladores como um derivado do OnTimer
  • Os ticks não serão perfeitos porque o OnTimer funciona com um atraso (a quantidade de atraso não é importante, mas sim sua presença).
  • É preciso usar a função SymbolInfoTick para obter os ticks

Acredito que, para aqueles que contam cada milissegundo, esse pode ser um momento irresistível, especialmente para os que gostam de arbitragem. No entanto, eu não enfatizo isso em meu modelo. Ao longo dos anos, desenvolvendo diferentes sistemas, eu adotei a abordagem de negociação baseada em barras. Isso significa que as operações de negociação e outros cálculos ocorrem principalmente quando uma nova barra aparece. Essa abordagem possui várias vantagens óbvias:

  • A imprecisão ao determinar o início de uma nova barra não afeta significativamente as negociações.
  • Quanto maior o período da barra, menor é essa influência.
  • A discretização na forma de barras aumenta a velocidade de teste significativamente.
  • A mesma qualidade de teste tanto em testes com ticks reais quanto em ticks artificiais.

Essa abordagem ensina uma certa forma de construir EAs. Esse paradigma elimina muitos problemas associados aos EAs baseados em ticks, acelera o processo de teste e proporciona uma expectativa matemática de lucro mais elevada, que é o principal obstáculo, além de economizar muito tempo e poder de computação. Acredito que podemos encontrar muitas outras vantagens, mas acho que isso é o suficiente no contexto deste artigo.

Para implementar nosso modelo, não é necessário implementar toda a estrutura do espaço de trabalho do terminal de negociação dentro dele, mas é suficiente apenas implementar um gráfico separado para cada robô. Essa não é a estrutura mais otimizada, mas se concordarmos que cada instrumento individual estará presente apenas uma vez na lista de instrumentos, então essa otimização não é necessária. Ficará assim:

our realization

Implementamos a estrutura mais simples para a implementação dos gráficos. Agora é hora de pensar sobre os parâmetros de entrada desse modelo e, mais importante, como levar em conta o número dinâmico de gráficos e EAs para cada situação dentro das possibilidades permitidas pela linguagem MQL5. A única maneira de resolver esse problema é usar variáveis de entrada de tipo string. Uma string nos permite armazenar uma grande quantidade de dados. Na verdade, para descrever todos os parâmetros necessários para esse modelo, precisaremos de arrays dinâmicos nos dados de entrada.  Claro, ninguém vai implementar essas coisas simplesmente porque poucas pessoas usariam essas possibilidades. A string é o nosso array dinâmico, no qual podemos colocar o que quisermos. Então, vamos usá-lo. Para o meu modelo mais simples, decidi introduzir três variáveis assim:

  • Charts - nossos gráficos (lista)
  • Chart Lots - lotes para negociação (lista)
  • Chart Timeframes - períodos dos gráficos (lista)

Em geral, podemos combinar todos esses dados em uma única string, mas então sua estrutura será complexa e será difícil para um usuário potencial entender como descrever corretamente os dados. Além disso, será muito fácil cometer erros na leitura e interpretação dos dados. Sem mencionar a incrível complexidade da função de conversão que retiraria esses dados das strings. Eu vi soluções semelhantes entre vendedores e, em geral, eles fizeram tudo certo. Todos os dados são simplesmente listados e separados por vírgulas. No início do EA, esses dados são retirados da string usando funções especiais e preenchidos nas respectivos arrays dinâmicos, que são posteriormente usados no código. Nós também seguiremos esse caminho. Podemos adicionar mais strings similares com regras de enumeração idênticas. Eu decidi usar ":" como separador. Se usarmos vírgulas, não ficará claro como lidar com arrays duplos, como Chart Lots. Foi assim que escolhi esse separador. É possível adicionar mais dessas variáveis de string, e, em geral, é possível construir um modelo ainda mais completo e versátil, mas minha tarefa aqui é apenas mostrar como implementar isso e fornecer a você a primeira versão do modelo que você pode modificar rapidamente e facilmente.

Não basta implementar esses arrays, é também necessário implementar variáveis comuns, por exemplo:

  • Work Timeframe For Unsigned - período do gráfico, quando não especificado
  • Fix Lot For Unsigned - lote quando não especificado

A lista Charts deve ser preenchida. A mesma ação é opcional para Chart Lots e Chart Timeframes. Por exemplo, podemos utilizar um único lote para todos os gráficos e o mesmo período para todos eles. Uma funcionalidade similar será implementada em nosso modelo. É desejável aplicar essas regras de implementação sempre que possível para garantir a concisão e a clareza ao definir os parâmetros de entrada de um EA construído com base em tais modelos. 

Agora, vamos definir mais algumas variáveis importantes para uma implementação mínima desse padrão:

  • Last Bars Count - número de barras mais recentes que armazenamos para cada gráfico
  • Deposit For Lot - depósito para o uso de um lote específico
  • First Magic - ID único para as operações de um EA separado

Acredito que a primeira variável seja bastante clara. A segunda variável é mais difícil de entender. É assim que eu regulamento o lote automático nos meus EAs. Se eu definir "0", informo ao algoritmo que ele deve negociar apenas um lote fixo especificado na string correspondente ou em uma variável compartilhada que consideramos acima. Caso contrário, eu defino o depósito necessário para que o lote especificado nas configurações possa ser aplicado. É fácil entender que, com um depósito menor ou maior, esse lote muda de valor de acordo com a equação:

  • Lot = Input Lot * ( Current Deposit / Deposit For Lot )

Acredito que agora tudo deve estar claro. Se quisermos um lote fixo, definimos zero, e em outros casos ajustamos o depósito nas configurações de entrada para os riscos. Eu acho que é uma solução simples e eficaz. Se necessário, você pode alterar a abordagem de avaliação de riscos para o lote automático, mas pessoalmente eu gosto dessa opção, não faz sentido complicá-la demais.

Vale a pena mencionar sobre a sincronização, em particular sobre a questão de definir o "Expert Magic Number", ou identificador do Expert Advisor. Ao negociar com EAs ou até mesmo em uma forma mista, todos os programadores respeitáveis prestam especial atenção a esta variável em particular. A questão é que, ao usar vários EAs, é muito importante garantir que cada um deles tenha um ID único. Caso contrário, ao lidar com ordens ou posições, você terá uma confusão completa e suas estratégias deixarão de funcionar corretamente, e na maioria dos casos elas simplesmente deixarão de funcionar completamente. Eu espero que não seja necessário explicar o motivo disso. Cada vez que um EA é colocado no gráfico, precisamos configurar esses IDs e garantir que eles não se repitam. Mesmo um único erro pode levar a consequências desastrosas. Além disso, se você fechar acidentalmente o gráfico com o EA, terá que reconfigurá-lo novamente. Como resultado, a probabilidade de cometer um erro aumenta consideravelmente. Isso pode ser também muito desagradável em muitos outros aspectos. Por exemplo, você fecha o gráfico e esquece qual ID foi usado lá. Nesse caso, você terá que procurá-lo no histórico de negociações. Sem o ID, o EA recém reiniciado pode funcionar incorretamente, e muitas coisas desagradáveis podem acontecer.

O uso de um modelo como o meu nos liberta desse controle e minimiza possíveis erros, pois precisamos definir apenas o ID inicial nas configurações do EA, enquanto o restante dos IDs será gerado automaticamente usando um incremento e atribuído às cópias correspondentes dos EAs. Esse processo acontecerá automaticamente a cada reinicialização. De qualquer forma, lembrar-se apenas de um ID inicial é muito mais fácil do que se lembrar de algum ID aleatório no meio do caminho.


Escrevendo um modelo universal

É hora de implementar o modelo. Vou tentar omitir elementos excessivos, para que todos que precisem desse modelo possam baixar e ver o restante no código-fonte. Aqui, mostrarei apenas coisas que estão diretamente relacionadas às nossas ideias. Níveis de stop e outros parâmetros são definidos pelos usuários. Você pode encontrar minha implementação no código-fonte.  Primeiro, vamos definir as variáveis de entrada de que definitivamente precisaremos:

//+------------------------------------------------------------------+
//| Inputs                                                           |
//+------------------------------------------------------------------+
input string SymbolsE="EURUSD:GBPUSD:USDCHF:USDJPY:NZDUSD:AUDUSD:USDCAD";//Charts
input string LotsE="0.01:0.01:0.01:0.01:0.01:0.01:0.01";//Chart Lots
input string TimeframesE="H1:H1:H1:H1:H1:H1:H1";//Chart Timeframes
input int LastBars=10;//Last Bars Count
input ENUM_TIMEFRAMES TimeframeE=PERIOD_M1;//Work Timeframe For Unsigned
input double RepurchaseLotE=0.01;//Fix Lot For Unsigned
input double DepositForRepurchaseLotE=0.00;//Deposit For Lot (if "0" then fix)
input int MagicE=156;//First Magic

Aqui você pode ver um exemplo de preenchimento de variáveis de string que refletem nossos arrays dinâmicos, assim como o exemplo de variáveis compartilhadas. Aliás, esse código terá a mesma aparência tanto no MQL4 quanto no MQL5. Tentei tornar tudo o mais similar possível.

Agora, vamos decidir como obter nossos dados de string. Isso será feito pela função correspondente, mas primeiro criaremos arrays onde nossa função adicionará os dados obtidos das strings:

//+------------------------------------------------------------------+
//|Arrays                                                            |
//+------------------------------------------------------------------+
string S[];// Symbols array
double L[];//Lots array
ENUM_TIMEFRAMES T[];//Timeframes array

A função a seguir preenche esses arrays:

//+------------------------------------------------------------------+
//| Fill arrays                                                      |
//+------------------------------------------------------------------+
void ConstructArrays()
   {
      int SCount=1;
      for (int i = 0; i < StringLen(SymbolsE); i++)//calculation of the number of tools
         {
         if (SymbolsE[i] == ':')
            {
            SCount++;
            }
         }
      ArrayResize(S,SCount);//set the size of the character array
      ArrayResize(CN,SCount);//set the size of the array to use bars for each character
      int Hc=0;//found instrument index
      for (int i = 0; i < StringLen(SymbolsE); i++)//building an array of tools
         {
         if (i == 0)//if we just started
            {
            int LastIndex=-1;
            for (int j = i; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i,LastIndex);
               Hc++;
               }
            else
               {
               S[Hc]=SymbolsE;
               Hc++;
               }
            }          
         if (SymbolsE[i] == ':')
            {
            int LastIndex=-1;
            for (int j = i+1; j < StringLen(SymbolsE); j++)
               {
               if (StringGetCharacter(SymbolsE,j) == ':')
                  {
                  LastIndex=j;
                  break;
                  }
               }
            if (LastIndex != -1)//if no separating colon was found
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,LastIndex-(i+1));
               Hc++;
               }
            else
               {
               S[Hc]=StringSubstr(SymbolsE,i+1,StringLen(SymbolsE)-(i+1));
               Hc++;
               }               
            }
         }
      for (int i = 0; i < ArraySize(S); i++)//assignment of the requested number of bars
         {
         CN[i]=LastBars;
         }
      ConstructLots();
      ConstructTimeframe();         
   }

Resumidamente, ele calcula a quantidade de dados na string com base nos separadores e, com base no primeiro array, define o tamanho de todos os outros arrays, de forma idêntica ao array com símbolos, após o que os símbolos são preenchidos primeiro e, em seguida, funções como ConstructLots e ConstructTimeframe são executadas. A implementação delas é semelhante à implementação dessa função, com algumas diferenças. Você pode ver a implementação delas no código-fonte. Eu não as adicionei ao artigo para não mostrar código duplicado.

Continuemos, ra precisamos criar classes específicas para os objetos do gráfico virtual e do robô virtual encadeado, respectivamente. Vamos começar definindo que os gráficos virtuais e os Expert Advisors serão armazenados em arrays:

//+------------------------------------------------------------------+
//| Charts & experts pointers                                        |
//+------------------------------------------------------------------+
Chart *Charts[];
BotInstance *Bots[];

Agora, vamos começar com a classe do gráfico:

//+------------------------------------------------------------------+
//| Chart class                                                      |
//+------------------------------------------------------------------+
class Chart
   {
   public:
   datetime TimeI[];
   double CloseI[];
   double OpenI[];
   double HighI[];
   double LowI[];
   string BasicSymbol;//the base instrument that was extracted from the substring
   double ChartPoint;//point size of the current chart
   double ChartAsk;//Ask
   double ChartBid;//Bid
   datetime tTimeI[];//auxiliary array to control the appearance of a new bar
   static int TCN;//tcn
   string CurrentSymbol;//symbol
   ENUM_TIMEFRAMES Timeframe;//timeframe
   int copied;//how much data is copied
   int lastcopied;//last amount of data copied
   datetime LastCloseTime;//last bar time
   MqlTick LastTick;//last tick fos this instrument
   
   Chart()
      {
      ArrayResize(tTimeI,2);
      }
   
   void ChartTick()//this chart tick
      {
      SymbolInfoTick(CurrentSymbol,LastTick);
      ArraySetAsSeries(tTimeI,false);
      copied=CopyTime(CurrentSymbol,Timeframe,0,2,tTimeI);
      ArraySetAsSeries(tTimeI,true);
      if ( copied == 2 && tTimeI[1] > LastCloseTime )
         {
         ArraySetAsSeries(CloseI,false);                        
         ArraySetAsSeries(OpenI,false);                           
         ArraySetAsSeries(HighI,false);                        
         ArraySetAsSeries(LowI,false);                              
         ArraySetAsSeries(TimeI,false);                                                            
         lastcopied=CopyClose(CurrentSymbol,Timeframe,0,Chart::TCN+2,CloseI);
         lastcopied=CopyOpen(CurrentSymbol,Timeframe,0,Chart::TCN+2,OpenI);   
         lastcopied=CopyHigh(CurrentSymbol,Timeframe,0,Chart::TCN+2,HighI);   
         lastcopied=CopyLow(CurrentSymbol,Timeframe,0,Chart::TCN+2,LowI);
         lastcopied=CopyTime(CurrentSymbol,Timeframe,0,Chart::TCN+2,TimeI);
         ArraySetAsSeries(CloseI,true);
         ArraySetAsSeries(OpenI,true);
         ArraySetAsSeries(HighI,true);                        
         ArraySetAsSeries(LowI,true);
         ArraySetAsSeries(TimeI,true);         
         LastCloseTime=tTimeI[1];
         }
      ChartBid=LastTick.bid;
      ChartAsk=LastTick.ask;
      ChartPoint=SymbolInfoDouble(CurrentSymbol,SYMBOL_POINT);
      }
   };
int Chart::TCN = 0;

A classe possui apenas uma função, que controla a atualização de ticks e barras, bem como os campos necessários para identificar alguns parâmetros necessários de um gráfico específico. Alguns parâmetros estão faltando lá. Se desejar, você pode adicionar os que faltam adicionando uma atualização a eles, por exemplo, assim como a atualização do "ChartPoint". Os arrays de barras são feitos no estilo do MQL4. Ainda acho muito conveniente trabalhar com arrays predefinidos no MQL4. É muito útil quando você sabe que a barra zero é a barra atual. De qualquer forma, esta é apenas a minha visão. Você é livre para seguir a sua própria.

Agora precisamos descrever a classe de um EA virtual separado:

//+------------------------------------------------------------------+
//| Bot instance class                                               |
//+------------------------------------------------------------------+
class BotInstance//expert advisor object
   {
   public:
   CPositionInfo  m_position;// trade position object
   CTrade         m_trade;// trading object   
   ///-------------------this robot settings----------------------
   int MagicF;//Magic
   string CurrentSymbol;//Symbol
   double CurrentLot;//Start Lot
   int chartindex;//Chart Index
   ///------------------------------------------------------------   
      
   
   ///constructor
   BotInstance(int index,int chartindex0)//load all data from hat using index, + chart index
      {
      chartindex=chartindex0;
      MagicF=MagicE+index;
      CurrentSymbol=Charts[chartindex].CurrentSymbol;
      CurrentLot=L[index];
      m_trade.SetExpertMagicNumber(MagicF);
      }
   ///
   
   void InstanceTick()//bot tick
      {
      if ( bNewBar() ) Trade();
      }
      
   private:
   datetime Time0;
   bool bNewBar()//new bar
      {
      if ( Time0 < Charts[chartindex].TimeI[1] && Charts[chartindex].ChartPoint != 0.0 )
         {
         if (Time0 != 0)
            {
            Time0=Charts[chartindex].TimeI[1];
            return true;
            }
         else
            {
            Time0=Charts[chartindex].TimeI[1];
            return false;
            }
         }
      else return false;
      }
      
   //////************************************Main Logic********************************************************************
   void Trade()//main trade function
      {
      //Close[0]   -->   Charts[chartindex].CloseI[0] - example of access to data arrays of bars of the corresponding chart
      //Open[0]   -->   Charts[chartindex].OpenI[0] -----------------------------------------------------------------------
      //High[0]   -->   Charts[chartindex].HighI[0] -----------------------------------------------------------------------
      //Low[0]   -->   Charts[chartindex].LowI[0] -------------------------------------------------------------------------
      //Time[0]   -->   Charts[chartindex].TimeI[0] -----------------------------------------------------------------------      

      if ( true )
         {
            CloseBuyF();
            //CloseSellF();       
         }
      if ( true )
         {
            BuyF();
            //SellF(); 
         }

      }
      
   double OptimalLot()//optimal lot calculation
      {
      if (DepositForRepurchaseLotE != 0.0) return CurrentLot * (AccountInfoDouble(ACCOUNT_BALANCE)/DepositForRepurchaseLotE);
      else return CurrentLot;
      }
      
   //here you can add functionality or variables if the trading function turns out to be too complicated
   //////*******************************************************************************************************************
   
   ///trade functions
   int OrdersG()//the number of open positions / orders of this virtual robot
      {
      ulong ticket;
      bool ord;
      int OrdersG=0;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetString(POSITION_SYMBOL) == CurrentSymbol )
            {
            OrdersG++;
            }
         }
      return OrdersG;
      }
   
   /////////********/////////********//////////***********/////////trade function code block
   void BuyF()//buy market
      {
      double DtA;
      double CorrectedLot;
   
      DtA=double(TimeCurrent())-GlobalVariableGet("TimeStart161_"+IntegerToString(MagicF));//unique bot marker last try datetime
      if ( (DtA > 0 || DtA < 0) )
         {
         CorrectedLot=OptimalLot(Charts[chartindex]);
         if ( CorrectedLot > 0.0 )
            {
            //try buy logic
            }            
         }
      }
      
   void SellF()//sell market
      {
      //Same logic
      }

   void CloseSellF()//close sell position
      {
      ulong ticket;
      bool ord;
      for ( int i=0; i<PositionsTotal(); i++ )
         {
         ticket=PositionGetTicket(i);
         ord=PositionSelectByTicket(ticket);      
         if ( ord && PositionGetInteger(POSITION_MAGIC) == MagicF && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL 
         && PositionGetString(POSITION_SYMBOL) == Charts[chartindex].CurrentSymbol )
            {
            //Close Sell logic
            }
         }    
      }
      
   void CloseBuyF()//close buy position
      {
      //same logic 
      }        
      
   bool bOurMagic(ulong ticket,int magiccount)//whether the magic of the current deal matches one of the possible magics of our robot
      {
      int MagicT[];
      ArrayResize(MagicT,magiccount);
      for ( int i=0; i<magiccount; i++ )
         {
         MagicT[i]=MagicE+i;
         }
      for ( int i=0; i<ArraySize(MagicT); i++ )
         {
         if ( HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicT[i] ) return true;
         }
      return false;
      }
   /////////********/////////********//////////***********/////////end trade function code block
   };

O autor removeu parte da lógica repetitiva para reduzir a quantidade de código. Esta é a classe em que todo o algoritmo do seu EA deve ser implementado. A principal funcionalidade presente nesta classe:

  • Trade() - função principal de negociação que é chamada no manipulador de barras para o gráfico correspondente
  • BuyF() - função de compra a mercado
  • SellF() - função de venda a mercado
  • CloseBuyF() - função de fechamento de posições de compra a mercado
  • CloseSellF() - função de fechamento de posições de venda a mercado

Este é o conjunto mínimo de funções para demonstrar a negociação por barras. Para esta demonstração, só precisamos abrir qualquer posição e fechá-la na próxima barra. Isso é suficiente dentro do contexto deste artigo. Há alguma funcionalidade adicional desta classe que deve complementar o entendimento:

  • OrdersG() - contagem de posições abertas em um símbolo específico vinculado ao gráfico
  • OptimalLot() - preparação de um lote antes de enviá-lo para a função de negociação (selecionando um lote fixo ou calculando um lote automático)
  • bOurMagic()  - verificação de transações do histórico em conformidade com a lista de permissões (para verificar um histórico personalizado apenas)

Essas funções podem ser necessárias para implementar a lógica de negociação. Esses são apenas exemplos. Também seria razoável lembrar do manipulador de nova barra:

  • InstanceTick() - simulação de ticks em uma instância separada do EA
  • bNewBar() - predicado para verificar o aparecimento de uma nova barra (usado dentro do InstanceTick)

Se o predicado mostrar uma nova barra, então a função Trade é acionada. Esta é a função, na qual a lógica principal de negociação deve ser definida. A conexão com o gráfico correspondente é feita usando a variável chartindex atribuída quando a instância é criada. Portanto, cada instância do EA sabe qual gráfico deve obter a cotação.

Agora, vamos considerar o processo de criação dos próprios gráficos e EAs virtuais. Os gráficos virtuais são criados primeiro:

//+------------------------------------------------------------------+
//| Creation of graph objects                                        |
//+------------------------------------------------------------------+
void CreateCharts()
   {
   bool bAlready;
   int num=0;
   string TempSymbols[];
   string Symbols[];
   ConstructArrays();//array preparation
   int tempcnum=CN[0];
   Chart::TCN=tempcnum;//required number of stored bars for all instruments
   for (int j = 0; j < ArraySize(Charts); j++)//fill in all the names and set the dimensions of all time series, each graph
      {
      Charts[j] = new Chart();
      Charts[j].lastcopied=0;
      ArrayResize(Charts[j].CloseI,tempcnum+2);//assign size to character arrays
      ArrayResize(Charts[j].OpenI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].HighI,tempcnum+2);//----------------------------------
      ArrayResize(Charts[j].LowI,tempcnum+2);//-----------------------------------
      ArrayResize(Charts[j].TimeI,tempcnum+2);//----------------------------------
      Charts[j].CurrentSymbol = S[j];//symbol
      Charts[j].Timeframe = T[j];//timeframe
      }
   ArrayResize(Bots,ArraySize(S));//assign a size to the array of bots      
   }

Após criar os gráficos e definir o tamanho da array com os EAs virtuais, precisamos criar as instâncias dos próprios EAs e implementar a conexão dos EAs virtuais com os gráficos:

//+------------------------------------------------------------------+
//| create and hang all virtual robots on charts                     |
//+------------------------------------------------------------------+
void CreateInstances()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      for (int j = 0; j < ArraySize(Charts); j++)
         {
         if ( Charts[j].CurrentSymbol == S[i] )
            {
            Bots[i] = new BotInstance(i,j);
            break;
            } 
         }
      }
   }

A conexão é realizada usando o índice "j", que é gravado em cada instância do EA virtual quando ele é criado. A variável correspondente, que já mostrei acima, está destacada ali. É claro que tudo isso pode ser feito de maneiras diferentes e com muito mais elegância, mas acho que o principal é que a ideia geral está clara. 

Bem, tudo o que resta é mostrar como os ticks são simulados em cada gráfico e o EA que está associado a ele:

//+------------------------------------------------------------------+
//| All bcharts & all bots tick imitation                            |
//+------------------------------------------------------------------+
void AllChartsTick()
   {
   for (int i = 0; i < ArraySize(Charts); i++)
      {
      Charts[i].ChartTick();
      }
   }

void AllBotsTick()
   {
   for (int i = 0; i < ArraySize(S); i++)
      {
      if ( Charts[Bots[i].chartindex].lastcopied >= Chart::TCN+1 ) Bots[i].InstanceTick();
      }
   }

Só gostaria de salientar que esse modelo foi obtido por meio do retoque do meu modelo mais complexo, que foi projetado para fins muito mais sérios, então pode haver algo desnecessário em alguns trechos. Acredito que você não terá problemas para remover o excesso e deixá-lo bonito, se for importante.

Além do modelo, há uma interface simples que, na minha opinião, também pode ser útil, por exemplo, ao fazer um pedido como freelancer ou para outros fins:


Deixei espaço livre nessa interface, o que é suficiente para três entradas; se você ficar sem espaço, poderá expandi-la facilmente e alterar sua estrutura por completo. Mas especificamente neste exemplo, para adicionar os três campos que faltam, você precisa encontrar os seguintes lugares no código:

//+------------------------------------------------------------------+
//| Reserved elements                                                |
//+------------------------------------------------------------------+

   "template-UNSIGNED1",//UNSIGNED1
   "template-UNSIGNED2",//UNSIGNED2
   "template-UNSIGNED3",//UNSIGNED3

   //LabelCreate(0,OwnObjectNames[13],0,x+Border+2,y+17+Border+20*5+20*5+23,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED1
   //LabelCreate(0,OwnObjectNames[14],0,x+Border+2,y+17+Border+20*5+20*5+23+20*1,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED2
   //LabelCreate(0,OwnObjectNames[15],0,x+Border+2,y+17+Border+20*5+20*5+23+20*2,corner,"","Arial",11,clrWhite,0.0,ANCHOR_LEFT);//UNSIGNED3

   ////////////////////////////
   //TempText="UNSIGNED1 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[13],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED2 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);   
   //ObjectSetString(0,OwnObjectNames[14],OBJPROP_TEXT,TempText);
   //TempText="UNSIGNED3 : ";
   //TempText+=DoubleToString(NormalizeDouble(0.0),3);
   //ObjectSetString(0,OwnObjectNames[15],OBJPROP_TEXT,TempText);
   ///////////////////////////
 

As três primeiras entradas atribuem os nomes dos novos elementos na interface, as três segundas são usadas na criação da interface ao iniciar o EA e as três últimas são usadas na função para atualizar as informações na interface. Acho que devemos encerrar esta parte e passar a testar os dois modelos para ver se funcionam. Será suficiente mostrar sua operação no visualizador do testador para demonstrar seu funcionamento. Mostrarei apenas a versão para o MetaTrader 5, porque seu visualizador é muito melhor e mais avançado e, além disso, o resultado do trabalho mostrará claramente tudo o que você precisa para confirmar o desempenho:

checking using MetaTrader 5 tester visualization


Como você pode ver na captura de tela, carregamos todos os 7 gráficos para os principais pares de moedas do mercado Forex. O log mostra que a negociação está em andamento para todos os instrumentos da lista. A negociação continua de forma independente, conforme solicitado. Em outras palavras, esses Expert Advisors negociam cada um em seu próprio gráfico e não interagem de forma alguma


Considerações finais

Neste artigo, revisamos as principais nuances da construção de modelos universais para os terminais MetaTrader 4 e MetaTrader 5, fizemos um modelo simples, mas funcional, analisamos os pontos mais importantes de seu funcionamento e também confirmamos sua viabilidade usando o visualizador de testes do MetaTrader 5. Acredito que agora seja bastante óbvio que um modelo como esse não é tão complicado. Em geral, você pode fazer várias implementações de tais modelos, mas é claro que eles podem ser completamente diferentes, mas ainda assim aplicáveis. O principal é entender as nuances básicas de construção de tais estruturas. Se necessário, você pode reconfigurar os modelos para uso pessoal.


Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/12434

Arquivos anexados |
MultiTemplate.mq4 (93.94 KB)
MultiTemplate.mq5 (91.41 KB)
Teoria das Categorias em MQL5 (Parte 6): produtos fibrados monomórficos e coprodutos fibrados epimórficos Teoria das Categorias em MQL5 (Parte 6): produtos fibrados monomórficos e coprodutos fibrados epimórficos
A teoria das categorias é um ramo diversificado e em expansão da matemática que só recentemente começou a ser abordado na comunidade MQL5. Esta série de artigos tem como objetivo analisar alguns de seus conceitos para criar uma biblioteca aberta e utilizar ainda mais essa maravilhosa seção na criação de estratégias de negociação.
Como conectar o MetaTrader 5 ao PostgreSQL Como conectar o MetaTrader 5 ao PostgreSQL
Esse artigo descreve quatro métodos de conexão do código MQL5 ao banco de dados Postgres e apresenta um guia passo a passo para configurar um ambiente de desenvolvimento para um deles, a API REST, por meio do Windows Subsystem for Linux (WSL). Além disso, mostra-se um aplicativo de demonstração para a API com o código MQL5 necessário para inserir dados e consultar as respectivas tabelas, bem como um EA de demonstração para usar esses dados.
Experimentos com redes neurais (Parte 5): Normalização de parâmetros de entrada para alimentar a rede neural Experimentos com redes neurais (Parte 5): Normalização de parâmetros de entrada para alimentar a rede neural
As redes neurais são tudo para nós. E vamos verificar na prática se é assim, indagando se MetaTrader 5 é uma ferramenta autossuficiente para implementar redes neurais na negociação. A explicação vai ser simples.
Um exemplo de como montar modelos ONNX em MQL5 Um exemplo de como montar modelos ONNX em MQL5
O ONNX (Open Neural Network Exchange) é um padrão aberto para a representação de modelos de redes neurais. Neste artigo, mostraremos a possibilidade de usar dois modelos ONNX simultaneamente em um Expert Advisor.