Creación de un Expert Advisor que opera con varios instrumentos

Nikolay Kositsin | 25 febrero, 2014


Introducción

El aspecto técnico del código de programa de un Expert Advisor para una divisa, ejecutado en una ventana única, le permite hacer trading con distintos activos financieros al mismo tiempo. En general, esto no era un problema en MQL4. Pero con la aparición del terminal MetaTrader 5, los operadores han podido por fin llevar a cabo un análisis completo del funcionamiento de dichos sistemas automatizados, mediante los probadores de estrategias.

Por lo tanto, los sistemas automatizados multidivisa son ahora más populares que nunca, y podemos pronosticar un aumento de interés en la implementación de dichos sistemas de trading. Pero el principal problema para la implementación de dichos robots radica en la expansión del código del programa que se debe a las dimensiones, como mucho, en una progresión aritmética, y nos es fácil para un programador principiante.

Vamos a escribir en este artículo un Expert Advisor multidivisa sencillo, en el cual se minimizan los errores de estructura, si los hay.


1. Implementación de un sistema sencillo de seguimiento de tendencia

De hecho, vamos a empezar con un sistema de trading muy sencillo, siguiendo la tendencia mediante el indicador técnico integrado en el terminal Promedio móvil exponencial triple (Triple Exponential Moving Average). Es un algoritmo muy sencillo, no necesita comentarios especiales, y que vamos a incluir ahora en el código del programa.

Pero primero, me gustaría hacer las conclusiones más generales sobre el Expert Advisor. Esto tiene sentido para comenzar con el bloque de parámetros de entrada del Expert Advisor, declarados de manera general.

Por lo tanto, lo primero que tenemos que hacer es elegir los activos financieros que vamos a procesar. Esto se puede hacer mediante las variables de la línea de entrada, en la cual podemos almacenar los valores de los activos. Estaría bien ahora disponer de un interruptor para prohibir el trading para cada activo financiero, lo que permitirá desactivar las operaciones por activos.

Por supuesto, cada activo tiene que ir asociado a sus propios parámetros de trading como Stop Loss, Take Profit, el volumen de la posición abierta y el slippage. Y por razones obvias, los parámetros de entrada de cada indicador Promedio móvil exponencial triple para cada variedad de trading tienen que ser independientes.

A continuación, tenemos un bloque final de variables de entrada para una sola variedad, hecho de acuerdo con estos argumentos. Lo único que diferencia los bloques restantes es el número en los parámetros de entrada del Expert Advisor. Para este ejemplo me he limitado a doce activos financieros, aunque en el caso ideal no hay ningún límite de número de bloques en el programa.

¡Tan solo tenemos que hacer trading con algo! Y lo más importante, nuestro ordenador tiene que disponer de recursos suficientes para resolver este 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;

Ahora que hemos averiguado las variables a nivel general, podemos proceder a la implementación del código dentro de la función OnTick(). La opción más lógica en este caso sería separar el algoritmo de recepción de señales de trading y la parte actual del trading del Expert Advisor en dos funciones personalizadas.

Y puesto que el Expert Advisor trabaja con doce activos financieros al mismo tiempo, tienen que haber también doce llamadas a estas funciones dentro del bloque OnTick(). 

Por supuesto, el primer parámetro de entrada de estas funciones debe ser un número único, que corresponde a estos activos de trading. Y por razones obvias, el segundo parámetro de entrada sería la línea del nombre del activo financiero del trading.

En cuanto al tercer parámetro, estableceremos una variable lógica para resolver la operación de trading. A continuación, para el algoritmo que determina las señales de trading, seguiremos las señales del indicador de entrada, y para la función de trading, la distancia hasta las órdenes pendientes, el volumen de la posición y el slippage (slippage admisible del precio de una posición abierta).

Para transferir las señales de trading de una función a otra, hay que establecer matrices estáticas como parámetros de la función, para conseguir sus valores de referencia. Esta es la versión final del código propuesto para la función 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); 
//---
  }

En la función TradeSignalCounter(), hace falta obtener el identificador del indicador técnico Promedio móvil exponencial triple solo una vez al principio de cada variedad, y luego a cada cambio de barra para calcular las señales de trading.

Este esquema relativamente sencillo con la implementación en el código está empezando a rebosar a través de pequeños detalles.

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);
  }

En este sentido, el código de la función TradePerformer() es muy sencillo:

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);
  }

Pero esto se debe solo al empaquetado de los comandos reales para el rendimiento de la operaciones de trading en cuatro funciones adicionales:
BuyPositionClose();
SellPositionClose();
BuyPositionOpen();
SellPositionOpen();

Las cuatro funciones trabajan de forma similar, por lo que podemos limitarnos a examinar una de ellas solo:

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);
  }

Básicamente, este es más o menos todo el Expert Advisor multidivisa (Exp_TEMA.mq5)!

Además de las funciones vistas antes, contiene dos funciones adicionales de usuario:

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

La primera de estas funciones devuelve el valor true en el momento de cambio de barra, basándose en el símbolo y período de tiempo seleccionados, y la segunda, devuelve la línea del código resultante de la transacción, resultante del campo retcode de la estructura de petición de trading MqlTradeResult

El Expert Advisor está listo, es el momento de comenzar las pruebas! No hay una diferencia significativa entre las pruebas del Expert Advisor multidivisa y el Expert Advisor de una sola divisa.

Definir las configuraciones en la pestaña "Configuración" en el Probador de estrategias:

<img src="http://p.mql5.com/data/2/105/fig1_en.png" title="Figura 1. Pestaña "Configuración" (Settings) del Probador de estrategias" alt="Figura 1. Pestaña "Configuración" (Settings) del Probador de estrategias" height="319" width="743">

Figura 1. Pestaña "Configuración" (Settings) del Probador de estrategias

Si es necesario, ajustar los valores de los parámetros de entrada en la pestaña "Parámetros de entrada":

<img src="http://p.mql5.com/data/2/105/fig2_en.png" title="Figura 2. Pestaña "Parámetros de entrada" (Inputs) del Probador de estrategias" alt="Figura 2. Pestaña "Parámetros de entrada" (Inputs) del Probador de estrategias" height="262" width="652">

Figura 2. Pestaña "Parámetros de entrada" (Inputs) del Probador de estrategias

y hacer clic en el botón "Empezar" (Start) de la pestaña "Configuración" del Probador de estrategias:

Figura 3. Ejecución de la prueba de un Expert Advisor

Figura 3. Ejecución de la prueba de un Expert Advisor

El tiempo de ejecución de la primera prueba del Expert Advisor puede ser muy largo, debido a la carga de los historiales de los doce símbolos. Después de finalizar la prueba en el probador de estrategias, abrimos la pestaña "Resultados" (Results):

Figura 4. Resultados de la prueba

Figura 4. Resultados de la prueba

y hacemos un análisis de los datos, usando los contenidos de la pestaña "Gráfico" (Chart):

Figura 5. Gráfico de la dinámica del balance y del patrimonio

Figura 5. Gráfico de la dinámica del balance y del patrimonio

y el "Diario" (Journal):

Figura 6. Diario del Probador de estrategias

Figura 6. Diario del Probador de estrategias

Naturalmente, la esencia misma de las entradas y salidas del algoritmo al mercado para este Expert Advisor es muy sencilla, y sería muy ingenuo esperar resultados muy relevantes al utilizar por primera vez unos parámetros aleatorios. Pero nuestro objetivo aquí es demostrar la idea de base para la implementación de un Expert Advisor de la manera más sencilla posible.

Con la optimización pueden surgir algunos inconvenientes con el Expert Advisor, debido a la gran cantidad de parámetros de entrada. El algoritmo genético de optimización requiere una cantidad mucho menor de estos parámetros, para que el Expert Advisor pueda optimizar en cada variedad individualmente, desactivando las variedades restantes de los parámetros de entrada TradeN.

Ahora, que ya se ha descrito la esencia del enfoque, puede empezar a trabajar en un algoritmo de toma de decisión más interesante para el robot multidivisa.


2. Resonancias en los mercados financieros y sus aplicaciones en los sistemas de trading

La idea de tener en cuenta las correlaciones entre los distintos activos financieros, en general, no es nueva, y sería interesante implementar el algoritmo, que se basará justamente en el análisis de dichas tendencias. Voy a implementar en este artículo un sistema automatizado multidivisa, basado en el artículo de Vasily Yakimin "Resonances - a New Class of Technical Indicators" (Resonancias -una nueva clase de indicadores técnicos) publicado en el diario ruso "Currency Speculator" (Especuladores de divisas) del 04/05/2001.

La esencia de este método se resume de la siguiente manera. Por ejemplo, para investigar el par EUR/USD, no usamos únicamente los resultados de algunos indicadores del activo financiero, sino también los resultados del mismo indicador relacionado con el par EUR/USD, EUR/JPY y USD/JPY. Es lo mejor para utilizar el indicador, y sus valores están normalizados en el mismo rango para simplificar y facilitar las mediciones y los cálculos.

Teniendo en cuento estos requisitos, que son muy adecuados para este clásico indicador estocástico. Aunque, en realidad, no hay ninguna diferencia en la utilización de otros indicadores. Para la dirección de la tendencia, vamos a tomar el signo de la diferencia entre el valor del estocástico Stoh y su línea de señal Sign .

Figura 7. Determinación de la dirección de la tendencia

Figura 7. Determinación de la dirección de la tendencia

Para el símbolo de la variable dStoh hay una tabla entera de combinaciones y sus interpretaciones para la dirección de la tendencia actual:

Figura 8. Combinaciones del símbolo de la variable dStoh y la dirección de la tendencia

Figura 8. Combinaciones del símbolo de la variable dStoh y la dirección de la tendencia

En el caso de que dos señales de los activos EUR/JPY y USD/JPY tengan valores opuestos, deberíamos de sumarlos, y si el resultado es positivo, se consideran ambas señales positivas, de los contrario, negativas.

Así pues, para la apertura de posiciones Largas, se usa el caso de la tendencia alcista, y para salir la tendencia bajista, o durante la tendencia con las señales del indicador del activo principal EUR/USD negativas. Además, hay que salir de la posición Larga si el activo principal no tiene señales, o cuando la suma de la variable dStoh de los activos restantes es negativa. Para las cortas, es exactamente igual, salvo que al revés.

La mejor solución sería poner toda la parte analítica del Expert Advisor en el indicador multidivisa, y de los buffers del indicador para el Expert Advisor, coger solamente las señales de control del trading. La versión de este tipo de indicador está en el indicador MultiStochastic.mq5, proporcionando un análisis visual de las condiciones del mercado.

Figura 9. El indicador MultiStochastic

Figura 9. El indicador MultiStochastic

Las barras verdes señalan la apertura y la retención de las posiciones largas, y las barras rojas la cortas, respectivamente. Los puntos de color rosa y verde claro en la parte superior del gráfico representan las señales de salida de las posiciones largas y cortas.

Se puede utilizar este indicador directamente para recibir señales en el Expert Advisor, pero sería mejor facilitar su trabajo y quitar todos los buffers innecesarios y los elementos de visualización, dejando solamente lo que está directamente implicado en suministrar señales de trading. Esto es precisamente lo que se hizo en el indicador MultiStochastic_Exp.mq5.

En este Expert Advisor, he llevado a cabo operaciones de trading con tres variedades solo, así que el código es extremadamente sencillo:

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);
//---
  }

Sin embargo, el código de la función TradeSignalCounter() es un poco más complejo: El hecho de que el indicador multidivisa trabaja directamente con tres series de tiempo de activos distintos, y por lo tanto, implementamos una comprobación más sutil de las barras para adecuar el número mínimo de las mismas en una de las tres series de tiempo, mediante la función Rates_Total().

Además, se hace una comprobación adicional de la sincronización de las series de tiempo, mediante la función Synchro_Check(), para garantizar una precisión al determinar el momento de un cambio simultaneo de la barra en todas las series de tiempo.

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,
                           " ): Error!!! 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);
  }

No hay ninguna otra diferencia de concepto significativa en el código de este Expert Advisor (Exp_ResonanceHunter.mq5) puesto que se compila en base a algunos componentes funcionales. Así que, no creo que sea necesario dedicar más tiempo a su estructura interna.


Conclusión

En mi opinión, el código del Expert Advisor multidivisa en MQL5 es exactamente parecido al código de un Expert Advisor ordinario.