Creación de un Asesor Experto multisistema y multidivisa

Maxim Khrolenko | 7 mayo, 2014

Introducción

Creo que hay bastantes traders que operan con más de un símbolo de trading y utilizan múltiples estrategias. Este enfoque no sólo le permite aumentar potencialmente su beneficio, sino que también minimiza el riesgo de disminución substancial sobre la gestión eficiente del dinero. Al crear a un Asesor Experto, el primer paso para comprobar la eficiencia de la estrategia del programa es la optimización para determinar los mejores parámetros de entrada.

Con los valores de los parámetros identificados, los Asesores Expertos estarían técnicamente listos para operar. Sin embargo eso dejaría una pregunta importante sin contestar; ¿cuáles serían los resultados de las pruebas si un trader pudiera poner todas sus estrategias juntas en un único Asesor Experto? La idea de que se pueda superponer la disminución en varios símbolos o estrategias en algún punto y dar lugar a una desastrosa disminución total o incluso marginal puede llevar a veces a una sorpresa desagradable.

Este artículo representa una introducción a un concepto de creación de un Asesor Experto multisistema y multidivisa que nos permitirá encontrar una respuesta a esta importante pregunta.


1. Estructura del Asesor Experto

En términos generales, la estructura del Asesor Experto es la siguiente:

Fig. 1. Estructura de un Asesor Experto multisistema y multidivisa

Fig. 1. Estructura de un Asesor Experto multisistema y multidivisa

Como puede ver, el programa está basado en un bucle for. Cada estrategia está ordenada en un bucle donde cada repetición es responsable de la operación de trading de cada símbolo separadamente. Aquí, puede organizar en bucles un número ilimitado de estrategias. Lo importante es que su ordenador tenga recursos suficientes para "procesar" este programa.

Debería tener presente que sólo puede haber una posición para cada símbolo que opera en MetaTrader 5. Tal posición representa la suma de muchas Compras y Ventas ejecutadas anteriormente. Por lo tanto, el resultado de la prueba multiestrategia para un símbolo no será idéntico a la suma de probar resultados separados de las mismas estrategias para el mismo símbolo.

Para un análisis más detallado de la estructura del Asesor Experto tomaremos 2 estrategias, cada una de ellas opera con dos símbolos:

Estrategia A:

Estrategia В:

Para no depender de los nuevos ticks de un símbolo en el que se probará u operará el Asesor Experto, es conveniente utilizar la función OnTimer() para operar en el modo  multidivisa.

Para ello, al inicializar el Asesor Experto especificamos la frecuencia de generación de eventos para la llamada del programa del cálculo mediante la función EventSetTimer(), y en cuanto a la desinicialización utilizamos la función EventKillTimer() para indicar al terminal que detenga la generación de eventos:

// Include standard libraries
// Create external parameters
// Create arrays, variables, indicator handles, etc.

//--- Initialization of the Expert Advisor
int OnInit()
  {
   //--- Set event generation frequency
   EventSetTimer(1); // 1 second
   // ...
   return(0);
  }
void OnTimer()
  {
   // ...
  }
//--- Deinitialization of the Expert Advisor
void OnDeinit(const int reason)
  {
   //--- Stop event generation
   EventKillTimer();
   // ...
  }

En vez de EventSetTimer(), también puede utilizar EventSetMillisecondTimer(), donde la frecuencia se establece con una precisión de milisegundos pero no se debe abusar de ella mediante llamadas demasiado frecuentes al programa de cálculo.

Para el acceso a la configuración de la cuenta, posición y símbolo, así como las funciones de trading, utilizaremos CAccountInfo, CPositionInfo, CSymbolInfo y las clases CTrade , respectivamente. Vamos a incluirlas en el Asesor Experto:

//--- Include standard libraries
#include <Trade\AccountInfo.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\Trade.mqh>

Ya que el Asesor Experto está basado en bucles, deberemos crear matrices para sus parámetros externos. En primer lugar, vamos a crear constantes iguales al número de símbolos para cada estrategia:

//--- Number of traded symbols for each strategy
#define Strategy_A 2
#define Strategy_B 2

Luego creamos parámetros externos. Utilizando las constantes, determinamos el tamaño de las matrices que se van a copiar. Además, creamos controladores del indicador y otras variables globales.

A continuación, se muestra un ejemplo para un símbolo de la estrategia A:

//------------------- External parameters of strategy A
input string          Data_for_Strategy_A="Strategy A -----------------------";
//--- Symbol 0
input string          Symbol_A0      = "EURUSD";   // Symbol
input bool            IsTrade_A0     = true;       // Permission for trading
//--- Bollinger Bands (BB) parameters
input ENUM_TIMEFRAMES Period_A0      = PERIOD_H1;  // ВВ period
input uint            BBPeriod_A0    = 20;         // Period for calculation of the moving average of BB
input int             BBShift_A0     = 0;          // Horizontal shift of ВВ
input double          BBDeviation_A0 = 2.0;        // Number of standard deviations of BB
//...
//--- General parameters of strategy A
input double          DealOfFreeMargin_A = 1.0;    // Percent of free margin for a deal
input uint            MagicNumber_A      = 555;    // Magic number
input uint            Slippage_A         = 100;    // Permissible slippage for a deal
//...
//------------- Set variables of strategy A -----
//--- Arrays for external parameters
string          Symbol_A[Strategy_A];
bool            IsTrade_A[Strategy_A];
ENUM_TIMEFRAMES Period_A[Strategy_A];
int             BBPeriod_A[Strategy_A];
int             BBShift_A[Strategy_A];
double          BBDeviation_A[Strategy_A];
//--- Arrays for global variables
double          MinLot_A[Strategy_A],MaxLot_A[Strategy_A];
double          Point_A[Strategy_A],ContractSize_A[Strategy_A];
uint            DealNumber_A[Strategy_A];
datetime        Locked_bar_time_A[Strategy_A],time_arr_A[];
//--- Indicator handles
int             BB_handle_high_A[Strategy_A];
int             BB_handle_low_A[Strategy_A];
//--- Arrays for indicator values
double          BB_upper_band_high[],BB_lower_band_high[];
double          BB_upper_band_low[],BB_lower_band_low[];
//--- Class
CTrade          Trade_A;
//...
//--- Set global variables for all strategies
long            Leverage;
//--- Classes
CAccountInfo    AccountInfo;
CPositionInfo   PositionInfo;
CSymbolInfo     SymbolInfo;

Para tener la posibilidad de deshabilitar el trading para un símbolo determinado, hemos creado una variable booleana IsTrade_A0 que se colocará al principio de los bucles.


2. Inicialización del Asesor Experto

Primero, consigamos los valores necesarios para todas las estrategias, por ejemplo, apalancamiento. Ya que el apalancamiento se aplica a la cuenta de trading y no tiene nada que ver con una estrategia o un símbolo, no hay necesidad de copiar su valor a las matrices:

//--- Get the leverage for the account
   Leverage=AccountInfo.Leverage();

Después copiamos las variables externas a las matrices:

//--- Copy external variables to arrays
   Symbol_A[0]     =Symbol_A0;
   IsTrade_A[0]    =IsTrade_A0;
   Period_A[0]     =Period_A0;
   BBPeriod_A[0]   =(int)BBPeriod_A0;
   BBShift_A[0]    =BBShift_A0;
   BBDeviation_A[0]=BBDeviation_A0;

Si se define cualquier parámetro externo por un tipo que requerirá una conversión a otro tipo, se puede hacer de una manera más conveniente al copiar a las matrices.

En este caso, podemos ver que BBPeriod_A0 fue creada como uint para impedir al usuario poner un valor negativo. Aquí, la convertimos a int y la copiamos a la matriz que también fue creada como int. De otro modo, el compilador dará un aviso si intentamos insertar el parámetro de tipo uint en el controlador del indicador.

Vamos a ver, además, si el símbolo de la operación de trading está disponible en la Observación del Mercado y si ha sido utilizado más de una vez dentro de una estrategia:

//--- Check for the symbol in the Market Watch
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      if(IsSymbolInMarketWatch(Symbol_A[i])==false)
        {
         Print(Symbol_A[i]," could not be found on the server!");
         ExpertRemove();
        }
     }

//--- Check whether the symbol is used more than once
   if(Strategy_A>1)
     {
      for(int i=0; i<Strategy_A-1; i++)
        {
         if(IsTrade_A[i]==false) continue;
         for(int j=i+1; j<Strategy_A; j++)
           {
            if(IsTrade_A[j]==false) continue;
            if(Symbol_A[i]==Symbol_A[j])
              {
               Print(Symbol_A[i]," is used more than once!");
               ExpertRemove();
              }
           }
        }
     }
//--- The IsSymbolInMarketWatch() function
bool IsSymbolInMarketWatch(string f_Symbol)
  {
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      if(f_Symbol==SymbolName(s,false))
         return(true);
     }
   return(false);
  }

Si los símbolos se seleccionaron correctamente, se comprueban los errores en los parámetros de entrada para cada uno de ellos, se crean los controladores del indicador, se consiguen los datos necesarios para el cálculo del lote y, si es necesario, se hacen otras cosas tal como se define por la estrategia correspondiente.

Aplicaremos las acciones ya mencionadas dentro de un bucle.

//--- General actions
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      //--- Check for errors in input parameters
      //...
      //--- Set indicator handles
      BB_handle_high_A[i]=iBands(Symbol_A[i],Period_A[i],BBPeriod_A[i],BBShift_A[i],BBDeviation_A[i],
                                 PRICE_HIGH);
      if(BB_handle_high_A[i]<0)
        {
         Print("Failed to create a handle for Bollinger Bands based on High prices for ",Symbol_A[i]," . Handle=",INVALID_HANDLE,
               "\n Error=",GetLastError());
         ExpertRemove();
        }
      //...
      //--- Calculate data for the Lot
      //--- set the name of the symbol for which the information will be obtained
      SymbolInfo.Name(Symbol_A[i]);
      //--- minimum and maximum volume size in trading operations
      MinLot_A[i]=SymbolInfo.LotsMin();
      MaxLot_A[i]=SymbolInfo.LotsMax();
      //--- point value
      Point_A[i]=SymbolInfo.Point();
      //--- contract size
      ContractSize_A[i]=SymbolInfo.ContractSize();

      //--- Set some additional parameters
     }

Entonces, ponemos los parámetros para las operaciones de trading de la estrategia A usando el objeto Trade_A de la clase CTrade.

//--- Set parameters for trading operations
//--- set the magic number
   Trade_A.SetExpertMagicNumber(MagicNumber_A);
//--- set the permissible slippage in points upon deal execution
   Trade_A.SetDeviationInPoints(Slippage_A);
//--- order filling mode, use the mode that is allowed by the server
   Trade_A.SetTypeFilling(ORDER_FILLING_RETURN);
//--- logging mode, it is advisable not to call this method as the class will set the optimal mode by itself
   Trade_A.LogLevel(1);
//--- the function to be used for trading: true - OrderSendAsync(), false - OrderSend().
   Trade_A.SetAsyncMode(true);

Se repite el mismo procedimiento para cada estrategia, es decir,

  1. Copiar las variables externas a las matrices;
  2. Comprobar si los símbolos están seleccionados correctamente;
  3. Comprobar los errores, establecer los controladores del indicador, calcular los datos para el lote y para todo lo necesario para una estrategia determinada;
  4. Ajustar los parámetros para las operaciones de trading.

Por último, sería bueno comprobar si se usa una y otra vez el mismo símbolo en varias estrategias (se muestra a continuación un ejemplo para dos estrategias):

//--- Check whether one and the same symbol is used in several strategies
   for(int i=0; i<Strategy_A; i++)
     {
      if(IsTrade_A[i]==false) continue;
      for(int j=0; j<Strategy_B; j++)
        {
         if(IsTrade_B[j]==false) continue;
         if(Symbol_A[i]==Symbol_B[j])
           {
            Print(Symbol_A[i]," is used in several strategies!");
            ExpertRemove();
           }
        }
     }


3. Bucles de trading "for"

El marco (framework) de los bucles for dentro de la función OnTimer() es así:

void OnTimer()
  {
//--- Check if the terminal is connected to the trade server
   if(TerminalInfoInteger(TERMINAL_CONNECTED)==false) return;

//--- Section A: Main loop of the FOR operator for strategy A -----------
   for(int A=0; A<Strategy_A; A++)
     {
      //--- A.1: Check whether the symbol is allowed to be traded
      if(IsTrade_A[A]==false)
         continue; // terminate the current FOR iteration

     }

//--- Section В: Main loop of the FOR operator for strategy В -----------
   for(int B=0; B<Strategy_B; B++)
     {
      //--- B.1: Check whether the symbol is allowed to be traded
      if(IsTrade_B[B]==false)
         continue; // terminate the current FOR iteration

     }
  }


Si un símbolo único de un Asesor Experto basado en una estrategia única tiene una condición por la cual todos los cálculos subsiguientes deben detenerse, utilizamos el operador return. En nuestro caso, necesitamos terminar la repetición actual y continuar con el siguiente símbolo de repetición. Para esto, es mejor utilizar el operador continue.

Si quiere mejorar su Asesor Experto multiestrategia agregando una estrategia con un bucle for que contiene una condición para la finalización de todos los cálculos subsiguientes, puede utilizar el siguiente modelo:

//--- Section N: Main loop of the FOR operator for strategy N -----------
for(int N=0; N<Strategy_N; N++)
  {

   //...
   bool IsInterrupt=false;
   for(int i=0; i<Number; i++)
     {
      if(...) // terminate all calculations
        {
         IsInterrupt=true;
         break;
        }
     }
   if(IsInterrupt=true)
      continue; // terminate the current FOR iteration
   //...

  }

Después de crear la infraestructura para los bucles for, simplemente insertamos los códigos de otros Asesores Expertos y luego sustituimos algunas variables por los elementos de la matriz.

Por ejemplo, cambiamos la variable predefinida Symbol por Symbol_A[i] o Point por Point_A[i]. Los valores de estas variables son típicos del símbolo dado y por lo tanto se copian en las matrices al inicio.

Por ejemplo, vamos a averiguar el valor del indicador:

 //--- A.3: Lower band of BB calculated based on High prices
 if(CopyBuffer(BB_handle_high_A[A],LOWER_BAND,BBShift_A[A],1,BB_lower_band_high)<=0)
    continue; // terminate the current FOR iteration
 ArraySetAsSeries(BB_lower_band_high,true);

Para llevar a cabo el cierre de una posición de compra, vamos a escribir el siguiente código:

 //--- A.7.1: Calculate the current Ask and Bid prices
 SymbolInfo.Name(Symbol_A[A]);
 SymbolInfo.RefreshRates();
 double Ask_price=SymbolInfo.Ask();
 double Bid_price=SymbolInfo.Bid();

 if(PositionSelect(Symbol_A[A]))
   {
    //--- A.7.2: Closing a BUY position
    if(PositionInfo.PositionType()==POSITION_TYPE_BUY)
      {
       if(Bid_price>=BB_lower_band_high[0] || DealNumber_A[A]==0)
         {
          if(!Trade_A.PositionClose(Symbol_A[A]))
            {
             Print("Failed to close the Buy ",Symbol_A[A]," position. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
          else
            {
             Print("The Buy ",Symbol_A[A]," position closed successfully. Code=",Trade_A.ResultRetcode(),
                   " (",Trade_A.ResultRetcodeDescription(),")");
             continue; // terminate the current FOR iteration
            }
         }
      }

    //...
   }

Apertura de una posición de Compra:

 //--- A.9.1: for a Buy
 if(Ask_price<=BB_lower_band_low[0])
   {
    //...

    //--- A.9.1.3: Execute a deal
    if(!Trade_A.Buy(OrderLot,Symbol_A[A]))
      {
       Print("The Buy ",Symbol_A[A]," has been unsuccessful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
    else
      {
       Print("The Buy ",Symbol_A[A]," has been successful. Code=",Trade_A.ResultRetcode(),
             " (",Trade_A.ResultRetcodeDescription(),")");
       continue; // terminate the current FOR iteration
      }
   }

Recuerde que debe finalizar la generación de eventos del temporizador y borrar el controlador del indicador en la desinicialización.


4. Resultados de la prueba

Una vez listo el Asesor Experto, probamos cada estrategia y cada símbolo por separado y comparamos los resultados de la prueba con los obtenidos en el modo de prueba del trading de todas las estrategias y los símbolos simultáneamente.

Se asume que el usuario ya ha identificado los valores óptimos de los parámetros de entrada.


A continuación se muestra la configuración del Probador de Estrategias:

Fig. 2. Configuración del Probador de Estrategias

Fig. 2. Configuración del Probador de Estrategias

Resultados para la estrategia A, EURUSD:

Fig. 3. Resultados de la prueba para la estrategia A, EURUSD

Fig. 3. Resultados de la prueba para la estrategia A, EURUSD

Resultados para la estrategia A, GBPUSD:


Fig. 4. Resultados de la prueba para la estrategia A, GBPUSD

Resultados para la estrategia B, AUDUSD:

Fig. 5. Resultados de la prueba para la estrategia В, AUDUSD

Fig. 5. Resultados de la prueba para la estrategia В, AUDUSD

Resultados para la estrategia B, EURJPY:

Fig. 6. Resultados de la prueba para la estrategia В, AUDUSD

Fig. 6. Resultados de la prueba para la estrategia В, AUDUSD

Resultados de la prueba para todas las estrategias y símbolos:

Fig. 7. Resultados de la Prueba para todas las estrategias y símbolos

Fig. 7. Resultados de la Prueba para todas las estrategias y símbolos


Conclusión

Como resultado, tenemos una estructura conveniente y sencilla del Asesor Experto multisistema y multidivisa en el cual puede implementar virtualmente cualquiera de sus estrategias.

Este Asesor Experto le permite hacer una mejor valoración de la eficiencia del trading utilizando todas sus estrategias. También puede resultar útil en caso de que sólo se permita un Asesor Experto para trabajar en una cuenta determinada. Se adjunta el código fuente del Asesor Experto al artículo para facilitar el estudio de las informaciones antes mencionadas.