Libro de Recetas MQL5: Cómo Evitar Errores al Configurar/Modificar Niveles de Trading

Anatoli Kazharski | 28 mayo, 2014

Introducción

Continuando con nuestro trabajo en el Asesor Experto desde el artículo anterior de la serie llamado "MQL5 Cookbook: Analyzing Position Properties in the MetaTrader 5 Strategy Tester", (“Libro de Recetas MQL5: Analizar Propiedades de Posición en el Probador de Estrategias de MetaTrader 5”), seguiremos trabajando en nuestro código, reforzándolo con un buen número de útiles funciones, mejorando y optimizando también las funciones ya existentes.

En los foros de programación MQL, las preguntas de principiantes referentes a errores que surgen al configurar/modificar los niveles de trading (Stop Loss, Take Profit y órdenes pendientes) son muy frecuentes. Creo que muchos de ustedes ya estarán familiarizados con el mensaje de cierre del diario [Invalid stops], o detención inválida. En este artículo crearemos funciones que normalizarán y comprobarán la corrección de los valores de nivel de trading antes de abrir/modificar una posición.

El Asesor Experto tendrá, en esta ocasión, parámetros externos que se podrán optimizar en el Probador de Estrategias de MetaTrader 5, y se parecerán en algunos aspectos a un sistema de trading simple. Desde luego, todavía nos queda mucho por hacer antes de desarrollar un sistema de trading real. Pero Roma no se construyó en un día. De modo que vamos a ello.

La optimización de código en las funciones de asistentes se considerará conforme se vaya desarrollando el artículo. El panel de información no se tratará puesto que debemos echar un vistazo a algunas propiedades de posición que no se pueden obtener usando identificadores estándar (se requiere el uso del historial de transacciones). Este tema se tratará, no obstante, en uno de los siguientes artículos de la serie.


Desarrollo de Asesor Experto

Comencemos. Como viene siendo normal, empezaremos insertando numeraciones adicionales, variables, arrays y funciones auxiliares al principio del archivo. Necesitaremos una función que nos permita obtener propiedades de símbolo fácilmente. El mismo enfoque sencillo será necesario para obtener propiedades de posición.

En los artículos anteriores vimos que las variables globales se asignaban a todas las propiedades a la vez en la función GetPositionProperties. En esta ocasión trataremos de dar la posibilidad de obtener cada propiedad por separado. Abajo hay dos enumeraciones para la implementación de lo explicado arriba. Las funciones mismas se revisarán más adelante.

//--- Enumeration of position properties
enum ENUM_POSITION_PROPERTIES
  {
   P_SYMBOL        = 0,
   P_MAGIC         = 1,
   P_COMMENT       = 2,
   P_SWAP          = 3,
   P_COMMISSION    = 4,
   P_PRICE_OPEN    = 5,
   P_PRICE_CURRENT = 6,
   P_PROFIT        = 7,
   P_VOLUME        = 8,
   P_SL            = 9,
   P_TP            = 10,
   P_TIME          = 11,
   P_ID            = 12,
   P_TYPE          = 13,
   P_ALL           = 14
  };
//--- Enumeration of symbol properties
enum ENUM_SYMBOL_PROPERTIES
  {
   S_DIGITS       = 0,
   S_SPREAD       = 1,
   S_STOPSLEVEL   = 2,
   S_POINT        = 3,
   S_ASK          = 4,
   S_BID          = 5,
   S_VOLUME_MIN   = 6,
   S_VOLUME_MAX   = 7,
   S_VOLUME_LIMIT = 8,
   S_VOLUME_STEP  = 9,
   S_FILTER       = 10,
   S_UP_LEVEL     = 11,
   S_DOWN_LEVEL   = 12,
   S_ALL          = 13
  };

La enumeración ENUM_SYMBOL_PROPERTIES no contiene todas las propiedades de símbolo, pero se pueden añadir en cualquier momento si es necesario. La enumeración también contiene propiedades definidas por el usuario (10, 11, 12) cuyo cálculo se basa en otras propiedades de símbolo. Hay un identificador que se puede usar para obtener todas las propiedades de la enumeración de una vez, al igual que en la enumeración de propiedades de posición.

A esto le siguen los parámetros externos del Asesor Experto:

//--- External parameters of the Expert Advisor
input int            NumberOfBars=2;     // Number of Bullish/Bearish bars for a Buy/Sell
input double         Lot         =0.1;   // Lot
input double         StopLoss    =50;    // Stop Loss
input double         TakeProfit  =100;   // Take Profit
input double         TrailingStop=10;    // Trailing Stop
input bool           Reverse     =true;  // Position reverse

Echemos un vistazo a los parámetros externos:

Solo el parámetro NumberOfBars necesita una explicación adicional. No tiene sentido configurar el valor de este parámetro, por ejemplo, a más de cinco, pues esto sería bastante raro y ya sería demasiado tarde para abrir una posición tras semejante movimiento. Por tanto, necesitaremos una variable que nos ayude a ajustar el valor de este parámetro.

//--- To check the value of the NumberOfBars external parameter
int                  AllowedNumberOfBars=0;

Este parámetro también determinará la cantidad de datos de barras que se almacenarán en arrays de precio. Esto se tratara más adelante cuando lleguemos a la modificación de las funciones personalizadas.

Al igual que en el caso de las propiedades de posición, declararemos variables a nivel global para propiedades de símbolo con el fin de facilitar acceso desde cualquier función:

//--- Symbol properties
int                  sym_digits=0;           // Number of decimal places
int                  sym_spread=0;           // Spread in points
int                  sym_stops_level=0;      // Stops level
double               sym_point=0.0;          // Point value
double               sym_ask=0.0;            // Ask price
double               sym_bid=0.0;            // Bid price
double               sym_volume_min=0.0;     // Minimum volume for a deal
double               sym_volume_max=0.0;     // Maximum volume for a deal
double               sym_volume_limit=0.0;   // Maximum permissible volume for a position and orders in one direction
double               sym_volume_step=0.0;    // Minimum volume change step for a deal
double               sym_offset=0.0;         // Offset from the maximum possible price for a transaction
double               sym_up_level=0.0;       // Upper Stop level price
double               sym_down_level=0.0;     // Lower Stop level price

Puesto que el Trailing Stop se debe calcular basándonos en los puntos alto y bajo de la barra, necesitaremos arrays para los datos de esta barra:

//--- Price data arrays
double               close_price[]; // Close (closing prices of the bar)
double               open_price[];  // Open (opening prices of the bar)
double               high_price[];  // High (bar's highs)
double               low_price[];   // Low (bar's lows)

Procedamos ahora a la modificación y creación de funciones. Ya tenemos la función GetBarsData, que copia precios de apertura y cierre de barras a arrays de precio. Ahora necesitamos también puntos altos y bajos. Además, el valor obtenido del parámetro NumberOfBars se debe ajustar. Este es el aspecto de la función después de la modificación:

//+------------------------------------------------------------------+
//| Getting bar values                                               |
//+------------------------------------------------------------------+
void GetBarsData()
  {
//--- Adjust the number of bars for the position opening condition
   if(NumberOfBars<=1)
      AllowedNumberOfBars=2;              // At least two bars are required
   if(NumberOfBars>=5)
      AllowedNumberOfBars=5;              // but no more than 5
   else
      AllowedNumberOfBars=NumberOfBars+1; // and always more by one
//--- Reverse the indexing order (... 3 2 1 0)
   ArraySetAsSeries(close_price,true);
   ArraySetAsSeries(open_price,true);
   ArraySetAsSeries(high_price,true);
   ArraySetAsSeries(low_price,true);
//--- Get the closing price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyClose(_Symbol,Period(),0,AllowedNumberOfBars,close_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the opening price of the bar
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyOpen(_Symbol,Period(),0,AllowedNumberOfBars,open_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the bar's high
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyHigh(_Symbol,Period(),0,AllowedNumberOfBars,high_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the High price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
//--- Get the bar's low
//    If the number of the obtained values is less than requested, print the relevant message
   if(CopyLow(_Symbol,Period(),0,AllowedNumberOfBars,low_price)<AllowedNumberOfBars)
     {
      Print("Failed to copy the values ("
            +_Symbol+", "+TimeframeToString(Period())+") to the Low price array! "
            "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError()));
     }
  }

Las condiciones que requieran al menos dos barras y siempre una más están ahí porque solo avanzaremos con barras completadas que empiecen con un índice [1]. De hecho, en este caso los ajustes se pueden considerar innecesarios, puesto que los datos de la barra se pueden copiar empezando por el índice especificado en el tercer parámetro de las funciones CopyOpen, CopyClose, CopyHigh y CopyLow. El límite de cinco barras también se puede cambiar (hacia arriba/hacia abajo) a su discreción.

La función GetTradingSignal ahora se ha vuelto un poco más compleja, puesto que la condición se generará de forma diferente dependiendo del número de barras especificado en el parámetro NumberOfBars. Además, ahora usaremos un tipo de valor devuelto más correcto - order type:

//+------------------------------------------------------------------+
//| Determining trading signals                                      |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE GetTradingSignal()
  {
//--- A Buy signal (ORDER_TYPE_BUY) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]>open_price[1])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==3 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==4 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars==5 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4])
      return(ORDER_TYPE_BUY);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]>open_price[1] && 
      close_price[2]>open_price[2] && 
      close_price[3]>open_price[3] && 
      close_price[4]>open_price[4] && 
      close_price[5]>open_price[5])
      return(ORDER_TYPE_BUY);
//--- A Sell signal (ORDER_TYPE_SELL) :
   if(AllowedNumberOfBars==2 && 
      close_price[1]<open_price[1])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==3 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==4 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars==5 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4])
      return(ORDER_TYPE_SELL);
   if(AllowedNumberOfBars>=6 && 
      close_price[1]<open_price[1] && 
      close_price[2]<open_price[2] && 
      close_price[3]<open_price[3] && 
      close_price[4]<open_price[4] && 
      close_price[5]<open_price[5])
      return(ORDER_TYPE_SELL);
//--- No signal (WRONG_VALUE):
   return(WRONG_VALUE);
  }

Modifiquemos ahora la función GetPositionProperties. En los artículos anteriores podíamos obtener todas las propiedades de una vez. No obstante, en algunas ocasiones solo deseará obtener una propiedad. Para ello puede usar las funciones estándar ofrecidas por el lenguaje, pero esto no sería tan conveniente como lo que nosotros deseamos. Abajo tiene el código de la función GetPositionProperties modificada. Ahora, al pasar un determinado identificador de la enumeración ENUM_POSITION_PROPERTIES, puede obtener una sola propiedad de posición o todas las propiedades a la vez.

//+------------------------------------------------------------------+
//| Getting position properties                                      |
//+------------------------------------------------------------------+
void GetPositionProperties(ENUM_POSITION_PROPERTIES position_property)
  {
//--- Check if there is an open position
   pos_open=PositionSelect(_Symbol);
//--- If an open position exists, get its properties
   if(pos_open)
     {
      switch(position_property)
        {
         case P_SYMBOL        : pos_symbol=PositionGetString(POSITION_SYMBOL);                  break;
         case P_MAGIC         : pos_magic=PositionGetInteger(POSITION_MAGIC);                   break;
         case P_COMMENT       : pos_comment=PositionGetString(POSITION_COMMENT);                break;
         case P_SWAP          : pos_swap=PositionGetDouble(POSITION_SWAP);                      break;
         case P_COMMISSION    : pos_commission=PositionGetDouble(POSITION_COMMISSION);          break;
         case P_PRICE_OPEN    : pos_price=PositionGetDouble(POSITION_PRICE_OPEN);               break;
         case P_PRICE_CURRENT : pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);           break;
         case P_PROFIT        : pos_profit=PositionGetDouble(POSITION_PROFIT);                  break;
         case P_VOLUME        : pos_volume=PositionGetDouble(POSITION_VOLUME);                  break;
         case P_SL            : pos_sl=PositionGetDouble(POSITION_SL);                          break;
         case P_TP            : pos_tp=PositionGetDouble(POSITION_TP);                          break;
         case P_TIME          : pos_time=(datetime)PositionGetInteger(POSITION_TIME);           break;
         case P_ID            : pos_id=PositionGetInteger(POSITION_IDENTIFIER);                 break;
         case P_TYPE          : pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); break;
         case P_ALL           :
            pos_symbol=PositionGetString(POSITION_SYMBOL);
            pos_magic=PositionGetInteger(POSITION_MAGIC);
            pos_comment=PositionGetString(POSITION_COMMENT);
            pos_swap=PositionGetDouble(POSITION_SWAP);
            pos_commission=PositionGetDouble(POSITION_COMMISSION);
            pos_price=PositionGetDouble(POSITION_PRICE_OPEN);
            pos_cprice=PositionGetDouble(POSITION_PRICE_CURRENT);
            pos_profit=PositionGetDouble(POSITION_PROFIT);
            pos_volume=PositionGetDouble(POSITION_VOLUME);
            pos_sl=PositionGetDouble(POSITION_SL);
            pos_tp=PositionGetDouble(POSITION_TP);
            pos_time=(datetime)PositionGetInteger(POSITION_TIME);
            pos_id=PositionGetInteger(POSITION_IDENTIFIER);
            pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);                     break;
         default: Print("The passed position property is not listed in the enumeration!");               return;
        }
     }
//--- If there is no open position, zero out variables for position properties
   else
      ZeroPositionProperties();
  }

Similarmente, podemos implementar la función GetSymbolProperties para obtener propiedades de símbolo:

//+------------------------------------------------------------------+
//| Getting symbol properties                                        |
//+------------------------------------------------------------------+
void GetSymbolProperties(ENUM_SYMBOL_PROPERTIES symbol_property)
  {
   int lot_offset=1; // Number of points for the offset from the Stops level
//---
   switch(symbol_property)
     {
      case S_DIGITS        : sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);                   break;
      case S_SPREAD        : sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);                   break;
      case S_STOPSLEVEL    : sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);   break;
      case S_POINT         : sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);                           break;
      //---
      case S_ASK           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);                       break;
      case S_BID           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);                       break;
         //---
      case S_VOLUME_MIN    : sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);                 break;
      case S_VOLUME_MAX    : sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);                 break;
      case S_VOLUME_LIMIT  : sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);             break;
      case S_VOLUME_STEP   : sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);               break;
      //---
      case S_FILTER        :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);        break;
         //---
      case S_UP_LEVEL      :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);                     break;
         //---
      case S_DOWN_LEVEL    :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      case S_ALL           :
         sym_digits=(int)SymbolInfoInteger(_Symbol,SYMBOL_DIGITS);
         sym_spread=(int)SymbolInfoInteger(_Symbol,SYMBOL_SPREAD);
         sym_stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
         sym_point=SymbolInfoDouble(_Symbol,SYMBOL_POINT);
         sym_ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),sym_digits);
         sym_bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),sym_digits);
         sym_volume_min=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
         sym_volume_max=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX);
         sym_volume_limit=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_LIMIT);
         sym_volume_step=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
         sym_offset=NormalizeDouble(CorrectValueBySymbolDigits(lot_offset*sym_point),sym_digits);
         sym_up_level=NormalizeDouble(sym_ask+sym_stops_level*sym_point,sym_digits);
         sym_down_level=NormalizeDouble(sym_bid-sym_stops_level*sym_point,sym_digits);                   break;
         //---
      default: Print("The passed symbol property is not listed in the enumeration!"); return;
     }
  }

Por favor, que tenga en cuenta que algunas propiedades de símbolo pueden requerir la obtención de otras propiedades primero.

Ahora tenemos una nueva función, CorrectValueBySymbolDigits. Devuelve el valor relevante dependiendo del número de posiciones decimales en el precio. Se puede pasar a la función un número íntegro o real. El tipo de datos pasados determina la versión de la función que se usará. Esta cualidad se llama function overloading, o sobrecarga de función.

//+------------------------------------------------------------------+
//| Adjusting the value based on the number of digits in the price (int)|
//+------------------------------------------------------------------+
int CorrectValueBySymbolDigits(int value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }
//+------------------------------------------------------------------+
//| Adjusting the value based on the number of digits in the price (double)|
//+------------------------------------------------------------------+
double CorrectValueBySymbolDigits(double value)
  {
   return (sym_digits==3 || sym_digits==5) ? value*=10 : value;
  }

Nuestro Asesor Experto tendrá un parámetro externo para especificar el volumen (Lote) de la posición de apertura. Creemos una función que ajuste el lote de acuerdo con la especificación del símbolo - CalculateLot:

//+------------------------------------------------------------------+
//| Calculating position lot                                         |
//+------------------------------------------------------------------+
double CalculateLot(double lot)
  {
//--- To adjust as per the step
   double corrected_lot=0.0;
//---
   GetSymbolProperties(S_VOLUME_MIN);  // Get the minimum possible lot
   GetSymbolProperties(S_VOLUME_MAX);  // Get the maximum possible lot
   GetSymbolProperties(S_VOLUME_STEP); // Get the lot increase/decrease step
//--- Adjust as per the lot step
   corrected_lot=MathRound(lot/sym_volume_step)*sym_volume_step;
//--- If less than the minimum, return the minimum
   if(corrected_lot<sym_volume_min)
      return(NormalizeDouble(sym_volume_min,2));
//--- If greater than the maximum, return the maximum
   if(corrected_lot>sym_volume_max)
      return(NormalizeDouble(sym_volume_max,2));
//---
   return(NormalizeDouble(corrected_lot,2));
  }

Procedamos ahora a funciones que tienen relación directa con el título del artículo. Son muy sencillas y directas, y podrá entender su propósito sin ninguna dificultad de leyendo los comentarios en el código.

La función CalculateTakeProfit se usa para calcular el valor de Take Profit:

//+------------------------------------------------------------------+
//| Calculating the Take Profit value                                |
//+------------------------------------------------------------------+
double CalculateTakeProfit(ENUM_ORDER_TYPE order_type)
  {
//--- If Take Profit is required
   if(TakeProfit>0)
     {
      //--- For the calculated Take Profit value
      double tp=0.0;
      //--- If you need to calculate the value for a SELL position
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Calculate the level
         tp=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Return the calculated value if it is lower than the lower limit of the Stops level
         //    If the value is higher or equal, return the adjusted value
         return(tp<sym_down_level ? tp : sym_down_level-sym_offset);
        }
      //--- If you need to calculate the value for a BUY position
      if(order_type==ORDER_TYPE_BUY)
        {
         //--- Calculate the level
         tp=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(TakeProfit*sym_point),sym_digits);
         //--- Return the calculated value if it is higher that the upper limit of the Stops level
         //    If the value is lower or equal, return the adjusted value
         return(tp>sym_up_level ? tp : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

La función CalculateStopLoss se usa para calcular el valor de Stop Loss:

//+------------------------------------------------------------------+
//| Calculating the Stop Loss value                                  |
//+------------------------------------------------------------------+
double CalculateStopLoss(ENUM_ORDER_TYPE order_type)
  {
//--- If Stop Loss is required
   if(StopLoss>0)
     {
      //--- For the calculated Stop Loss value
      double sl=0.0;
      //--- If you need to calculate the value for a BUY position
      if(order_type==ORDER_TYPE_BUY)
        {
         // Calculate the level
         sl=NormalizeDouble(sym_ask-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Return the calculated value if it is lower that the lower limit of the Stops level
         //    If the value is higher or equal, return the adjusted value
         return(sl<sym_down_level ? sl : sym_down_level-sym_offset);
        }
      //--- If you need to calculate the value for a SELL position
      if(order_type==ORDER_TYPE_SELL)
        {
         //--- Calculate the level
         sl=NormalizeDouble(sym_bid+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- Return the calculated value if it is higher than the upper limit of the Stops level
         //    If the value is lower or equal, return the adjusted value
         return(sl>sym_up_level ? sl : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

La función CalculateTrailingStop se usa para calcular el valor de Trailing Stop:

//+------------------------------------------------------------------+
//| Calculating the Trailing Stop value                              |
//+------------------------------------------------------------------+
double CalculateTrailingStop(ENUM_POSITION_TYPE position_type)
  {
//--- Variables for calculations
   double            level       =0.0;
   double            buy_point   =low_price[1];    // The Low value for a Buy
   double            sell_point  =high_price[1];   // The High value for a Sell
//--- Calculate the level for a BUY position
   if(position_type==POSITION_TYPE_BUY)
     {
      //--- Bar's low minus the specified number of points
      level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- If the calculated level is lower than the lower limit of the Stops level, 
      //    the calculation is complete, return the current value of the level
      if(level<sym_down_level)
         return(level);
      //--- If it is not lower, try to calculate based on the bid price
      else
        {
         level=NormalizeDouble(sym_bid-CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- If the calculated level is lower than the limit, return the current value of the level
         //    otherwise set the nearest possible value
         return(level<sym_down_level ? level : sym_down_level-sym_offset);
        }
     }
//--- Calculate the level for a SELL position
   if(position_type==POSITION_TYPE_SELL)
     {
      // Bar's high plus the specified number of points
      level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
      //--- If the calculated level is higher than the upper limit of the Stops level, 
      //    the calculation is complete, return the current value of the level
      if(level>sym_up_level)
         return(level);
      //--- If it is not higher, try to calculate based on the ask price
      else
        {
         level=NormalizeDouble(sym_ask+CorrectValueBySymbolDigits(StopLoss*sym_point),sym_digits);
         //--- If the calculated level is higher than the limit, return the current value of the level
         //    Otherwise set the nearest possible value
         return(level>sym_up_level ? level : sym_up_level+sym_offset);
        }
     }
//---
   return(0.0);
  }

Ahora ya tenemos todas las funciones necesarias que devuelven valores correctos para operaciones de trading. Creemos una función que comprobará una condición para modificar el Trailing Stop, y procederá a ello si la condición especificada se cumple: ModifyTrailingStop. Debajo puede ver el código de esta función con comentarios detallados.

Por favor, preste atención al uso de todas las funciones creadas/modificadas arriba. El interruptor switch determina la condición relevante dependiendo del tipo de la posición actual y la condición resultante se almacena a continuación en la variable condition. Para modificar una posición, usaremos el método PositionModify de la clase CTrade de la Biblioteca Estándar.

//+------------------------------------------------------------------+
//| Modifying the Trailing Stop level                                |
//+------------------------------------------------------------------+
void ModifyTrailingStop()
  {
//--- If the Trailing Stop and Stop Loss are set
   if(TrailingStop>0 && StopLoss>0)
     {
      double         new_sl=0.0;       // For calculating the new Stop Loss level
      bool           condition=false;  // For checking the modification condition
      //--- Get the flag of presence/absence of the position
      pos_open=PositionSelect(_Symbol);
      //--- If the position exists
      if(pos_open)
        {
         //--- Get the symbol properties
         GetSymbolProperties(S_ALL);
         //--- Get the position properties
         GetPositionProperties(P_ALL);
         //--- Get the Stop Loss level
         new_sl=CalculateTrailingStop(pos_type);
         //--- Depending on the position type, check the relevant condition for the Trailing Stop modification
         switch(pos_type)
           {
            case POSITION_TYPE_BUY  :
               //--- If the new Stop Loss value is higher
               //    than the current value plus the set step
               condition=new_sl>pos_sl+CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
            case POSITION_TYPE_SELL :
               //--- If the new Stop Loss value is lower
               //    than the current value minus the set step
               condition=new_sl<pos_sl-CorrectValueBySymbolDigits(TrailingStop*sym_point);
               break;
           }
         //--- If there is a Stop Loss, compare the values before modification
         if(pos_sl>0)
           {
            //--- If the condition for the order modification is met, i.e. the new value is lower/higher 
            //    than the current one, modify the Trailing Stop of the position
            if(condition)
              {
               if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
                  Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
              }
           }
         //--- If there is no Stop Loss, simply set it
         if(pos_sl==0)
           {
            if(!trade.PositionModify(_Symbol,new_sl,pos_tp))
               Print("Error modifying the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
  }

Ahora ajustaremos la función TradingBlock de acuerdo con todos los cambios hechos arriba. Al igual que con la función ModifyTrailingStop, todos los valores de las variables para una orden de trading se determinarán usando el interruptor switch. Esto reduce la cantidad de código significativamente y simplifica otras modificaciones, puesto que en lugar de una rama para dos tipos de posiciones, solo quedará una.

//+------------------------------------------------------------------+
//| Trading block                                                    |
//+------------------------------------------------------------------+
void TradingBlock()
  {
   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 reverse position
   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
//--- Get a signal
   signal=GetTradingSignal();
//--- If there is no signal, exit
   if(signal==WRONG_VALUE)
      return;
//--- Find out if there is a position
   pos_open=PositionSelect(_Symbol);
//--- Get all symbol properties
   GetSymbolProperties(S_ALL);
//--- Determine values for trade variables
   switch(signal)
     {
      //--- Assign values to variables for a BUY
      case ORDER_TYPE_BUY  :
         position_open_price=sym_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=sym_bid;
         order_type=ORDER_TYPE_SELL;
         opposite_position_type=POSITION_TYPE_BUY;
         break;
     }
//--- Calculate the Take Profit and Stop Loss levels
   sl=CalculateStopLoss(order_type);
   tp=CalculateTakeProfit(order_type);
//--- If there is no position
   if(!pos_open)
     {
      //--- Adjust the volume
      lot=CalculateLot(Lot);
      //--- Open a position
      //    If the position failed to open, print the relevant message
      if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
        {
         Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
        }
     }
//--- If there is a position
   else
     {
      //--- Get the position type
      GetPositionProperties(P_TYPE);
      //--- If the position is opposite to the signal and the position reverse is enabled
      if(pos_type==opposite_position_type && Reverse)
        {
         //--- Get the position volume
         GetPositionProperties(P_VOLUME);
         //--- Adjust the volume
         lot=pos_volume+CalculateLot(Lot);
         //--- Open the position. If the position failed to open, print the relevant message
         if(!trade.PositionOpen(_Symbol,order_type,lot,position_open_price,sl,tp,comment))
           {
            Print("Error opening the position: ",GetLastError()," - ",ErrorDescription(GetLastError()));
           }
        }
     }
//---
   return;
  }

También debemos hacer otra importante corrección en la función SetInfoPanel, pero antes preparemos unas cuantas funciones auxiliares que indicarán cómo/dónde se usa actualmente el programa:

//+------------------------------------------------------------------+
//| Returning the testing flag                                       |
//+------------------------------------------------------------------+
bool IsTester()
  {
   return(MQL5InfoInteger(MQL5_TESTER));
  }
//+------------------------------------------------------------------+
//| Returning the optimization flag                                  |
//+------------------------------------------------------------------+
bool IsOptimization()
  {
   return(MQL5InfoInteger(MQL5_OPTIMIZATION));
  }
//+------------------------------------------------------------------+
//| Returning the visual testing mode flag                           |
//+------------------------------------------------------------------+
bool IsVisualMode()
  {
   return(MQL5InfoInteger(MQL5_VISUAL_MODE));
  }
//+------------------------------------------------------------------+
//| Returning the flag for real time mode outside the Strategy Tester|
//| if all conditions are met                                        |
//+------------------------------------------------------------------+
bool IsRealtime()
  {
   if(!IsTester() && !IsOptimization() && !IsVisualMode())
      return(true);
   else
      return(false); 
  }

Lo único que necesitamos añadir a la función SetInfoPanel es una condición que le indique al programa que el panel de información solo se deber mostrar en los modos de visualización y tiempo real. Si esto se ignora, la simulación durará 4-5 veces más. Esto es especialmente importante al optimizar los parámetros.

//+------------------------------------------------------------------+
//| Setting the info panel                                           |
//|------------------------------------------------------------------+
void SetInfoPanel()
  {
//--- Visualization or real time modes
   if(IsVisualMode() || IsRealtime())
     {
     // The remaining code of the SetInfoPanel() function
     // ...
     }
  }

Ahora debemos hacer algunos cambios a las funciones del programa principal para poder proceder con la optimización de parámetros y la simulación del Asesor Experto.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Initialize the new bar
   CheckNewBar();
//--- Get the properties and set the panel
   GetPositionProperties(P_ALL);
//--- Set the info panel
   SetInfoPanel();
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- If the bar is not new, exit
   if(!CheckNewBar())
      return;
//--- If there is a new bar
   else
     {
      GetBarsData();          // Get bar data
      TradingBlock();         // Check the conditions and trade
      ModifyTrailingStop();   // Modify the Trailing Stop level
     }
//--- Get the properties and update the values on the panel
   GetPositionProperties(P_ALL);
//--- Update the info panel
   SetInfoPanel();
  }
//+------------------------------------------------------------------+
//| Trade event                                                      |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Get position properties and update the values on the panel
   GetPositionProperties(P_ALL);
//--- Update the info panel
   SetInfoPanel();
  }


Optimizar Parámetros y Simular el Asesor Experto

Optimicemos ahora los parámetros. Configuraremos el Probador de Estrategias tal y como se muestra abajo:

Fig. 1. Configuración de Probador de Estrategias para la optimización de parámetros.

Fig. 1. Configuración de Probador de Estrategias para la optimización de parámetros.

Los parámetros del Asesor Experto se darán en un gran rango de valores:

Fig. 2. Configuración de Probador de Estrategias para la optimización de parámetros.

Fig. 2. Configuración de Probador de Estrategias para la optimización de parámetros.

La optimización duró unos siete minutos en un procesador dual-core (Intel Core2 Duo P7350 @ 2.00GHz). Los resultados de la simulación del factor de recuperación máximo son los siguientes:

Fig. 3. Resultados de la simulación del factor de recuperación máximo.

Fig. 3. Resultados de la simulación del factor de recuperación máximo.


Conclusión

Esto es todo por ahora. Estudio, simulación, optimización, experimentación y resultados. El código fuente del Asesor Experto de este artículo se puede descargar usando el enlace de abajo para continuar con su estudio.