Criando um Consultor Especialista, que negocia em um número de instrumentos

Nikolay Kositsin | 23 janeiro, 2014

Introdução

O lado técnico de implementar o código do programa de forma que um único Consultor Especialista, lançado em um único gráfico, seja capaz de negociar com diferentes ativos financeiros ao mesmo tempo. Em geral, isso não era um problema mesmo no MQL4. Mas apenas com o advento do terminal do cliente do MetaTrader 5, negociantes finalmente conseguiram a oportunidade de realizar uma análise completa do trabalho de tais automatizados, utilizando verificadores de estratégia.

Então agora automatizados com múltiplas moedas correntes se tornarão mais populares do que nunca, e nós podemos prever uma onda de interesse na construção de tais sistemas de negociação. Mas o problema principal de implementação de tais robôs é o fato de que suas dimensões no código do programa expandem, no melhor dos casos, em progressão aritmética, e isso não é fácil de aceitar para um programador típico.

Nesse artigo escreveremos um Consultor Especialista simples com múltiplas moedas correntes, no qual as falhas de estrutura são, se não ausentes, ao menos minimizadas.


1. Implementando um sistema seguidor de tendências simples

De fato, poderíamos começar com um sistema de negociação maximamente simples, seguindo a tendência na base de um terminal integrado de um indicador técnico média móvel exponencial tripla. Este é um algorítimo muito simples, que não requer comentários especiais, e que incorporaremos no código do programa.

Mas, primeiro e antes de tudo, gostaria de fazer as conclusões mais gerais sobre o Expert Advisor. Faz sentido começar com o bloco de parâmetros do Consultor Especialista, declarados em um nível global.

Então, primeiro de tudo precisamos escolher os ativos financeiros em que trabalharemos. Isso pode ser feito utilizando variáveis de entrada, nas quais os símbolos do ativo podem ser armazenados. Agora seria bom possuir um interruptor de proibição para cada ativo financeiro, que permitiria desabilitar operações de negociação pelo ativo.

Naturalmente, cada recurso deveria estar associado com seus parâmetros individuais de stop loss (parar perdas), take profit (obter lucros), o volume da posição aberta, e slippage (derrapagem). E por razões óbvias, os parâmetros de entrada do indicador média móvel exponencial tripla para cada chip de negociação deveriam ser individual.

Aqui está um bloco final de variáveis de entrada para apenas um chip, realizado de acordo com esses argumentos. Os blocos restantes diferem apenas pelos números nos nomes dos parâmetros de entrada do Consultor Especialista. Para esse exemplo me limitei a apenas doze ativos financeiros, apesar de idealmente não haver limitações no software para o número de tais blocos.

Nós apenas precisamos de algo para negociar! E mais importante - nosso PC deve possuir recursos o suficiente para resolver esse problema.

input string                Symb0 = "EURUSD";
input  bool                Trade0 = true; 
input int                    Per0 = 15; 
input ENUM_APPLIED_PRICE ApPrice0 = PRICE_CLOSE;
input int                 StLoss0 = 1000;
input int               TkProfit0 = 2000;
input double                Lots0 = 0.1;
input int               Slippage0 = 30;

Agora que nós resolvemos as variáveis no nível global, podemos proceder para a construção do código dentro da função OnTick(). A opção mais racional seria a divisão do algoritmo para receber sinais de negociação e a parte da negociação de fato do Consultor Especialista em duas funções personalizadas.

E já que o Consultor Especialista funciona com doze ativos financeiros ao mesmo tempo, deve sempre haver doze chamadas dessas funções dentro do bloco OnTick().

Naturalmente, o primeiro parâmetro de entrada dessas funções deveria ser um número único, sob o qual esses ativos de negociação serão listados. O segundo parâmetro de entrada, por razões óbvias, será o nome da linha do ativo financeiro de negociação.

Para o papel de terceiro parâmetro, vamos configurar uma variável lógica para resolver a negociação. Depois, para o algorítimo de determinação de sinais de negociação, siga os sinais do indicador de entrada e para uma função de negócio - a distância para as ordens pendentes, o volume da posição e deslize (deslize permitido do preço da posição aberta).

Para transferir os sinais de negociação de uma função para a outra, arranjos estáticos deveriam ser configurados como os parâmetros da função, que derivam seus valores através de uma referência. Essa é a versão final do código proposto para a função OnTick().

void OnTick()
  {
//--- declare variables arrays for trade signals 
   static bool UpSignal[12], DnSignal[12], UpStop[12], DnStop[12];
  
//--- get trade signals
   TradeSignalCounter( 0, Symb0,  Trade0,  Per0,  ApPrice0,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 1, Symb1,  Trade1,  Per1,  ApPrice1,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 2, Symb2,  Trade2,  Per2,  ApPrice2,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 3, Symb3,  Trade3,  Per3,  ApPrice3,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 4, Symb4,  Trade4,  Per4,  ApPrice4,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 5, Symb5,  Trade5,  Per5,  ApPrice5,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 6, Symb6,  Trade6,  Per6,  ApPrice6,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 7, Symb7,  Trade7,  Per7,  ApPrice7,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 8, Symb8,  Trade8,  Per8,  ApPrice8,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter( 9, Symb9,  Trade9,  Per9,  ApPrice9,  UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(10, Symb10, Trade10, Per10, ApPrice10, UpSignal, DnSignal, UpStop, DnStop);
   TradeSignalCounter(11, Symb11, Trade11, Per11, ApPrice11, UpSignal, DnSignal, UpStop, DnStop);
  
//--- perform trade operations
   TradePerformer( 0, Symb0,  Trade0,  StLoss0,  TkProfit0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 1, Symb1,  Trade1,  StLoss1,  TkProfit1,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 2, Symb2,  Trade2,  StLoss2,  TkProfit2,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 3, Symb3,  Trade3,  StLoss3,  TkProfit3,  Lots3,  Slippage3,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 4, Symb4,  Trade4,  StLoss4,  TkProfit4,  Lots4,  Slippage4,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 5, Symb5,  Trade5,  StLoss5,  TkProfit5,  Lots5,  Slippage5,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 6, Symb6,  Trade6,  StLoss6,  TkProfit6,  Lots6,  Slippage6,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 7, Symb7,  Trade7,  StLoss7,  TkProfit7,  Lots7,  Slippage7,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 8, Symb8,  Trade8,  StLoss8,  TkProfit8,  Lots8,  Slippage8,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 9, Symb9,  Trade9,  StLoss9,  TkProfit9,  Lots9,  Slippage9,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer(10, Symb10, Trade10, StLoss10, TkProfit10, Lots10, Slippage10, UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer(11, Symb11, Trade11, StLoss11, TkProfit11, Lots11, Slippage11, UpSignal, DnSignal, UpStop, DnStop); 
//---
  }

Dentro da função TradeSignalCounter(), é apenas necessário obter o nome do indicador técnico média móvel exponencial tripla uma vez na inicialização de cada chip, e depois a cada mudança da barra para calcular os sinais de negociação.

Esse esquema relativamente simples com a implementação do código está começando a transbordar com detalhes pequenos.

bool TradeSignalCounter(int Number,
                        string Symbol_,
                        bool Trade,
                        int period,
                        ENUM_APPLIED_PRICE ApPrice,
                        bool &UpSignal[],
                        bool &DnSignal[],
                        bool &UpStop[],
                        bool &DnStop[])
  {
//--- check if trade is prohibited
   if(!Trade)return(true);

//--- declare variable to store final size of variables arrays
   static int Size_=0;

//--- declare array to store handles of indicators as static variable
   static int Handle[];

   static int Recount[],MinBars[];
   double TEMA[4],dtema1,dtema2;

//--- initialization
   if(Number+1>Size_) // Entering the initialization block only on first start
     {
      Size_=Number+1; // For this number entering the block is prohibited

      //--- change size of variables arrays
      ArrayResize(Handle,Size_);
      ArrayResize(Recount,Size_);
      ArrayResize(MinBars,Size_);

      //--- determine minimum number of bars, sufficient for calculation 
      MinBars[Number]=3*period;

      //--- setting array elements to 0
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;

      //--- use array as timeseries
      ArraySetAsSeries(TEMA,true);

      //--- get indicator's handle
      Handle[Number]=iTEMA(Symbol_,0,period,0,ApPrice);
     }

//--- check if number of bars is sufficient for calculation 
   if(Bars(Symbol_,0)<MinBars[Number])return(true);
//--- get trade signals 
   if(IsNewBar(Number,Symbol_,0) || Recount[Number]) // Entering the block on bar change or on failed copying of data
     {
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;

      //--- using indicator's handles, copy values of indicator's
      //--- buffers into static array, specially prepared for this purpose
      if(CopyBuffer(Handle[Number],0,0,4,TEMA)<0)
        {
         Recount[Number]=true; // As data were not received, we should return 
                               // into this block (where trade signals are received) on next tick!
         return(false);        // Exiting the TradeSignalCounter() function without receiving trade signals
        }

      //--- all copy operations from indicator buffer are successfully completed
      Recount[Number]=false; // We may not return to this block until next change of bar

      int Digits_ = int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+4);
      dtema2 = NormalizeDouble(TEMA[2] - TEMA[3], Digits_);
      dtema1 = NormalizeDouble(TEMA[1] - TEMA[2], Digits_);

      //---- determining the input signals
      if(dtema2 > 0 && dtema1 < 0) DnSignal[Number] = true;
      if(dtema2 < 0 && dtema1 > 0) UpSignal[Number] = true;

      //---- determining the output signals
      if(dtema1 > 0) DnStop[Number] = true;
      if(dtema1 < 0) UpStop[Number] = true;
     }
//----+
   return(true);
  }

Nesse aspecto, o código da função TradePerformer() acaba por ser bem simples:

bool TradePerformer(int    Number,
                    string Symbol_,
                    bool   Trade,
                    int    StLoss,
                    int    TkProfit,
                    double Lots,
                    int    Slippage,
                    bool  &UpSignal[],
                    bool  &DnSignal[],
                    bool  &UpStop[],
                    bool  &DnStop[])
  {
//--- check if trade is prohibited
   if(!Trade)return(true);

//--- close opened positions 
   if(UpStop[Number])BuyPositionClose(Symbol_,Slippage);
   if(DnStop[Number])SellPositionClose(Symbol_,Slippage);

//--- open new positions
   if(UpSignal[Number])
      if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         UpSignal[Number]=false; //This trade signal will be no more on this bar!
//---
   if(DnSignal[Number])
      if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit))
         DnSignal[Number]=false; //This trade signal will be no more on this bar!
//---
   return(true);
  }
Mas isso é apenas porque os comandos reais para o desempenho das operações de negociação estão empacotados em quatro funções adicionais:
BuyPositionClose();
SellPositionClose();
BuyPositionOpen();
SellPositionOpen();

Todas as quatro funções funcionam de forma completamente análoga, então podemos nos limitar a examinação de apenas uma delas:

bool BuyPositionClose(const string symbol,ulong deviation)
  {
//--- declare structures of trade request and result of trade request
   MqlTradeRequest request;
   MqlTradeResult result;
   ZeroMemory(request);
   ZeroMemory(result);

//--- check if there is BUY position
   if(PositionSelect(symbol))
     {
      if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) return(false);
     }
   else  return(false);

//--- initializing structure of the MqlTradeRequest to close BUY position
   request.type   = ORDER_TYPE_SELL;
   request.price  = SymbolInfoDouble(symbol, SYMBOL_BID);
   request.action = TRADE_ACTION_DEAL;
   request.symbol = symbol;
   request.volume = PositionGetDouble(POSITION_VOLUME);
   request.sl = 0.0;
   request.tp = 0.0;
   request.deviation=(deviation==ULONG_MAX) ? deviation : deviation;
   request.type_filling=ORDER_FILLING_FOK;
//---
   string word="";
   StringConcatenate(word,
                     "<<< ============ BuyPositionClose():   Close Buy position at ",
                     symbol," ============ >>>");
   Print(word);

//--- send order to close position to trade server
   if(!OrderSend(request,result))
     {
      Print(ResultRetcodeDescription(result.retcode));
      return(false);
     }
//----+
   return(true);
  }

Basicamente, isso é tudo do Consultor Especialista de múltiplas moedas correntes (Exp_TEMA.mq5)!

Além das funções consideradas, ele contém duas funções adicionais do usuário:

bool IsNewBar(int Number, string symbol, ENUM_TIMEFRAMES timeframe);
string ResultRetcodeDescription(int retcode);

A primeira dessas funções retorna ao valor verdadeiro no momento da mudança da barra, baseado no símbolo selecionado e cronograma, e o segundo, retorna a linha pelo código resultante da transação de negociação, derivado do campo retcode da estrutura do pedido de negociação MqlTradeResult.

O Consultor Especialista está pronto para começar com os testes! Não há diferenças sérias visíveis no teste do Consultor Especialista de múltiplas moedas correntes e de seu colega Consultor Especialista de moeda corrente única.

Determine as configurações a aba "Parâmetros" do verificador de estratégia:

Figura 1. Aba

Figura 1. Aba "Configurações" do verificador de estratégia

Se necessário, ajuste os valores dos parâmetros de entrada na aba "parâmetros de entrada:

Figura 2. Aba

Figura 2. Aba "Parâmetros" do verificador de estratégia

e então clique no botão "Iniciar" no verificador de estratégia na aba "Configurações":

Figura 3. Executando o teste do Consultor Especialista

Figura 3. Executando o teste do Consultor Especialista

O tempo que passa do primeiro teste do Consultor Especialista pode acabar por ser muito significante, devido ao carregamento do histórico para todos os doze símbolos. Após completar o teste no verificador de estratégia, abra a aba "Resultados":

Figura 4. Resultados do teste

Figura 4. Resultados do teste

e faça uma análise dos dados utilizando conteúdos da aba "Gráfico":

Figura 5. Gráfico de dinâmica de balanço e equidade

Figura 5. Gráfico de dinâmica de balanço e equidade

e "Diário"

Figura 6. Diário do verificador de estratégia

Figura 6. Diário do verificador de estratégia

Muito naturalmente, a essência das entradas e saídas do algoritmo do mercado desse Consultor Especialista é muito simples, e seria ingênuo esperar resultados muito significantes, quando usando os primeiros parâmetros aleatórios. Mas o nosso objetivo aqui é demonstrar a ideia fundamental de construir um Consultor Especialista de múltiplas moedas correntes da forma mais simples possível.

Com a otimização desse Consultor Especialista algumas inconveniências podem surgir devido a muitos parâmetros de entrada. O algoritmo de otimização genérico exige uma quantidade muito menor desses parâmetros, então o Consultor Especialista deve ser otimizado em cada chip individualmente, desabilitando os chips restantes de parâmetros de entrada TradeN.

Agora quando a essência da abordagem tiver sido esboçada, você pode começar a trabalhar com o algoritmo mais interessante de tomada de decisões para o robô de múltiplas moedas correntes.


2. Ressonâncias dos mercados financeiros e suas aplicações em sistemas de negociação

A ideia de levar em conta as correlações entre os ativos financeiros diferentes, em geral, não é nova, e poderia ser interessante implementar o algorítimo, que poderia ser baseado precisamente na análise de tais tendências. Neste artigo, implementarei uma moeda múltipla automatizada, baseado no artigo de Vasily Yakimkin "Resonances - a New Class of Technical Indicators" publicado na revista "Currency Speculator" (em Russo) 04, 05, 2001.

A essência dessa abordagem em poucas palavras se parece com o seguinte. Por exemplo, para pesquisar sobre EUR / USD, nós utilizamos não somente os resultados de alguns indicadores do ativo financeiro, mas também os resultados do mesmo indicador relacionado a ativos EUR/USD - EUR/JPY e USD/JPY. é melhor utilizar o indicador, os valores do qual são normalizados na mesma faixa de mudanças pela simplicidade e facilidade de medições e cálculos.

Considerando essas exigências, um bem adequado para esse clássico é o indicador estocástico. Apesar de, na realidade, não haver diferença no uso de outros indicadores. Como a tendência de direção, consideraremos o sinal de diferença entre o valor de estocástico Stoh e sua linha de sinal Sign .

Figura 7. Determinando a direção da tendência

Figura 7. Determinando a direção da tendência

Para o símbolo variável dStoh há uma tabela inteira de possíveis combinações e suas interpretações para a direção da tendência atual:

Figura 8. Combinações do símbolo variável dStoh e a direção da tendência

Figura 8. Combinações do símbolo variável dStoh e a direção da tendência

Em um caso onde dois sinais dos ativos EUR / JPY e USD / JPY possuem valores opostos, devemos determinar a soma deles e se essa soma é maior do que zero, considerar ambos os sinais como positivos, caso contrário - como negativos.

Assim, para abrir Longs, usamos a situação quando a tendência está crescendo e para sair, usamos a tendência para baixo, ou uma tendência quando os sinais do indicador do ativo principal EUR / USD são negativos. Também, saia da compra se o ativo principal não possui sinais, e se a soma da variável dStoh para os ativos restantes for menor que zero. Para as vendas, tudo é absolutamente análogo, apenas a situação é oposta.

A solução mais racional seria colocar toda uma parte analítica do Consultor Especialista no indicador de múltiplas moedas correntes, e para o Consultor Especialista dos amortecedores indicadores, tomar apenas os sinais prontos para controle de negociação. A versão desse tipo de indicador é apresentada pelo indicador MultiStochastic.mq5, fornecendo análises visuais das condições do mercado.

Figura 9. Indicador multi-estocástico

Figura 9. Indicador multi-estocástico

A barra verde sinaliza a abertura e retenção de compras, e as barras vermelhas - de vendas, respectivamente. Pontos em rosa e verde claro na extremidade superior do gráfico representam os sinais para sair das posições de compra e venda.

Esse indicador pode ser diretamente utilizado para receber sinais no Consultor Especialista, mas ainda seria melhor facilitar seu trabalho e remover todos os amortecedores e elementos desnecessários de visualização, deixando apenas o que está diretamente envolvido no fornecimento de sinais de negociação. Isso é precisamente o que foi feito no indicador MultiStochastic_Exp.mq5.

Nesse Consultor Especialista, eu negociei com apenas três chips, então o código da função OnTick() se tornou excessivamente simples:

void OnTick()
  {
//--- declare variables arrays for trade signals
  static bool UpSignal[], DnSignal[], UpStop[], DnStop[];
  
//--- get trade signals
  TradeSignalCounter(0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop);
  TradeSignalCounter(2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop);
                             
//--- perform trade operations
   TradePerformer( 0, SymbolA0,  Trade0,  StopLoss0,  0,  Lots0,  Slippage0,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 1, SymbolA1,  Trade1,  StopLoss1,  0,  Lots1,  Slippage1,  UpSignal, DnSignal, UpStop, DnStop);
   TradePerformer( 2, SymbolA2,  Trade2,  StopLoss2,  0,  Lots2,  Slippage2,  UpSignal, DnSignal, UpStop, DnStop);
//---
  }

No entanto, o código da função TradeSignalCounter() é um pouco mais complexo: O fato é que o indicador de moeda múltipla funciona diretamente com três séries de tempo de diferentes ativos financeiros, e assim, implementamos uma verificação mais adequada das barras para a adequação do número mínimo delas em uma das três séries de tempo, usando a função Rates_Total().

Adicionalmente, uma verificação adicional da sincronização da série de tempo feita é realizada, utilizando a função SynchroCheck(), para garantir a precisão da determinação do momento quando uma mudança de barra ocorre e toda a série de tempo simultaneamente.

bool TradeSignalCounter(int Number,
                        bool Trade,
                        int Kperiod,
                        int Dperiod,
                        int slowing,
                        ENUM_MA_METHOD ma_method,
                        ENUM_STO_PRICE price_,
                        string SymbolA,
                        string SymbolB,
                        string SymbolC,
                        bool &UpSignal[],
                        bool &DnSignal[],
                        bool &UpStop[],
                        bool &DnStop[])
  {
//--- check if trade is prohibited
   if(!Trade)return(true);
//--- declare variable to store sizes of variables arrays
   static int Size_=0;
//--- declare arrays to store handles of indicators as static variables
   static int Handle[];
   static int Recount[],MinBars[];
//---
   double dUpSignal_[1],dDnSignal_[1],dUpStop_[1],dDnStop_[1];
//--- change size of variables arrays
   if(Number+1>Size_)
     {
      uint size=Number+1;
      //----
      if(ArrayResize(Handle,size)==-1
         || ArrayResize(Recount,size)==-1
         || ArrayResize(UpSignal, size) == -1
         || ArrayResize(DnSignal, size) == -1
         || ArrayResize(UpStop, size) == -1
         || ArrayResize(DnStop, size) == -1
         || ArrayResize(MinBars,size) == -1)
        {
         string word="";
         StringConcatenate(word,"TradeSignalCounter( ",Number,
                           " ): Erro!!! Unable to change sizes of variables arrays!!!");
         int error=GetLastError();
         ResetLastError();
         //---
         if(error>4000)
           {
            StringConcatenate(word,"TradeSignalCounter( ",Number," ): Error code ",error);
            Print(word);
           }
         Size_=-2;
         return(false);
        }

      Size_=int(size);
      Recount[Number] = false;
      MinBars[Number] = Kperiod + Dperiod + slowing;

      //--- get indicator's handle
      Handle[Number]=iCustom(SymbolA,0,"MultiStochastic_Exp",
                             Kperiod,Dperiod,slowing,ma_method,price_,
                             SymbolA,SymbolB,SymbolC);
     }
//--- check if number of bars is sufficient for calculation 
   if(Rates_Total(SymbolA,SymbolB,SymbolC)<MinBars[Number])return(true);
//--- check timeseries synchronization
   if(!SynchroCheck(SymbolA,SymbolB,SymbolC))return(true);
//--- get trade signals 
   if(IsNewBar(Number,SymbolA,0) || Recount[Number])
     {
      DnSignal[Number] = false;
      UpSignal[Number] = false;
      DnStop  [Number] = false;
      UpStop  [Number] = false;

      //--- using indicators' handles, copy values of indicator's 
      //--- buffers into static arrays, specially prepared for this purpose
      if(CopyBuffer(Handle[Number], 1, 1, 1, dDnSignal_) < 0){Recount[Number] = true; return(false);}
      if(CopyBuffer(Handle[Number], 2, 1, 1, dUpSignal_) < 0){Recount[Number] = true; return(false);}
      if(CopyBuffer(Handle[Number], 3, 1, 1, dDnStop_  ) < 0){Recount[Number] = true; return(false);}
      if(CopyBuffer(Handle[Number], 4, 1, 1, dUpStop_  ) < 0){Recount[Number] = true; return(false);}

      //--- convert obtained values into values of logic variables of trade commands
      if(dDnSignal_[0] == 300)DnSignal[Number] = true;
      if(dUpSignal_[0] == 300)UpSignal[Number] = true;
      if(dDnStop_  [0] == 300)DnStop  [Number] = true;
      if(dUpStop_  [0] == 300)UpStop  [Number] = true;

      //--- all copy operations from indicator's buffers completed successfully
      //--- unnecessary to return into this block until next bar change
      Recount[Number]=false;
     }
//----+
   return(true);
  }

Não há outras diferenças radicais ideológicas do código desse Consultor Especialista (Exp_ResonanceHunter.mq5) devido ao fato de que ele é compilado na base dos mesmos componentes funcionais. Então, não acho que seja necessário gastar mais tempo em sua estrutura interna.


Conclusão

Em minha opinião, o código do Consultor Especialista de múltiplas moedas correntes no MQL5 é absolutamente análogo ao código de um Consultor Especialista normal.