English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Libro de Recetas MQL5: Asesor Experto Multidivisa: un Enfoque Simple, Claro y Rápido

Libro de Recetas MQL5: Asesor Experto Multidivisa: un Enfoque Simple, Claro y Rápido

MetaTrader 5Ejemplos | 28 mayo 2014, 11:29
1 388 0
Anatoli Kazharski
Anatoli Kazharski

Introducción

Este artículo describirá la implementación de un enfoque sencillo y apropiado para un Asesor Experto multidivisa. Esto significa que será posible configurar el Asesor Experto para simular/realizar operaciones de trading bajo condiciones idénticas pero con parámetros diferentes para cada símbolo. Como ejemplo, crearemos un patrón para dos símbolos pero de forma que solo podamos añadir símbolos adicionales, si es necesario, haciendo pequeños cambios en el código.

En MQL5 se puede implementar un patrón multidivisa de varias formas:

  • Podemos usar un patrón allá donde el Asesor Experto se guíe por tiempo, que será así capaz de realizar comprobaciones más precisas en los intervalos de tiempos especificados en la función OnTimer().

  • Alternativamente, al igual que en los asesores expertos presentados en los artículos anteriores de la serie, la comprobación se puede hacer en la función OnTick(), en cuyo caso el Asesor Experto dependerá de ticks para el símbolo en el que está funcionando. De modo que si hay una barra completada en otro símbolo, mientras que no haya un tick para el símbolo actual, el Asesor Experto solo realizará una comprobación cuando haya un nuevo tick en el símbolo actual.

  • Hay otra interesante opción sugerida por el autor Konstantin Gruzdev (Lizar). Empleo modelo de eventos: usando la función OnChartEvent() un Asesor Experto obtiene eventos que se reproducen por los agentes e indicador situados en los gráficos de símbolo involucrados en las simulaciones/operaciones de trading. Los agentes de indicador pueden reproducir eventos de nueva barra y tick de los símbolos a los que están adjuntos. Este tipo de indicador (EventsSpy.mq5) se puede descargar al final del artículo. Lo necesitaremos para la operación del Asesor Experto.


Desarrollo de Asesor Experto

El Asesor Experto presentado en el artículo "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" (“Libro de Recetas MQL5: Usar Indicadores Para Configurar Condiciones de Trading en Asesores Expertos”) servirá como plantilla. Ya he eliminado de él todo lo que tiene que ver con el panel información y también he simplificado las condiciones de apertura de posición tal y como se implementaron en el artículo anterior titulado "MQL5 Cookbook: Developing a Framework for a Trading System Based on the Triple Screen Strategy" (“Libro de Recetas MQL5: Desarrollar un Marco de Trabajo Para un Sistema de Trading Basado en la Estrategia de Triple Pantalla”). Puesto que nuestro objetivo es crear un Asesor Experto para dos símbolos, cada uno de ellos necesitará su propio conjunto de parámetros externos:

//--- External parameters of the Expert Advisor
sinput long   MagicNumber           = 777;      // Magic number
sinput int    Deviation             = 10;       // Slippage
//---
sinput string delimeter_00=""; // --------------------------------
sinput string Symbol_01             = "EURUSD"; // Symbol 1
input  int    IndicatorPeriod_01    = 5;        // |     Indicator period
input  double TakeProfit_01         = 100;      // |     Take Profit
input  double StopLoss_01           = 50;       // |     Stop Loss
input  double TrailingStop_01       = 10;       // |     Trailing Stop
input  bool   Reverse_01            = true;     // |     Position reversal
input  double Lot_01                = 0.1;      // |     Lot
input  double VolumeIncrease_01     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_01 = 10;       // |     Volume increase step
//---
sinput string delimeter_01=""; // --------------------------------
sinput string Symbol_02             = "NZDUSD"; // Symbol 2
input  int    IndicatorPeriod_02    = 5;        // |     Indicator period
input  double TakeProfit_02         = 100;      // |     Take Profit
input  double StopLoss_02           = 50;       // |     Stop Loss
input  double TrailingStop_02       = 10;       // |     Trailing Stop
input  bool   Reverse_02            = true;     // |     Position reversal
input  double Lot_02                = 0.1;      // |     Lot
input  double VolumeIncrease_02     = 0.1;      // |     Position volume increase
input  double VolumeIncreaseStep_02 = 10;       // |     Volume increase step

Los parámetros externos se colocarán en arrays cuyos tamaños dependerán del número de símbolos usados. El número de símbolos usados en el Asesor Experto se determinará por el valor de la constante NUMBER_OF_SYMBOLS que debemos crear al principio del archivo:

//--- Number of traded symbols
#define NUMBER_OF_SYMBOLS 2
//--- Name of the Expert Advisor
#define EXPERT_NAME MQL5InfoString(MQL5_PROGRAM_NAME)

Creemos los arrays que se requerirán para almacenar los parámetros externos:

//--- Arrays for storing external parameters
string Symbols[NUMBER_OF_SYMBOLS];            // Symbol
int    IndicatorPeriod[NUMBER_OF_SYMBOLS];    // Indicator period
double TakeProfit[NUMBER_OF_SYMBOLS];         // Take Profit
double StopLoss[NUMBER_OF_SYMBOLS];           // Stop Loss
double TrailingStop[NUMBER_OF_SYMBOLS];       // Trailing Stop
bool   Reverse[NUMBER_OF_SYMBOLS];            // Position reversal
double Lot[NUMBER_OF_SYMBOLS];                // Lot
double VolumeIncrease[NUMBER_OF_SYMBOLS];     // Position volume increase
double VolumeIncreaseStep[NUMBER_OF_SYMBOLS]; // Volume increase step

Las funciones de inicialización se colocarán en el archivo InitArrays.mqh. Para inicializar el array Symbols[] crearemos la función GetSymbol(). Obtendrá el nombre del símbolo de los parámetros externos, y si este símbolo está disponible en la lista de símbolos en el servidor, se seleccionará en la ventana de Market Watch (Observación de Mercado). De lo contrario, si el símbolo requerido no se puede encontrar en el servidor, la función devolverá una cadena de caracteres vacía, y el diario del Asesor Experto (Journal) se actualizará de la forma correspondiente.

Abajo puede ver el código de la función GetSymbol():

//+------------------------------------------------------------------+
//| Adding the specified symbol to the Market Watch window           |
//+------------------------------------------------------------------+
string GetSymbolByName(string symbol)
  {
   string symbol_name="";   // Symbol name on the server
//--- If an empty string is passed, return the empty string
   if(symbol=="")
      return("");
//--- Iterate over the list of all symbols on the server
   for(int s=0; s<SymbolsTotal(false); s++)
     {
      //--- Get the symbol name
      symbol_name=SymbolName(s,false);
      //--- If the required symbol is available on the server
      if(symbol==symbol_name)
        {
         //--- Select it in the Market Watch window
         SymbolSelect(symbol,true);
         //--- Return the symbol name
         return(symbol);
        }
     }
//--- If the required symbol cannot be found, return the empty string
   Print("The "+symbol+" symbol could not be found on the server!");
   return("");
  }

El array Symbols[] se inicializará en la función GetSymbols():

//+------------------------------------------------------------------+
//| Filling the array of symbols                                     |
//+------------------------------------------------------------------+
void GetSymbols()
  {
   Symbols[0]=GetSymbolByName(Symbol_01);
   Symbols[1]=GetSymbolByName(Symbol_02);
  }

Además, lo implementaremos de manera que un valor vacío en los parámetros externos de un determinado símbolo indicará que el bloque correspondiente no estará involucrado en la simulación/operación de trading. Esto es necesario para poder optimizar los parámetros de cada símbolo por separado, excluyendo completamente el resto a la vez.

Todos los demás arrays de parámetros externos se inicializan de la misma manera. En otras palabras, debemos crear una función separada para cada array. Los códigos de todas estas funciones se dan abajo:

//+------------------------------------------------------------------+
//| Filling the indicator period array                               |
//+------------------------------------------------------------------+
void GetIndicatorPeriod()
  {
   IndicatorPeriod[0]=IndicatorPeriod_01;
   IndicatorPeriod[1]=IndicatorPeriod_02;
  }
//+------------------------------------------------------------------+
//| Filling the Take Profit array                                    |
//+------------------------------------------------------------------+
void GetTakeProfit()
  {
   TakeProfit[0]=TakeProfit_01;
   TakeProfit[1]=TakeProfit_02;
  }
//+------------------------------------------------------------------+
//| Filling the Stop Loss array                                      |
//+------------------------------------------------------------------+
void GetStopLoss()
  {
   StopLoss[0]=StopLoss_01;
   StopLoss[1]=StopLoss_02;
  }
//+------------------------------------------------------------------+
//| Filling the Trailing Stop array                                  |
//+------------------------------------------------------------------+
void GetTrailingStop()
  {
   TrailingStop[0]=TrailingStop_01;
   TrailingStop[1]=TrailingStop_02;
  }
//+------------------------------------------------------------------+
//| Filling the Reverse array                                        |
//+------------------------------------------------------------------+
void GetReverse()
  {
   Reverse[0]=Reverse_01;
   Reverse[1]=Reverse_02;
  }
//+------------------------------------------------------------------+
//| Filling the Lot array                                            |
//+------------------------------------------------------------------+
void GetLot()
  {
   Lot[0]=Lot_01;
   Lot[1]=Lot_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncrease array                                 |
//+------------------------------------------------------------------+
void GetVolumeIncrease()
  {
   VolumeIncrease[0]=VolumeIncrease_01;
   VolumeIncrease[1]=VolumeIncrease_02;
  }
//+------------------------------------------------------------------+
//| Filling the VolumeIncreaseStep array                             |
//+------------------------------------------------------------------+
void GetVolumeIncreaseStep()
  {
   VolumeIncreaseStep[0]=VolumeIncreaseStep_01;
   VolumeIncreaseStep[1]=VolumeIncreaseStep_02;
  }

Ahora crearemos una función que nos ayudará a inicializar convenientemente todos los arrays de parámetros externos a la vez: la función InitializeInputParameters():

//+------------------------------------------------------------------+
//| Initializing external parameter arrays                           |
//+------------------------------------------------------------------+
void InitializeInputParameters()
  {
   GetSymbols();
   GetIndicatorPeriod();
   GetTakeProfit();
   GetStopLoss();
   GetTrailingStop();
   GetReverse();
   GetLot();
   GetVolumeIncrease();
   GetVolumeIncreaseStep();
  }

Después de la inicialización de los arrays de parámetros externos podemos proceder a la parte principal. Algunos procedimientos tales como la obtención de identificadores de indicador, sus valores e información de precios, así como la comprobación de una nueva barra, etc, se llevarán a cabo en bucles consecutivamente para cada símbolo. Esta es la razón por la que los valores de parámetros externos se han ordenado en arrays. De modo que todo se hará en los bucles tal y como se muestra a continuación:

//--- Iterate over all symbols
for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
  {
//--- If trading for this symbol is allowed
   if(Symbols[s]!="")
     {
      //--- The rest of the code
     }
  }

Pero antes de comenzar a modificar las funciones ya existentes y a crear otras nuevas, creemos también arrays que se necesitarán en ese patrón.

Necesitaremos dos arrays para identificadores de indicador:

//--- Array of indicator agent handles
int spy_indicator_handles[NUMBER_OF_SYMBOLS];
//--- Array of signal indicator handles
int signal_indicator_handles[NUMBER_OF_SYMBOLS];

Estos dos arrays se inicializarán primero con valores no válidos:

//+------------------------------------------------------------------+
//| Initializing arrays of indicator handles                         |
//+------------------------------------------------------------------+
void InitializeArrayHandles()
  {
   ArrayInitialize(spy_indicator_handles,INVALID_HANDLE);
   ArrayInitialize(signal_indicator_handles,INVALID_HANDLE);
  }

Ahora se podrá acceder a los arrays de datos de precio y valores de indicador usando estructuras:

//--- Data arrays for checking trading conditions
struct PriceData
  {
   double            value[];
  };
PriceData open[NUMBER_OF_SYMBOLS];      // Opening price of the bar
PriceData high[NUMBER_OF_SYMBOLS];      // High price of the bar
PriceData low[NUMBER_OF_SYMBOLS];       // Low price of the bar
PriceData close[NUMBER_OF_SYMBOLS];     // Closing price of the bar
PriceData indicator[NUMBER_OF_SYMBOLS]; // Array of indicator values

Ahora, si necesita obtener el valor de indicador en la última barra completada del primer símbolo de la lista, debe escribir esto:

double indicator_value=indicator[0].value[1];

También necesitamos crear arrays en lugar de las variables que se usaron anteriormente en la función CheckNewBar():

//--- Arrays for getting the opening time of the current bar
struct Datetime
  {
   datetime          time[];
  };
Datetime lastbar_time[NUMBER_OF_SYMBOLS];
//--- Array for checking the new bar for each symbol
datetime new_bar[NUMBER_OF_SYMBOLS];

De este modo, ya hemos ordenado los arrays. Ahora necesitamos modificar un número de funciones según los cambios que hemos hecho arriba. Empezaremos con la función GetIndicatorHandles():

//+------------------------------------------------------------------+
//| Getting indicator handles                                        |
//+------------------------------------------------------------------+
void GetIndicatorHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(signal_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            signal_indicator_handles[s]=iMA(Symbols[s],_Period,IndicatorPeriod[s],0,MODE_SMA,PRICE_CLOSE);
            //--- If the indicator handle could not be obtained
            if(signal_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to get the indicator handle for the symbol "+Symbols[s]+"!");
           }
        }
     }
  }

Ahora, independientemente del número de símbolos que se han usado en la simulación/operación de trading, el código de la función permanecerá igual.

Similarmente crearemos otra función, GetSpyHandles(), para obtener identificadores de agentes de indicador que trasmitirán ticks de otros símbolos. Pero antes de eso, añadiremos una enumeración más de todos los eventos por el símbolo, ENUM_CHART_EVENT_SYMBOL, ordenada como flags en el archivo Enums.mqh:

//+------------------------------------------------------------------+
//| New bar and tick events from all symbols and time frames         |
//+------------------------------------------------------------------+
enum ENUM_CHART_EVENT_SYMBOL
  {
   CHARTEVENT_NO         = 0,          // Events are disabled - 0
   CHARTEVENT_INIT       = 0,          // Initialization event - 0
   //---
   CHARTEVENT_NEWBAR_M1  = 0x00000001, // New bar event on a minute chart (1)
   CHARTEVENT_NEWBAR_M2  = 0x00000002, // New bar event on a 2-minute chart (2)
   CHARTEVENT_NEWBAR_M3  = 0x00000004, // New bar event on a 3-minute chart (4)
   CHARTEVENT_NEWBAR_M4  = 0x00000008, // New bar event on a 4-minute chart (8)
   //---
   CHARTEVENT_NEWBAR_M5  = 0x00000010, // New bar event on a 5-minute chart (16)
   CHARTEVENT_NEWBAR_M6  = 0x00000020, // New bar event on a 6-minute chart (32)
   CHARTEVENT_NEWBAR_M10 = 0x00000040, // New bar event on a 10-minute chart (64)
   CHARTEVENT_NEWBAR_M12 = 0x00000080, // New bar event on a 12-minute chart (128)
   //---
   CHARTEVENT_NEWBAR_M15 = 0x00000100, // New bar event on a 15-minute chart (256)
   CHARTEVENT_NEWBAR_M20 = 0x00000200, // New bar event on a 20-minute chart (512)
   CHARTEVENT_NEWBAR_M30 = 0x00000400, // New bar event on a 30-minute chart (1024)
   CHARTEVENT_NEWBAR_H1  = 0x00000800, // New bar event on an hour chart (2048)
   //---
   CHARTEVENT_NEWBAR_H2  = 0x00001000, // New bar event on a 2-hour chart (4096)
   CHARTEVENT_NEWBAR_H3  = 0x00002000, // New bar event on a 3-hour chart (8192)
   CHARTEVENT_NEWBAR_H4  = 0x00004000, // New bar event on a 4-hour chart (16384)
   CHARTEVENT_NEWBAR_H6  = 0x00008000, // New bar event on a 6-hour chart (32768)
   //---
   CHARTEVENT_NEWBAR_H8  = 0x00010000, // New bar event on a 8-hour chart (65536)
   CHARTEVENT_NEWBAR_H12 = 0x00020000, // New bar event on a 12-hour chart (131072)
   CHARTEVENT_NEWBAR_D1  = 0x00040000, // New bar event on a daily chart (262144)
   CHARTEVENT_NEWBAR_W1  = 0x00080000, // New bar event on a weekly chart (524288)
   //---
   CHARTEVENT_NEWBAR_MN1 = 0x00100000, // New bar event on a monthly chart (1048576)
   CHARTEVENT_TICK       = 0x00200000, // New tick event (2097152)
   //---
   CHARTEVENT_ALL        = 0xFFFFFFFF  // All events are enabled (-1)
  };

Esta enumeración es necesaria para trabajar con el indicador personalizado EventsSpy.mq5 (el archivo está adjunto en el artículo) en la función GetSpyHandles(), cuyo código puede ver abajo:

//+------------------------------------------------------------------+
//| Getting agent handles by the specified symbols                   |
//+------------------------------------------------------------------+
void GetSpyHandles()
  {
//--- Iterate over all symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the handle is yet to be obtained
         if(spy_indicator_handles[s]==INVALID_HANDLE)
           {
            //--- Get the indicator handle
            spy_indicator_handles[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_TICK);
            //--- If the indicator handle could not be obtained
            if(spy_indicator_handles[s]==INVALID_HANDLE)
               Print("Failed to install the agent on "+Symbols[s]+"");
           }
        }
     }
  }

Por favor, tenga en cuenta el último parámetro de la función iCustom(): en este caso, el identificador CHARTEVENT_TICK se ha usado para obtener eventos de tick. Pero, si es necesario, se puede modificar para obtener nuevos eventos de barra. Por ejemplo, si usa la línea tal y como se muestra abajo, el Asesor Experto obtendrá nuevos eventos de barra en intervalos cronológicos de un minuto (M1) y una hora (H1):

handle_event_indicator[s]=iCustom(Symbols[s],_Period,"EventsSpy.ex5",ChartID(),0,CHARTEVENT_NEWBAR_M1|CHARTEVENT_NEWBAR_H1);

Para obtener todos los eventos (eventos de ticks y de barra en todos los intervalos cronológicos), deberá especificar el identificador CHARTEVENT_ALL.

Todos los arrays se inicializan en la función OnInit():

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
  {
//--- Initialization of arrays of external parameters
   InitializeInputParameters();
//--- Initialization of arrays of indicator handles
   InitializeArrayHandles();
//--- Get agent handles
   GetSpyHandles();
//--- Get indicator handles
   GetIndicatorHandles();
//--- Initialize the new bar
   InitializeArrayNewBar();
  }

Como ya se dijo al principio del artículo, los eventos de los agentes de indicador se reciben en la función OnChartEvent(). Abajo puede ver el código que se usará en esta función:

//+------------------------------------------------------------------+
//| Chart events handler                                             |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Event identifier
                  const long &lparam,   // Long type event parameter
                  const double &dparam, // Double type event parameter
                  const string &sparam) // String type event parameter
  {
//--- If this is a custom event
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Exit if trading is not allowed
      if(CheckTradingPermission()>0)
         return;
      //--- If there was a tick event
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Check signals and trade on them
         CheckSignalsAndTrade();
         return;
        }
     }
  }

En la función CheckSignalAndTrade() (la línea resaltada en el código de arriba) tendremos un bucle en el que todo los símbolos se comprobarán alternativamente para el nuevo evento de barra y las nuevas señales de trading, tal y como se implementó antes en la función OnTick():

//+------------------------------------------------------------------+
//| Checking signals and trading based on the new bar event          |
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Iterate over all specified symbols
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- If trading for this symbol is allowed
      if(Symbols[s]!="")
        {
         //--- If the bar is not new, proceed to the next symbol
         if(!CheckNewBar(s))
            continue;
         //--- If there is a new bar
         else
           {
            //--- Get indicator data. If there is no data, proceed to the next symbol
            if(!GetIndicatorsData(s))
               continue;
            //--- Get bar data               
            GetBarsData(s);
            //--- Check the conditions and trade
            TradingBlock(s);
            //--- Trailing Stop
            ModifyTrailingStop(s);
           }
        }
     }
  }

Todas las funciones que se usaron en los parámetros externos, así como el símbolo y los datos del indicador, deben modificarse de acuerdo con los cambios de arriba. Para ello, deberemos añadir el número de símbolo como primer parámetro y sustituir todas las variables y arrays dentro de la función con los nuevos arrays descritos arriba.

Para ilustrar esto, los códigos de las funciones revisadas CheckNewBar(), TradingBlock() y OpenPosition() se facilitan abajo.

El código de la función CheckNewBar():

//+------------------------------------------------------------------+
//| Checking for the new bar                                         |
//+------------------------------------------------------------------+
bool CheckNewBar(int number_symbol)
  {
//--- Get the opening time of the current bar
//    If an error occurred when getting the time, print the relevant message
   if(CopyTime(Symbols[number_symbol],Period(),0,1,lastbar_time[number_symbol].time)==-1)
      Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError()));
//--- If this is a first function call
   if(new_bar[number_symbol]==NULL)
     {
      //--- Set the time
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      Print(__FUNCTION__,": Initialization ["+Symbols[number_symbol]+"][TF: "+TimeframeToString(Period())+"]["
            +TimeToString(lastbar_time[number_symbol].time[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]");
      return(false);
     }
//--- If the time is different
   if(new_bar[number_symbol]!=lastbar_time[number_symbol].time[0])
     {
      //--- Set the time and exit
      new_bar[number_symbol]=lastbar_time[number_symbol].time[0];
      return(true);
     }
//--- If we have reached this line, then the bar is not new, so return false
   return(false);
  }

El código de la función TradingBlock():

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   ENUM_ORDER_TYPE      signal=WRONG_VALUE;                 // Variable for getting a signal
   string               comment="hello :)";                 // Position comment
   double               tp=0.0;                             // Take Profit
   double               sl=0.0;                             // Stop Loss
   double               lot=0.0;                            // Volume for position calculation in case of position reversal
   double               position_open_price=0.0;            // Position opening price
   ENUM_ORDER_TYPE      order_type=WRONG_VALUE;             // Order type for opening a position
   ENUM_POSITION_TYPE   opposite_position_type=WRONG_VALUE; // Opposite position type
//--- Find out if there is a position
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Get the signal
   signal=GetTradingSignal(symbol_number);
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Get symbol properties
   GetSymbolProperties(symbol_number,S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=symb.ask;
         order_type=ORDER_TYPE_BUY;
         opposite_position_type=POSITION_TYPE_SELL;
         break;
         //--- Assign values to variables for a SELL
      case ORDER_TYPE_SELL :
         position_open_price=symb.bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Get the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(symbol_number,order_type);
   tp=CalculateTakeProfit(symbol_number,order_type);
//--- If there is no position
   if(!pos.exists)
     {
      //--- Adjust the volume
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Open a position
      OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
     }
//--- If the position exists
   else
     {
      //--- Get the position type
      GetPositionProperties(symbol_number,P_TYPE);
      //--- If the position is opposite to the signal and the position reversal is enabled
      if(pos.type==opposite_position_type && Reverse[symbol_number])
        {
         //--- Get the position volume
         GetPositionProperties(symbol_number,P_VOLUME);
         //--- Adjust the volume
         lot=pos.volume+CalculateLot(symbol_number,Lot[symbol_number]);
         //--- Reverse the position
         OpenPosition(symbol_number,lot,order_type,position_open_price,sl,tp,comment);
         return;
        }
      //--- If the signal is in the direction of the position and the volume increase is enabled, increase the position volume
      if(!(pos.type==opposite_position_type) && VolumeIncrease[symbol_number]>0)
        {
         //--- Get the Stop Loss of the current position
         GetPositionProperties(symbol_number,P_SL);
         //--- Get the Take Profit of the current position
         GetPositionProperties(symbol_number,P_TP);
         //--- Adjust the volume
         lot=CalculateLot(symbol_number,VolumeIncrease[symbol_number]);
         //--- Increase the position volume
         OpenPosition(symbol_number,lot,order_type,position_open_price,pos.sl,pos.tp,comment);
         return;
        }
     }
  }

El código de la función OpenPosition():

//+------------------------------------------------------------------+
//| Opening a position                                               |
//+------------------------------------------------------------------+
void OpenPosition(int symbol_number,
                  double lot,
                  ENUM_ORDER_TYPE order_type,
                  double price,
                  double sl,
                  double tp,
                  string comment)
  {
//--- Set the magic number in the trading structure
   trade.SetExpertMagicNumber(MagicNumber);
//--- Set the slippage in points
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Instant Execution and Market Execution mode
//    *** Starting with build 803, Stop Loss and Take Profit ***
//    *** can be set upon opening a position in the SYMBOL_TRADE_EXECUTION_MARKET mode ***
   if(symb.execution_mode==SYMBOL_TRADE_EXECUTION_INSTANT ||
      symb.execution_mode==SYMBOL_TRADE_EXECUTION_MARKET)
     {
      //--- If the position failed to open, print the relevant message
      if(!trade.PositionOpen(Symbols[symbol_number],order_type,lot,price,sl,tp,comment))
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
     }
  }

Por tanto, cada función recibe ahora el número de símbolo (symbol_number). Note también el cambio introducido en la construcción 803:

A partir de la construcción 803, Stop Loss y Take Profit se pueden configurar con la apertura de una posición en el modo SYMBOL_TRADE_EXECUTION_MARKET.

Los códigos revisados de las demás funciones se pueden encontrar en los archivos adjuntos. Todo lo que necesitamos hacer ahora es optimizar los parámetros y realizar la simulación.


Optimizar Parámetros y Simular el Asesor Experto

Primero optimizaremos los parámetros para el primer símbolo y después para el segundo. Empecemos con EURUSD.

Abajo puede ver la configuración del Probador de Estrategias:

Fig. 1. Configuración de Probador de Estrategias.

Fig. 1. Configuración de Probador de Estrategias.

La configuración del Asesor Experto se debe hacer tal y como se muestra abajo (por motivos de conveniencia, los archivos .set que contienen la configuración para cada símbolo están adjuntos en el artículo). Para excluir un símbolo determinado de la optimización, simplemente debe dejar el campo del parámetro del nombre de símbolo vacío. La optimización de los parámetros que se realiza para cada símbolo por separado también hará el proceso de optimización más rápido.

Fig. 2. Configuración de Asesor Experto para optimización de parámetros: EURUSD.

Fig. 2. Configuración de Asesor Experto para optimización de parámetros: EURUSD.

La optimización durará alrededor de una hora en un procesador dual-core. Abajo puede ver los resultados de la prueba de factor máximo de recuperación:

Fig. 3. Resultados de la prueba de factor máximo de recuperación para EURUSD.

Fig. 3. Resultados de la prueba de factor máximo de recuperación para EURUSD.

Ahora configure NZDUSD como el segundo símbolo. Para la optimización, deje la línea con el nombre del símbolo para el primer bloque de parámetros vacía.

Alternativamente, puede añadir un guión al final del nombre de símbolo. El Asesor Experto no encontrará el símbolo con ese nombre en la lista de símbolos, e inicializará el índice de array en una cadena de caracteres vacía.

Abajo puede ver los resultados para NZDUSD:

Fig. 4. Resultados de la prueba de factor máximo de recuperación para NZDUSD.

Fig. 4. Resultados de la prueba de factor máximo de recuperación para NZDUSD.

Ahora podemos realizar una simulación con los dos símbolos juntos. En la configuración de Probador de Estrategias puede configurar cualquier símbolo en el que se desea ejecutar el Asesor Experto, puesto que los resultados eran idénticos. Incluso puede ser un símbolo que no esté involucrado en operaciones de trading o simulaciones.

Abajo puede ver los resultados de la simulación para los dos símbolos:

Fig. 5. Resultados de la simulación para los dos símbolos: EURUSD y NZDUSD.

Fig. 5. Resultados de la simulación para los dos símbolos: EURUSD y NZDUSD.


Conclusión

Eso es todo. Los códigos fuente se encuentran adjuntos y se pueden descargar para un estudio más detallado de lo explicado arriba. Como ejercicio de práctica, trate de seleccionar uno o más símbolos, o cambie las condiciones de apertura de posición usando otros indicadores.

Después de descomprimir los archivos, coloque la carpeta MultiSymbolExpert en el directorio MetaTrader 5\MQL5\Experts. Además, el indicador EventsSpy.mq5 se debe colocar en el directorio MetaTrader 5\MQL5\Indicators.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/648

Libro de Recetas MQL5: Desarrollar un Asesor Experto Multidivisa con un Número Ilimitado de Parámetros Libro de Recetas MQL5: Desarrollar un Asesor Experto Multidivisa con un Número Ilimitado de Parámetros
En este artículo crearemos un patrón que usa un solo conjunto de parámetros para la optimización del sistema de trading, a la vez que permite un número ilimitado de parámetros. La lista de símbolo se creará en un archivo de texto estándar (*.txt). Los parámetros de entrada para cada símbolo también se almacenarán en archivos. De esta forma podremos evitar la restricción del terminal del número de parámetros centrada de un Asesor Experto.
Libro de Recetas MQL5: Desarrollo de un Marco de Trabajo para un Sistema de Trading Basado en la Estrategia de Triple Pantalla Libro de Recetas MQL5: Desarrollo de un Marco de Trabajo para un Sistema de Trading Basado en la Estrategia de Triple Pantalla
En este artículo desarrollaremos un marco de trabajo para un sistema de trading basado en la estrategia de Triple Pantalla en MQL5. El Asesor Experto no se desarrollará de cero. En lugar de ello, simplemente modificaremos el programa del artículo anterior "MQL5 Cookbook: Using Indicators to Set Trading Conditions in Expert Advisors" (“Libro de Recetas MQL5: Usar Indicadores Para Configurar Condiciones de Trading en Asesores Expertos”), que sustancialmente ya vale para nuestros propósitos. El artículo también demostrará cómo se pueden modificar fácilmente los patrones de programas ya hechos.
Libro de Recetas MQL5: Escribir el Historial de Transacciones y Crear Gráficos de Saldo para cada Símbolo en Excel Libro de Recetas MQL5: Escribir el Historial de Transacciones y Crear Gráficos de Saldo para cada Símbolo en Excel
Al explicar mis ideas en varios foros, a menudo utilizo ejemplos de mis resultados de simulación en forma de capturas de pantalla de gráficos de Microsoft Excel. Muchas veces me ha llegado la pregunta de cómo se pueden crear estos gráficos. Ahora por fin tengo algo de tiempo para explicarlo todo en este artículo.
El Indicador ZigZag: Nuevo Enfoque y Soluciones El Indicador ZigZag: Nuevo Enfoque y Soluciones
Este artículo examina la posibilidad de crear un indicador ZigZag avanzado. La idea de identificar nodos se basa en el uso del indicador Envelopes (Envolturas). Suponemos que podemos obtener una determinada combinación de parámetros centrada para una serie de Envelopes, mientras que todos los nodos de ZigZag se encuentran dentro de los confines de las bandas de Envelopes. Por tanto, podemos tratar de predecir las coordenadas del nuevo nodo.