English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Recetas MQL5 - Asesor multidivisa y funcionamiento de órdenes pendientes en MQL5

Recetas MQL5 - Asesor multidivisa y funcionamiento de órdenes pendientes en MQL5

MetaTrader 5Ejemplos | 29 abril 2014, 16:19
1 016 0
Anatoli Kazharski
Anatoli Kazharski

Introducción

En esta ocasión veremos la creación de un asesor multidivisa, cuyo algoritmo de comercio será construido para trabajar con las órdenes pendientes Buy Stop y Sell Stop. Construiremos un esquema para el comercio/simulación interno. En el artículo veremos las siguientes cuestiones:

  • El comercio en el diapasón temporal indicado. Haremos lo necesario para que se pueda indicar la hora de comienzo y finalización del comercio. Por ejemplo, se puede tratar de un diapasón temporal que abarque las sesiones comerciales europeas o americanas. Por supuesto, tendremos la posibilidad de elegir el mejor diapasón temporal durante la optimización de los parámetros del experto.
  • Cómo establecer/modificar/eleminar órdenes pendientes.
  • Procesamiento de eventos comerciales: comprobación de la última posición de Take profit o Stop Loss, control del historial de operaciones conforme a cada símbolo.


Proceso de desarrollo del experto

Como plantilla tomaremos el código del artículo "Recetas MQL5 - Experto multidivisa: ejemplo de un esquema sencillo, preciso y rápido. Los cambios serán significativos, pero la estructura básica del esquema permanecerá igual. Crearemos un asesor para comercio interno, pero este modo se podrá desconectar cuando el usuario lo desee. Las órdenes pendientes, en tal caso, siempre se establecerán directamente (conforme al evento "barra nueva"), si la posición estaba cerrada.

Comenzaremos con los parámetros externos del experto. Primero crearemos una nueva enumeración ENUM_HOURS en el archivo incorporable Enums.mqh. La cantidad de indicadores en esta enumeración se corresponde con la cantidad de horas en el día:

//--- Enumeración de las horas
enum ENUM_HOURS
  {
   h00 = 0,  // 00 : 00
   h01 = 1,  // 01 : 00
   h02 = 2,  // 02 : 00
   h03 = 3,  // 03 : 00
   h04 = 4,  // 04 : 00
   h05 = 5,  // 05 : 00
   h06 = 6,  // 06 : 00
   h07 = 7,  // 07 : 00
   h08 = 8,  // 08 : 00
   h09 = 9,  // 09 : 00
   h10 = 10, // 10 : 00
   h11 = 11, // 11 : 00
   h12 = 12, // 12 : 00
   h13 = 13, // 13 : 00
   h14 = 14, // 14 : 00
   h15 = 15, // 15 : 00
   h16 = 16, // 16 : 00
   h17 = 17, // 17 : 00
   h18 = 18, // 18 : 00
   h19 = 19, // 19 : 00
   h20 = 20, // 20 : 00
   h21 = 21, // 21 : 00
   h22 = 22, // 22 : 00
   h23 = 23  // 23 : 00
  };

En la lista de parámetros externos crearemos cuatro parámetros relacionados con el comercio en el diapasón temporal:

  • TradeInTimeRange - conexión/desconexión del modo. Como ya hemos dicho más arriba, crearemos la posibilidad de trabajar con un asesor comercial, no sólo en el diapasón temporal, sino en el régimen de veinticuatro horas (ininterrumpido).
  • StartTrade - hora de comienzo del comercio. Cuando la hora del servidor coincida con el valor indicado, el experto establecerá las órdenes pendientes, siempre que el modo esté conectado.
  • StopOpenOrders - hora de finalización del establecimiento de órdenes. En cuanto la hora del servidor coincida con este valor, el experto dejará de establecer órdenes pendientes, si la posición se cierra.
  • EndTrade - hora de finalización del comercio. En cuanto la hora del servidor sea igual a al valor indicado, el experto dejará de comerciar. La posición abierta sobre el símbolo indicado se cerrará, y las órdenes pendientes serán eliminadas

La lista de parámetros externos, dentro del código, tendrá el aspecto que se muestra abajo (ejemplo para dos símbolos). En el parámetro PendingOrder se establece la distancia en puntos a partir del precio actual.

//--- Parámetros externos del experto
sinput long       MagicNumber       = 777;      // Número mágico
sinput int        Deviation         = 10;       // Deslizamiento
//---
sinput string delimeter_00=""; // --------------------------------
sinput string     Symbol_01            ="EURUSD";  // Símbolo 1
input  bool       TradeInTimeRange_01  =true;      // |     Comercio en el diapasón temporal
input  ENUM_HOURS StartTrade_01        = h10;      // |     Hora del comienzo del comercio
input  ENUM_HOURS StopOpenOrders_01    = h17;      // |     Hora de finalización del establecimiento de órdenes
input  ENUM_HOURS EndTrade_01          = h22;      // |     Hora finalización del comercio
input  double     PendingOrder_01      = 50;       // |     Orden pendiente
input  double     TakeProfit_01        = 100;      // |     Take profit
input  double     StopLoss_01          = 50;       // |     Stop loss
input  double     TrailingStop_01      = 10;       // |     Trading stop
input  bool       Reverse_01           = true;     // |     Viraje de posición
input  double     Lot_01               = 0.1;      // |     Lote
//---
sinput string delimeter_01=""; // --------------------------------
sinput string     Symbol_02            ="AUDUSD";  // Símbolo 2
input  bool       TradeInTimeRange_02  =true;      // |     Comercio en el diapasón temporal
input  ENUM_HOURS StartTrade_02        = h10;      // |     Hora del comienzo del comercio
input  ENUM_HOURS StopOpenOrders_02    = h17;      // |     Hora de finalización del establecimiento de órdenes
input  ENUM_HOURS EndTrade_02          = h22;      // |     Hora finalización del comercio
input  double     PendingOrder_02      = 50;       // |     Orden pendiente
input  double     TakeProfit_02        = 100;      // |     Take profit
input  double     StopLoss_02          = 50;       // |     Stop loss
input  double     TrailingStop_02      = 10;       // |     Trading stop
input  bool       Reverse_02           = true;     // |     Viraje de posición
input  double     Lot_02               = 0.1;      // |     Lote

Hay que efectuar los cambios correspondientes también en la lista de las matrices que se llenan con los valores de los parámetros externos:

//--- Matrices para almacenar los parámetros externos
string     Symbols[NUMBER_OF_SYMBOLS];          // Símbolo
bool       TradeInTimeRange[NUMBER_OF_SYMBOLS]; // Comercio en el diapasón temporal
ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS];       // Hora del comienzo del comercio
ENUM_HOURS StopOpenOrders[NUMBER_OF_SYMBOLS];   // Hora de finalización del establecimiento de órdenes
ENUM_HOURS EndTrade[NUMBER_OF_SYMBOLS];         // Hora finalización del comercio
double     PendingOrder[NUMBER_OF_SYMBOLS];     // Orden pendiente
double     TakeProfit[NUMBER_OF_SYMBOLS];       // Take profit
double     StopLoss[NUMBER_OF_SYMBOLS];         // Stop loss
double     TrailingStop[NUMBER_OF_SYMBOLS];     // Trading stop
bool       Reverse[NUMBER_OF_SYMBOLS];          // Viraje de posición
double     Lot[NUMBER_OF_SYMBOLS];              // Lote

Haremos que durante el modo de viraje de posición (con el parámetro Reverse en la posición true), cuando se active una de las órdenes pendientes, la orden pendiente opuesta sea establecida de nuevo. No tenemos la posibilidad de cambiar el volumen de una orden pendiente, como sucede con sus niveles de precio (precio de la orden, Stop loss, Take profit), por eso hay que eliminarla y establecer una nueva orden pendiente con el volumen necesario.

Asímismo, si está conectado el modo de viraje de posición y se ha establecido simultáneamente el nivel de Trading stop, entonces, la orden pendiente se irá desplazando tras el precio, conforme este se mueva. Si además se ha establecido también el Stop loss, entonces se calculará y establecerá conforme a la orden pendiente.

A un nivel global en el programa, crearemos dos variables de línea para los comentarios a las órdenes pendientes:

//--- Comentarios a las órdenes pendientes
string comment_top_order    ="top_order";
string comment_bottom_order ="bottom_order";

Mientras se carga el experto, en el momento de la inicialización, en la función OnInit() comprobaremos la corrección de los parámetros externos. La comprobación consistirá en que mientras esté conectado el modo de comercio en el diapasón temporal, la hora del comienzo del comercio no deberá ser antes de una hora de la finalización del establecimiento de las órdenes pendientes, y la hora de establecimiento de las órdenes pendientes no deberá ser antes de una hora de la finalización del comercio. Escribimos la función CheckInputParameters(), que realizará esa comprobación:

//+------------------------------------------------------------------+
//| Comprueba los parámetros externos                                |
//+------------------------------------------------------------------+
bool CheckInputParameters()
  {
//--- Revisaremos todos los símbolos indicados
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Si no hay símbolo o el comercio durante el diapasón temporal está desconectado, pasaremos al símbolo siguiente
      if(Symbols[s]=="" || !TradeInTimeRange[s])
         continue;
      //--- Comprobamos que la hora de comienzo y finalización del comercio sean correctas
      if(StartTrade[s]>=EndTrade[s])
        {
         Print(Symbols[s],
               ": La hora de comienzo del comercio ("+IntegerToString(StartTrade[s])+") "
               "debe ser menor a la hora de la finalización del comercio ("+IntegerToString(EndTrade[s])+")!");
         return(false);
        }
      //--- Se puede comenzar el comercio no más tarde de una hora antes de la hora de establecimiento de las órdenes pendientes.
      //    El establecimiento de las órdenes pendientes deberá tener lugar no más tarde de una hora antes de la finalización del comercio.
      if(StopOpenOrders[s]>=EndTrade[s] ||
         StopOpenOrders[s]<=StartTrade[s])
        {
         Print(Symbols[s],
               ": La hora de la finalización del establecimiento de órdenes ("+IntegerToString(StopOpenOrders[s])+") "
               "debe ser menor a la hora de finalización ("+IntegerToString(EndTrade[s])+") y "
               "mayor que la hora de comienzo del comercio  ("+IntegerToString(StartTrade[s])+")!");
         return(false);
        }
     }
//--- Los parámetros son correctos
   return(true);
  }

Para la realización de nuestro esquema nos harán falta funciones, en las que se llevarán a cabo comprobaciones de localización en los diapasones temporales para el comercio y el establecimiento de órdenes pendientes. Llamaremos a estas funciones IsInTradeTimeRange() y IsInOpenOrdersTimeRange(). Su principio de funcionamiento es el mismo, la única diferencia se encuentra en el límite superior del diapasón comprobado. Más adelante les mostraremos en el artículo dónde hay que usar estas funciones.

//+------------------------------------------------------------------+
//| Comprueba si nos encontramos en el diapasón temporal comercial   |
//+------------------------------------------------------------------+
bool IsInTradeTimeRange(int symbol_number)
  {
//--- Si está conectado el comercio en el diapasón temporal
   if(TradeInTimeRange[symbol_number])
     {
      //--- Estructura de la fecha y la hora
      MqlDateTime last_date;
      //--- Obtenemos los últimos datos de fecha y hora
      TimeTradeServer(last_date);
      //--- Fuera del diapasón temporal permitido
      if(last_date.hour<StartTrade[symbol_number] ||
         last_date.hour>=EndTrade[symbol_number])
         return(false);
     }
//---Dentro del diapasón temporal permitido
   return(true);
  }
//+------------------------------------------------------------------+
//| Comprueba si nos encontramos en el diapasón temporal             |
//| de establecimiento de órdenes                                    |
//+------------------------------------------------------------------+
bool IsInOpenOrdersTimeRange(int symbol_number)
  {
//--- Si está conectado el comercio en el diapasón temporal
   if(TradeInTimeRange[symbol_number])
     {
      //--- Estructura de la fecha y la hora
      MqlDateTime last_date; 
      //--- Obtenemos los últimos datos de fecha y hora
      TimeTradeServer(last_date);
      //--- Fuera del diapasón temporal permitido
      if(last_date.hour<StartTrade[symbol_number] ||
         last_date.hour>=StopOpenOrders[symbol_number])
         return(false);
     }
//--- Dentro del diapasón temporal permitido
   return(true);
  }

En artículos anteriores ya hemos visto las funciones para obtener las propiedades de una posición, símbolo o del historial de operaciones. En este artículo necesitaremos una función análoga para obtener las propiedades de una orden pendiente. En el archivo incorporable Enums.mqh creamos una enumeración con las propiedades de la orden pendiente:

//--- Enumeración de las propiedades de una orden pendiente
enum ENUM_ORDER_PROPERTIES
  {
   O_SYMBOL          = 0,
   O_MAGIC           = 1,
   O_COMMENT         = 2,
   O_PRICE_OPEN      = 3,
   O_PRICE_CURRENT   = 4,
   O_PRICE_STOPLIMIT = 5,
   O_VOLUME_INITIAL  = 6,
   O_VOLUME_CURRENT  = 7,
   O_SL              = 8,
   O_TP              = 9,
   O_TIME_SETUP      = 10,
   O_TIME_EXPIRATION = 11,
   O_TIME_SETUP_MSC  = 12,
   O_TYPE_TIME       = 13,
   O_TYPE            = 14,
   O_ALL             = 15
  };

Después, en el archivo incorporable TradeFunctions.mqh hay que describir la estructura con las propiedades de la orden pendiente y crear su muestra:

//--- Propiedades de la orden pendiente
struct pending_order_properties
  {
   string            symbol;          // Símbolo
   long              magic;           // Número magico
   string            comment;         // Comentarios
   double            price_open;      // Precio indicado en la orden
   double            price_current;   // Precio actual del símbolo de la orden
   double            price_stoplimit; // El precio de la realización de la orden Limit cuando se active la orden StopLimit
   double            volume_initial;  // Volumen incial de la realización de la orden
   double            volume_current;  // Volumen sin cumplir
   double            sl;              // Nivel de Stop Loss
   double            tp;              // Nivel Take Profit
   datetime          time_setup;      // Hora de la realización de la orden
   datetime          time_expiration; // Tiempo de expiración de la orden
   datetime          time_setup_msc;  // Tiempo de establecimiento de la orden para su ejecución en milisegundos desde 01.01.1970
   datetime          type_time;       // Tiempo de vida útil de la orden
   ENUM_ORDER_TYPE   type;            // Tipo de posición
  };
//--- variable de las propiedades de la orden
pending_order_properties ord;

Para ontener las propiedades de una orden pendiente o todas las propiedades de golpe, escribimos la función GetPendingOrderProperties(). Primero hay que elegir la orden y sólo después se podra usar esta función para obtener sus propiedades. Mostraremos cómo hacerlo más adelante en este artículo.

//+------------------------------------------------------------------+
//| Obtiene las propiedades de una orden pendiente                   |
//| elegida por anticipado                                           |
//+------------------------------------------------------------------+
void GetPendingOrderProperties(ENUM_ORDER_PROPERTIES order_property)
  {
   switch(order_property)
     {
      case O_SYMBOL          : ord.symbol=OrderGetString(ORDER_SYMBOL);                              break;
      case O_MAGIC           : ord.magic=OrderGetInteger(ORDER_MAGIC);                               break;
      case O_COMMENT         : ord.comment=OrderGetString(ORDER_COMMENT);                            break;
      case O_PRICE_OPEN      : ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN);                      break;
      case O_PRICE_CURRENT   : ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT);                break;
      case O_PRICE_STOPLIMIT : ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT);            break;
      case O_VOLUME_INITIAL  : ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL);              break;
      case O_VOLUME_CURRENT  : ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT);              break;
      case O_SL              : ord.sl=OrderGetDouble(ORDER_SL);                                      break;
      case O_TP              : ord.tp=OrderGetDouble(ORDER_TP);                                      break;
      case O_TIME_SETUP      : ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);           break;
      case O_TIME_EXPIRATION : ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); break;
      case O_TIME_SETUP_MSC  : ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC);   break;
      case O_TYPE_TIME       : ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME);             break;
      case O_TYPE            : ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);                break;
      case O_ALL             :
         ord.symbol=OrderGetString(ORDER_SYMBOL);
         ord.magic=OrderGetInteger(ORDER_MAGIC);
         ord.comment=OrderGetString(ORDER_COMMENT);
         ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN);
         ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT);
         ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT);
         ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL);
         ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT);
         ord.sl=OrderGetDouble(ORDER_SL);
         ord.tp=OrderGetDouble(ORDER_TP);
         ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP);
         ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION);
         ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC);
         ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME);
         ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);                                      break;
         //---
      default: Print("¡La propiedad transmitida de la orden pendiente no se encuentra en la enumeración!");            return;
     }
  }

Ahora vamos a escribir las funciones básicas para establecer, modificar y eliminar órdenes pendientes. La función SetPendingOrder() establece una orden pendiente, y si no se consigue establecer la orden, muestra un mensaje en el registro con el código de error y su descripción:

//+------------------------------------------------------------------+
//| Establece una orden pospuesta                                    |
//+------------------------------------------------------------------+
void SetPendingOrder(int                  symbol_number,   // Número del símbolo
                     ENUM_ORDER_TYPE      order_type,      // Tipo de orden
                     double               lot,             // Volumen
                     double               stoplimit_price, // Nivel de la orden StopLimit
                     double               price,           // Precio
                     double               sl,              // Stop loss
                     double               tp,              // Take profit
                     ENUM_ORDER_TYPE_TIME type_time,       // Duración de la orden
                     string               comment)         // Comentarios
  {
//--- Establecemos el número mágico en la estructura comercial
   trade.SetExpertMagicNumber(MagicNumber);
//--- Si no se ha logrado establecer la orden pendiente, mostrar un mensaje sobre ello
   if(!trade.OrderOpen(Symbols[symbol_number],
                       order_type,lot,stoplimit_price,price,sl,tp,type_time,0,comment))
      Print("Error al establecer la orden pendiente: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }

La función ModifyPendingOrder() modifica la orden pendiente. Vamos a hacer posible que con esta función se pueda cambiar, no sólo el precio de la orden, sino también su volumen, transmitiéndoselo como último parámetro de la función. Si el valor del volumen transmitido es superior a cero, esto significa que la orden pendiente debe ser eliminada y hay que establecer una nueva con el volumen indicado. En todos los casos semejantes, tiene lugar sólo la modificación de la orden existente, con un cambio de precio.

//+------------------------------------------------------------------+
//| Cambiar la orden pendiente                                       |
//+------------------------------------------------------------------+
void ModifyPendingOrder(int                  symbol_number,   // Número del símbolo
                        ulong                ticket,          // Ticket de la orden
                        ENUM_ORDER_TYPE      type,            // Tipo de la orden
                        double               price,           // Precio de la orden
                        double               sl,              // Stop loss de la orden
                        double               tp,              // Take profit de la orden
                        ENUM_ORDER_TYPE_TIME type_time,       // Duración de la orden
                        datetime             time_expiration, // Fecha de expiración de la orden
                        double               stoplimit_price, // Precio
                        string               comment,         // Comentarios
                        double               volume)          // Volumen
  {
//--- Si se ha transmido un valor distinto a cero, establecemos de nuevo la orden
   if(volume>0)
     {
      //--- Si no se ha logrado eliminar la orden, salimos
      if(!DeletePendingOrder(ticket))
         return;
      //--- Establecemos la orden pendiente
      SetPendingOrder(symbol_number,type,volume,0,price,sl,tp,type_time,comment);
      //--- Corregimos las posiciones de Stop Loss con respecto a la orden
      CorrectStopLossByOrder(symbol_number,price,type);
     }
//--- Si se transmite un volumen igual a cero, modificamos la orden
   else
     {
      //--- Si no se ha logrado cambiar la orden pendiente, se debe mostrar un mensaje sobre ello
      if(!trade.OrderModify(ticket,price,sl,tp,type_time,time_expiration,stoplimit_price))
         Print("Error al cambiar el precio de la orden pendiente: ",
         GetLastError()," - ",ErrorDescription(GetLastError()));
      //--- De otro modo, corregimos las posiciones de Stop Loss con respecto a la orden
      else
         CorrectStopLossByOrder(symbol_number,price,type);
     }
  }

En el código de más arriba tenemos dos funciones más: DeletePendingOrder() y CorrectStopLossByOrder(). La primera elimina la orden pendiente, y la segunda corrige el nivel de la posición de Stop loss con respecto a la orden pendiente.

//+------------------------------------------------------------------+
//| Elimina la orden pendiente                                       |
//+------------------------------------------------------------------+
bool DeletePendingOrder(ulong ticket)
  {
//--- Si no se ha logrado eliminar la orden pendiente, se muestra un mensaje sobre ello
   if(!trade.OrderDelete(ticket))
     {
      Print("Error al eliminar la orden pendiente: ",GetLastError()," - ",ErrorDescription(GetLastError()));
      return(false);
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| Corrige las posiciones Stop Loss con respecto                    |
//| a la orden pendiente                                             |
//+------------------------------------------------------------------+
void CorrectStopLossByOrder(int             symbol_number, // Número del símbolo
                            double          price,         // Precio de la orden
                            ENUM_ORDER_TYPE type)          // Tipo de la orden
  {
//--- Si Stop Loss está desconectado, salimos
   if(StopLoss[symbol_number]==0)
      return;
//--- Si Stop Loss está conectado
   double new_sl=0.0; // Nuevo valor para Stop Loss
//--- Obtenemos el valor de un punto y
   GetSymbolProperties(symbol_number,S_POINT);
//--- la cantidad de símbolos en el precio tras la coma
   GetSymbolProperties(symbol_number,S_DIGITS);
//--- Obtenemos las posiciones Take Profit
   GetPositionProperties(symbol_number,P_TP);
//--- Calculamos con respecto al tipo de la orden
   switch(type)
     {
      case ORDER_TYPE_BUY_STOP  :
         new_sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         break;
      case ORDER_TYPE_SELL_STOP :
         new_sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         break;
     }
//--- Modificamos la posición
   if(!trade.PositionModify(Symbols[symbol_number],new_sl,pos.tp))
      Print("Error al modificar la posición: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }

Antes de establecer una orden pendiente, hay que comprobar de manera adicional si existe alguna orden comercial con el mismo comentario. Como ya dijimos al inicio del artículo, la orden Buy Stop superior la estableceremos con el comentario "top_order", y la orden Sell Stop, con el comentario "bottom_order". Para llevar a cabo esa comprobación, escribiremos la función CheckPendingOrderByComment():

//+------------------------------------------------------------------+
//| Comprueba la existencia de una orden pendiente                   |
//|  según el comentario                                             |
//+------------------------------------------------------------------+
bool CheckPendingOrderByComment(int symbol_number,string comment)
  {
   int    total_orders  =0;  // Cantidad total de órdenes pendientes
   string order_symbol  =""; // Símbolo de la orden
   string order_comment =""; // Comentarios de la orden
//--- Obtenemos la cantidad de órdenes pendientes
   total_orders=OrdersTotal();
//--- Repasamos un ciclo por todas las órdenes
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Elegimos la orden conforme a su ticket
      if(OrderGetTicket(i)>0)
        {
         //--- Obtenemos el nombre del símbolo
         order_symbol=OrderGetString(ORDER_SYMBOL);
         //--- Si los símbolos son iguales
         if(order_symbol==Symbols[symbol_number])
           {
            //--- Obtenemos los comentarios de la orden
            order_comment=OrderGetString(ORDER_COMMENT);
            //--- Si los comentarios coinciden
            if(order_comment==comment)
               return(true);
           }
        }
     }
//--- No se ha encontrado la orden con el comentario indicado
   return(false);
  }

En el código de arriba se puede ver que la cantidad total de órdenes se puede obtener con la ayuda de la función incorporable OrdersTotal(). Para obtener la cantidad de órdenes sobre un símbolo en concreto, hay que escribir una función personalizada. La llamaremos OrdersTotalBySymbol():

//+------------------------------------------------------------------+
//| Retorna la cantidad de órdenes sobre el símbolo indicado         |
//+------------------------------------------------------------------+
int OrdersTotalBySymbol(string symbol)
  {
   int   count        =0; // Contador de órdenes
   int   total_orders =0; // Cantidad de órdenes pendientes
//--- Obtenemos la cantidad de órdenes pendientes
   total_orders=OrdersTotal();
//--- Repasamos un ciclo por todas las órdenes
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Si la orden ha sido elegida
      if(OrderGetTicket(i)>0)
        {
         //--- Obtenemos el símbolo de la orden
         GetOrderProperties(O_SYMBOL);
         //--- Si el símbolo de la orden y el símbolo indicado coinciden
         if(ord.symbol==symbol)
            //--- Aumentamos el contador
            count++;
        }
     }
//--- Devolvemos la cantidad de órdenes
   return(count);
  }

Antes de establecer una orden pendiente, hay que calcular para ella el precio y, en caso necesario, los niveles de Stop loss y Take profit. Si tenemos conectado el viraje de posición, necesitaremos funciones personalizadas por separado para recalcular y tener en cuenta los cambios de nivel del trading stop.

Para calcular el precio de una orden pendiente, escribiremos la función CalculatePendingOrder():

//+------------------------------------------------------------------+
//| Calcula el nivel (precio) de una orden pendiente                 |
//+------------------------------------------------------------------+
double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type)
  {
//--- Para el valor calculado de una orden pendiente
   double price=0.0;
//--- Si hay que calcular el valor de una orden SELL STOP
   if(order_type==ORDER_TYPE_SELL_STOP)
     {
      //--- Calculamos el nivel
      price=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level
      //    Si el valor es mayor o igual, devolvemos el valor corregido
      return(price<symb.down_level ? price : symb.down_level-symb.offset);
     }
//--- Si hay que calcular el valor para la orden BUY STOP
   if(order_type==ORDER_TYPE_BUY_STOP)
     {
      //--- Calculamos el nivel
      price=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level
      //    Si el valor es menor o igual, devolvemos el valor corregido
      return(price>symb.up_level ? price : symb.up_level+symb.offset);
     }
//---
   return(0.0);
  }

Más abajo tenemos el código de la función para el cálculo de los niveles de Stop loss y Take profit en una orden pendiente:

//+------------------------------------------------------------------+
//| Calcula el nivel de Stop loss para una orden pendiente           |
//+------------------------------------------------------------------+
double CalculatePendingOrderStopLoss(int symbol_number,ENUM_ORDER_TYPE order_type,double price)
  {
//--- Si es necesario Stop Loss
   if(StopLoss[symbol_number]>0)
     {
      double sl         =0.0; // Para el valor calculado de Stop Loss
      double up_level   =0.0; // Nivel superior de Stop Levels
      double down_level =0.0; // Nivel inferior de Stop Levels
      //--- Si hay que calcular el valor para la orden BUY STOP
      if(order_type==ORDER_TYPE_BUY_STOP)
        {
         //--- Determinamos el umbral inferior
         down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
         //--- Calculamos el nivel
         sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level
         //    Si el valor es mayor o igual, devolvemos el valor corregido
         return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits));
        }
      //--- Si hay que calcular el valor para la orden SELL STOP
      if(order_type==ORDER_TYPE_SELL_STOP)
        {
         //--- Determinamos el umbral superior
         up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
         //--- Calculamos el nivel
         sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits);
         //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level
         //    Si el valor es menor o igual, devolvemos el valor corregido
         return(sl>up_level ? sl : NormalizeDouble(up_level+symb.offset,symb.digits));
        }
     }
//---
   return(0.0);
  }
//+------------------------------------------------------------------+
//| Calcula el nivel de Take Profit para un orden pendiente          |
//+------------------------------------------------------------------+
double CalculatePendingOrderTakeProfit(int symbol_number,ENUM_ORDER_TYPE order_type,double price)
  {
//--- Si es necesario Take Profit
   if(TakeProfit[symbol_number]>0)
     {
      double tp         =0.0; // Para el valor calculado de Take Profit
      double up_level   =0.0; // Nivel superior de Stop Levels
      double down_level =0.0; // Nivel inferior de Stop Levels
      //--- Si hay que calcular el valor para la orden SELL STOP
      if(order_type==ORDER_TYPE_SELL_STOP)
        {
         //--- Determinamos el umbral inferior
         down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits);
         //--- Calculamos el nivel
         tp=NormalizeDouble(price-CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
         //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level
         //    Si el valor es mayor o igual, devolvemos el valor corregido
         return(tp<down_level ? tp : NormalizeDouble(down_level-symb.offset,symb.digits));
        }
      //--- Si hay que calcular el valor para la orden BUY STOP
      if(order_type==ORDER_TYPE_BUY_STOP)
        {
         //--- Determinamos el umbral superior
         up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits);
         //--- Calculamos el nivel
         tp=NormalizeDouble(price+CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits);
         //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level
         //    Si el valor es menor o igual, devolvemos el valor corregido
         return(tp>up_level ? tp : NormalizeDouble(up_level+symb.offset,symb.digits));
        }
     }
//---
   return(0.0);
  }

Para calcular el nivel de parada (precio) de una orden dirigida en dirección contraria y su segumiento tenemos CalculateReverseOrderTrailingStop() y ModifyPendingOrderTrailingStop(). Con el código de estas funciones se podrá familiarizar después.

Código de la función CalculateReverseOrderTrailingStop():

//+----------------------------------------------------------------------------+
//| Calcula el nivel de Trailing Stop para una orden dirigida                  |
//| en dirección contraria                                                     |
//+----------------------------------------------------------------------------+
double CalculateReverseOrderTrailingStop(int symbol_number,ENUM_POSITION_TYPE position_type)
  {
//--- Variables para los cálculos
   double    level       =0.0;
   double    buy_point   =low[symbol_number].value[1];  // Valor Low para Buy
   double    sell_point  =high[symbol_number].value[1]; // Valor High para Sell
//--- Calculamos el nivel para la posición BUY
   if(position_type==POSITION_TYPE_BUY)
     {
      //--- Mínimo de la barra menos la cantidad indicada puntos
      level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Si el nivel calculado es inferior que el nivel inferior del límite (stops level), 
      //    entonces el cálculo se finalizará, devolvemos el valor actual del nivel
      if(level<symb.down_level)
         return(level);
      //--- De otra forma probaremos a calcular a partir del precio de bid
      else
        {
         level=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
         //--- Si el nivel calculado es inferior que el limitador, devolvemos el valor actual del nivel
         //    De otra forma, estableceremos el más cercano posible
         return(level<symb.down_level ? level : symb.down_level-symb.offset);
        }
     }
//--- Calculamos el nivel para la posición SELL
   if(position_type==POSITION_TYPE_SELL)
     {
      // El máximo de la barra más la cantidad indicada de puntos
      level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
      //--- Si el nivel calculado es mayor que el nivel superior de la limitación (stops level), 
      //    entonces el cálculo se finalizará, devolvemos el valor actual de nivel
      if(level>symb.up_level)
         return(level);
      //--- De otra forma probaremos a calcular a partir del precio de ask
      else
        {
         level=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits);
         //--- Si el nivel calculado es superior que el limitador, devolvemos el valor actual del nivel
         //    De otra forma, estableceremos el más cercano posible
         return(level>symb.up_level ? level : symb.up_level+symb.offset);
        }
     }
//---
   return(0.0);
  }

Código de la función ModifyPendingOrderTrailingStop():

//+------------------------------------------------------------------+
//| Cambia el nivel de Trailing Stop para una orden pendiente        |
//+------------------------------------------------------------------+
void ModifyPendingOrderTrailingStop(int symbol_number)
  {
//--- Salimos, si el viraje de posición se encuentra desactivado o el Trailing Stop no ha sido introducido
   if(!Reverse[symbol_number] || TrailingStop[symbol_number]==0)
      return;
//--- 
   double          new_level              =0.0;         // Para el cálculo del nuevo nivel de la orden pendiente
   bool            condition              =false;       // Para comprobar las condiciones de la modificación
   int             total_orders           =0;           // Cantidad total de órdenes pendientes
   ulong           order_ticket           =0;           // Ticket de la orden
   string          opposite_order_comment ="";          // Comentarios de la orden opuesta
   ENUM_ORDER_TYPE opposite_order_type    =WRONG_VALUE; // Tipo de orden

//--- Obtenemos la bandera sobre la presencia/ausencia de posición
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Si no hay posición
   if(!pos.exists)
      return;
//--- Obtenemos cantidad de órdenes pendientes
   total_orders=OrdersTotal();
//--- Obtenemos las propiedades del símbolo
   GetSymbolProperties(symbol_number,S_ALL);
//--- Obtenemos las propiedades de la posición
   GetPositionProperties(symbol_number,P_ALL);
//--- Obtenemos el nivel para Stop Loss
   new_level=CalculateReverseOrderTrailingStop(symbol_number,pos.type);
//--- Pasamos el ciclo por todas las órdenes desde la última a la primera
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Si se ha elegido la orden
      if((order_ticket=OrderGetTicket(i))>0)
        {
         //--- Obtenemos el símbolo de la orden
         GetPendingOrderProperties(O_SYMBOL);
         //--- Obtenemos los comentarios de la orden
         GetPendingOrderProperties(O_COMMENT);
         //--- Obtenemos el precio de la orden
         GetPendingOrderProperties(O_PRICE_OPEN);
         //--- Dependiendo del tipo de posición, comprobaremos que se correspondan las condiciones de modificación de Trailing Stop
         switch(pos.type)
           {
            case POSITION_TYPE_BUY  :
               //--- Si el nuevo valor para la orden es mayor que el valor actual más el salto establecido, entonces la condición se ha cumplido
               condition=new_level>ord.price_open+CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
               //--- Determinamos el tipo y comentarios de la orden pendiente dirigida en dirección contraria, para su comprobación
               opposite_order_type    =ORDER_TYPE_SELL_STOP;
               opposite_order_comment =comment_bottom_order;
               break;
            case POSITION_TYPE_SELL :
               //--- Si el nuevo valor para la orden es menor que el valor actual menos el salto establecido, entonces la condición se ha cumplido
               condition=new_level<ord.price_open-CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point);
               //--- Determinamos el tipo y comentarios de la orden pendiente dirigida en dirección contraria, para su comprobación
               opposite_order_type    =ORDER_TYPE_BUY_STOP;
               opposite_order_comment =comment_top_order;
               break;
           }
         //--- Si las condiciones se cumplen, coinciden el símbolo de la orden y la posición
         //    y también coinciden los comentarios de la orden y la orden dirigida en dirección opuesta
         if(condition && 
            ord.symbol==Symbols[symbol_number] && 
            ord.comment==opposite_order_comment)
           {
            double sl=0.0; // Stop loss
            double tp=0.0; // Take profit
            //--- Obtenemos los niveles de Take Profit y Stop Loss
            sl=CalculatePendingOrderStopLoss(symbol_number,opposite_order_type,new_level);
            tp=CalculatePendingOrderTakeProfit(symbol_number,opposite_order_type,new_level);
            //--- Cambiamos el orden
            ModifyPendingOrder(symbol_number,order_ticket,opposite_order_type,new_level,sl,tp,
                               ORDER_TIME_GTC,ord.time_expiration,ord.price_stoplimit,ord.comment,0);
            return;
           }
        }
     }
  }

A veces puede resultar necesario determinar si ha sido cerrada la posición de Stop loss o Take profit. En nuestro caso se darán situaciones así, por eso escribiremos funciones que determinarán este evento sobre el comentario de la última operación. Para obtener los comentarios de la última operación sobre el símbolo indicado, escribiremos una función por separado, a la que llamaremos GetLastDealComment():

//+------------------------------------------------------------------+
//| Retorna los comentarios de la última operación sobre             |
//| el símbolo indicado                                              |
//+------------------------------------------------------------------+
string GetLastDealComment(int symbol_number)
  {
   int    total_deals  =0;  // Total de operaciones en la lista de la historia elegida
   string deal_symbol  =""; // Símbolo de la operación
   string deal_comment =""; // Comentarios de la operación
//--- Si hemos obtenido el historial de operaciones
   if(HistorySelect(0,TimeCurrent()))
     {
      //--- Obtenemos la cantidad de operaciones en la lista obtenida
      total_deals=HistoryDealsTotal();
      //--- Hacemos un repaso de todas las operaciones en la lista obtenida, desde la última operación a la primera
      for(int i=total_deals-1; i>=0; i--)
        {
         //--- Obtenemos los comentarios de la operación
         deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT);
         //--- Obtenemos el símbolo de la operación
         deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL);
         //--- Si el símbolo de la operación y el símbolo actual son iguales, detenemos el ciclo
         if(deal_symbol==Symbols[symbol_number])
            break;
        }
     }
//---
   return(deal_comment);
  }

Ahora vamos a escribir las funciones que determinan el motivo del cierre de la última posición en el símbolo indicado: es lo más facil del mundo. Más abajo tenemos el código de las funciones IsClosedByTakeProfit() y IsClosedByStopLoss():

//+------------------------------------------------------------------+
//| Retorma el motivo del cierre de la posición según Take Profit    |
//+------------------------------------------------------------------+
bool IsClosedByTakeProfit(int symbol_number)
  {
   string last_comment="";
//--- Obtenemos los comentarios de la última operación en el símbolo indicado
   last_comment=GetLastDealComment(symbol_number);
//--- Si en el comentario tenemos la línea "tp"
   if(StringFind(last_comment,"tp",0)>-1)
      return(true);
//--- Si no tenemos la línea "tp"
   return(false);
  }
//+------------------------------------------------------------------+
//| Retorna el motivo del cierre de posición según Stop Loss         |
//+------------------------------------------------------------------+
bool IsClosedByStopLoss(int symbol_number)
  {
   string last_comment="";
//--- Obtenemos los comentarios de la última operación en el símbolo indicado
   last_comment=GetLastDealComment(symbol_number);
//--- Si en el comentario tenemos la línea "sl"
   if(StringFind(last_comment,"sl",0)>-1)
      return(true);
//--- Si no tenemos la línea "sl"
   return(false);
  }

Otra comprobación que vamos a hacer es si se trata de la última operación en la historia de operaciones en el símbolo indicado. El ticket de la última operación se almacenará en la memoria, para ello hay que declarar la variable a nivel global del programa:

//--- Matriz para comprobar el ticket de la últmia operación en cada símbolo
ulong last_deal_ticket[NUMBER_OF_SYMBOLS];

La función IsLastDealTicket(), ideada para comprobar el ticket de la última operación, tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//| Retorna el evento de la última operación en el símbolo indicado  |
//+------------------------------------------------------------------+
bool IsLastDealTicket(int symbol_number)
  {
   int    total_deals =0;  // Total de operaciones en la lista de la historia elegida
   string deal_symbol =""; // Símbolo de la operación
   ulong  deal_ticket =0;  // Ticket de la operación
//--- Si hemos obtenido la historia de las operaciones
   if(HistorySelect(0,TimeCurrent()))
     {
      //--- Obtenemos la cantidad de operaciones en la lista obtenida
      total_deals=HistoryDealsTotal();
      //--- Hacemos un repsaso de todas las operaciones en la lista obtenida, desde la última a la primera
      for(int i=total_deals-1; i>=0; i--)
        {
         //--- Obtenemos el ticket de la operación
         deal_ticket=HistoryDealGetTicket(i);
         //--- Obtenemos el símbolo de la operación
         deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL);
         //--- Si el Símbolo de la operación y el símbolo actual coinciden, detenemos el ciclo
         if(deal_symbol==Symbols[symbol_number])
           {
            //--- Si los tickets son iguales, salimos
            if(deal_ticket==last_deal_ticket[symbol_number])
               return(false);
            //--- Si los tickets no son iguales, informamos sobre ello y
            else
              {
               //--- Guardamos el ticket de la última operación
               last_deal_ticket[symbol_number]=deal_ticket;
               return(true);
              }
           }
        }
     }
//---
   return(false);
  }

Si la hora actual se sale de los límites del diapasón temporal indicado, la posición se cerrará forzosamente, con independencia de si se halla en pérdidas o ganancias. Para cerrar la posición, crearemos la función ClosePosition():

//+------------------------------------------------------------------+
//| Cierra la posición                                               |
//+------------------------------------------------------------------+
void ClosePosition(int symbol_number)
  {
//--- Vemos si hay posición  
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Si no la hay, salimos
   if(!pos.exists)
      return;
//--- Establecemos el tamaño del deslizamiento en puntos
   trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation));
//--- Si la posición no se ha cerrado, mostraremos un mensaje sobre ello
   if(!trade.PositionClose(Symbols[symbol_number]))
      Print("Error al cerrar la posición: ",GetLastError()," - ",ErrorDescription(GetLastError()));
  }

Cuando se cierra la posición al salir del diapsón temporal comercial, es indispensable eliminar las órdenes pendientes. Para ello escribiremos la función DeleteAllPendingOrders(), que eliminará todas las órdenes pendientes en el símbolo indicado:

//+------------------------------------------------------------------+
//| Elimina todas las ordenes pendientes                             |
//+------------------------------------------------------------------+
void DeleteAllPendingOrders(int symbol_number)
  {
   int   total_orders =0; // Cantidad de órdenes pendientes
   ulong order_ticket =0; // Ticket de la orden
//--- Obtenemos la cantidad de órdenes pendientes
   total_orders=OrdersTotal();
//--- Hacemos un repaso en el ciclo por todas las órdenes
   for(int i=total_orders-1; i>=0; i--)
     {
      //--- Si la orden ha sido elegida
      if((order_ticket=OrderGetTicket(i))>0)
        {
         //--- Obtenemos el símbolo de la orden
         GetOrderProperties(O_SYMBOL);
         //--- Si el símbolo de la orden y el símbolo actual coinciden
         if(ord.symbol==Symbols[symbol_number])
            //--- Eliminamos la orden
            DeletePendingOrder(order_ticket);
        }
     }
  }

Así, en el momento actual ya tenemos listas todas las funciones imprescindibles para construir el esquema. A continuación veremos la ya conocida función TradingBlock(), que ha cambiado significativamente, y la nueva función para gestionar las órdenes pendientes ManagePendingOrders(). En ella se llevará a cabo un control absoluto de la situación actual, con respecto a las órdenes pendientes.

La función TradingBlock() para el esquema actual, tiene este aspecto:

//+------------------------------------------------------------------+
//| Bloque comercial                                                 |
//+------------------------------------------------------------------+
void TradingBlock(int symbol_number)
  {
   double          tp=0.0;                 // Take Profit
   double          sl=0.0;                 // Stop Loss
   double          lot=0.0;                // Volumen para el cálculo de la posición en caso de viraje
   double          order_price=0.0;        // Precio para el establecimiento de la orden
   ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Tipo de orden para la apertura de posición
//--- Si se encuentra fuera del diapasón temporal para el establecimiento de órdenes pendientes
   if(!IsInOpenOrdersTimeRange(symbol_number))
      return;
//--- Vemos si existe una posición abierta del símbolo
   pos.exists=PositionSelect(Symbols[symbol_number]);
//--- Si no hay posición
   if(!pos.exists)
     {
      //--- Obtenemos las propiedades del símbolo
      GetSymbolProperties(symbol_number,S_ALL);
      //--- Corregimos el volumen
      lot=CalculateLot(symbol_number,Lot[symbol_number]);
      //--- Si no hay orden pendiente superior
      if(!CheckPendingOrderByComment(symbol_number,comment_top_order))
        {
         //--- Obtenemos el precio para el establecimiento de la orden pendiente
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP);
         //--- Obtenemos los niveles de Take Profit y Stop Loss
         sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
         tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_BUY_STOP,order_price);
         //--- Establecemos la orden pendiente
         SetPendingOrder(symbol_number,ORDER_TYPE_BUY_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_top_order);
        }
      //--- Si no hay orden pendiente inferior
      if(!CheckPendingOrderByComment(symbol_number,comment_bottom_order))
        {
         //--- Obtenemos el precio para el establecimiento de la orden pendiente
         order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP);
         //--- Obtenemos los niveles de Take Profit y Stop Loss
         sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
         tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_SELL_STOP,order_price);
         //--- Establecemos la orden pendiente
         SetPendingOrder(symbol_number,ORDER_TYPE_SELL_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_bottom_order);
        }
     }
  }

Código de la fucnión ManagePendingOrders() para la gestión de órdenes pendientes:

//+------------------------------------------------------------------+
//| Gestiona las órdenes pendientes                                  |
//+------------------------------------------------------------------+
void ManagePendingOrders()
  {
//--- Repasamos el ciclo por todos los símbolos
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Si no se permite comerciar conforme a ese símbolo, pasaremos al siguiente
      if(Symbols[s]=="")
         continue;
      //--- Vemos si existe alguna posición abierta del símbolo
      pos.exists=PositionSelect(Symbols[s]);
      //--- Si no la hay
      if(!pos.exists)
        {
         //--- Si la última operación ha sido sobre el símbolo actual y
         //    la salida de la posición ha sido en Take Profit o Stop Loss
         if(IsLastDealTicket(s) && 
            (IsClosedByStopLoss(s) || IsClosedByTakeProfit(s)))
            //--- Eliminamos todas las órdenes pendientes sobre el símbolo
            DeleteAllPendingOrders(s);
         //--- Pasamos al siguiente símbolo
         continue;
        }
      //--- Si hay posición
      ulong           order_ticket           =0;           // Tiket de la orden
      int             total_orders           =0;           // Cantidad de órdenes pendientes
      int             symbol_total_orders    =0;           // Cantidad de órdenes pendientes sobre el símbolo indicado
      string          opposite_order_comment ="";          // Comentarios de la orden opuesta
      ENUM_ORDER_TYPE opposite_order_type    =WRONG_VALUE; // Tipo de la orden
      //--- Obtenemos la cantidad total de órdenes pendientes
      total_orders=OrdersTotal();
      //--- Obtenemos la cantidad de órdenes sobre el símbolo indicado
      symbol_total_orders=OrdersTotalBySymbol(Symbols[s]);
      //--- Obtenemos las propiedades del símbolo
      GetSymbolProperties(s,S_ASK);
      GetSymbolProperties(s,S_BID);
      //--- Obtenemos los comentarios de la posición elegida
      GetPositionProperties(s,P_COMMENT);
      //--- Si los comentarios de la posición provienen de la orden superior,
      //    hay que eliminar/modificar/establecer la orden inferior
      if(pos.comment==comment_top_order)
        {
         opposite_order_type    =ORDER_TYPE_SELL_STOP;
         opposite_order_comment =comment_bottom_order;
        }
      //--- Si los comentarios de la posición provienen de la orden inferior,
      //    hay que eliminar/modificar/establecer la orden superior
      if(pos.comment==comment_bottom_order)
        {
         opposite_order_type    =ORDER_TYPE_BUY_STOP;
         opposite_order_comment =comment_top_order;
        }
      //--- Si no hay órdenes pendientes sobre este símbolo
      if(symbol_total_orders==0)
        {
         //--- Si el viraje de posición está conectado, establecemos una orden dirigida en sentido opuesto
         if(Reverse[s])
           {
            double tp=0.0;          // Take Profit
            double sl=0.0;          // Stop Loss
            double lot=0.0;         // Volumen para el cálculo posición en caso de viraje de posición
            double order_price=0.0; // Precio para el establecimiento de la orden
            //--- Obtenemos el precio para el establecimiento de la orden pendiente
            order_price=CalculatePendingOrder(s,opposite_order_type);
            //--- Obtenemos los niveles de Take Profit y Stop Loss
            sl=CalculatePendingOrderStopLoss(s,opposite_order_type,order_price);
            tp=CalculatePendingOrderTakeProfit(s,opposite_order_type,order_price);
            //--- Calculamos un volumen doble
            lot=CalculateLot(s,pos.volume*2);
            //--- Establecemos la orden pendiente
            SetPendingOrder(s,opposite_order_type,lot,0,order_price,sl,tp,ORDER_TIME_GTC,opposite_order_comment);
            //--- Corregimos el Stop Loss con respecto a la orden
            CorrectStopLossByOrder(s,order_price,opposite_order_type);
           }
         return;
        }
      //--- Si hay órdenes pendientes sobre este símbolo, entonces, dependiendo de las condiciones, eliminamos
      //    modificamos la orden dirigida en sentido opuesto
      if(symbol_total_orders>0)
        {
         //--- Hacemos una pasada en el ciclo por todas las órdenes, desde la última a la primera
         for(int i=total_orders-1; i>=0; i--)
           {
            //--- Si se ha elegido la orden
            if((order_ticket=OrderGetTicket(i))>0)
              {
               //--- Obtenemos el símbolo de la orden
               GetPendingOrderProperties(O_SYMBOL);
               //--- Obtenemos el comentario de la orden
               GetPendingOrderProperties(O_COMMENT);
               //--- Si coinciden el símbolo de la orden y de la posición, 
               //    e igualmente coinciden los comentarios de la orden y la orden dirigida en sentido contrario
               if(ord.symbol==Symbols[s] && 
                  ord.comment==opposite_order_comment)
                 {
                  //--- Si el viraje de posición se encuentra desconectado
                  if(!Reverse[s])
                     //--- Eliminamos la orden
                     DeletePendingOrder(order_ticket);
                  //--- Si el viraje de posición se encuentra conectado
                  else
                    {
                     double lot=0.0;
                     //--- Obtenemos las propiedades de la orden actual
                     GetPendingOrderProperties(O_ALL);
                     //--- Obtenemos el volumen de la posición actual
                     GetPositionProperties(s,P_VOLUME);
                     //--- Si la orden ya ha sido modificada, salimos del ciclo
                     if(ord.volume_initial>pos.volume)
                        break;
                     //--- Calculamos un volumen doble
                     lot=CalculateLot(s,pos.volume*2);
                     //--- Cambiamos (establecemos de nuevo) la orden
                     ModifyPendingOrder(s,order_ticket,opposite_order_type,
                                        ord.price_open,ord.sl,ord.tp,
                                        ORDER_TIME_GTC,ord.time_expiration,
                                        ord.price_stoplimit,opposite_order_comment,lot);
                    }
                 }
              }
           }
        }
     }
  }

Queda introducir unos pequeños cambios en el archivo principal del programa. Añadimos el operador de eventos comerciales OnTrade(). En esta función se realizará la comprobación de la situación actual, con respecto a las órdenes pendientes sobre un evento comercial.

//+------------------------------------------------------------------+
//| Procesamiento de eventos comerciales                             |
//+------------------------------------------------------------------+
void OnTrade()
  {
//--- Comprobamos el estado de las órdenes pendientes
   ManagePendingOrders();
  }

Asímismo, la función ManagePendingOrders() se usará en el operador de eventos personalizados OnChartEvent():

//+------------------------------------------------------------------+
//| Operador de eventos personalizados y eventos del gráfico         |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,         // Identificador del evento
                  const long &lparam,   // Parámetro del evento del tipo long
                  const double &dparam, // Parámetro del evento del tipo double
                  const string &sparam) // Parámetro del evento del tipo string
  {
//--- Si se trata de un evento personalizado
   if(id>=CHARTEVENT_CUSTOM)
     {
      //--- Salir, en caso de que esté prohibido comerciar
      if(CheckTradingPermission()>0)
         return;
      //--- Si ha tenido lugar el evento "tick"
      if(lparam==CHARTEVENT_TICK)
        {
         //--- Comprobamos el estado de las órdenes pendientes
         ManagePendingOrders();
         //--- Comprobamos las señales y comerciamos conforme a él
         CheckSignalsAndTrade();
         return;
        }
     }
  }

Y, al fin, los cambios han tenido lugar en la función CheckSignalsAndTrade(). En el código se destacan las líneas con las funciones nuevas que hemos visto en este artículo.

//+------------------------------------------------------------------+
//| Comprueba las señales y comercia conforme al evento "nueva barra"|
//+------------------------------------------------------------------+
void CheckSignalsAndTrade()
  {
//--- Hacemos una pasada por todos los símbolos indicados
   for(int s=0; s<NUMBER_OF_SYMBOLS; s++)
     {
      //--- Si el comercio conforme a este símbolo no está permitido, salimos
      if(Symbols[s]=="")
         continue;
      //--- Si la barra no es nueva, pasamos al siguiente símbolo
      if(!CheckNewBar(s))
         continue;
      //--- Si hay una nueva barra
      else
        {
         //--- Si se encuentra fuera del diapasón temporal
         if(!IsInTradeTimeRange(s))
           {
            //--- Cerramos la posición
            ClosePosition(s);
            //--- Eliminamos todas las órdenes pendientes
            DeleteAllPendingOrders(s);
            //--- Pasamos al siguiente símbolo
            continue;
           }
         //--- Obtenemos los datos de las barras
         GetBarsData(s);
         //--- Comprobamos las condiciones y comerciamos
         TradingBlock(s);
         //--- Si está conectado el viraje de posición
         if(Reverse[s])
            //--- Prolongamos el stop loss para la orden pendiente
            ModifyPendingOrderTrailingStop(s);
         //--- Si está desconectado el viraje de posición
         else
         //--- Prolongamos el stop loss
            ModifyTrailingStop(s);
        }
     }
  }

Ahora ya está todo listo y podemos probar a optimizar los parámetros de este asesor multidivisa. Estableceremos los siguientes ajustes en el Simulador de estrategias:

Dib. 1 - Ajustes del simulador para la optimización de parámetros

Dib. 1 - Ajustes del simulador para la optimización de parámetros

Efectuaremos la optimización de parámetros para la pareja de divisas EURUSD, y después para AUDUSD. En la captura de pantalla se muestran los parámetros que debemos establecer para optimizar EURUSD:

Dib. 2 - Establecimiento de los parámetros para optimizar el asesor multidivisa

Dib. 2 - Establecimiento de los parámetros para optimizar el asesor multidivisa

Después de haber realizado la optimización de los parámetros para la pareja de divisas EURUSD, tendremos que optimizar exactamente los mismos parámetros para AUDUSD. Más abajo tenemos el resultado conjunto de estos símbolos. Los resultados han sido elegidos según el factor máximo de recuperación. Para la simulación sobre los dos símbolos para el lote, se ha establecido el  valor 1.

Dib. 3 - Resultado conjunto sobre los dos símbolos

Dib. 3 - Resultado conjunto sobre los dos símbolos


Conclusión

Con esto damos por terminado el artículo. Si disponemos de funciones ya listas, podemos concentrarnos en el desarrollo del propio concepto de toma de decisiones comerciales. En ese caso, los cambios principales se realizarán en las funciones TradingBlock() y ManagePendingOrders(). Para aquellos que hace poco que han empezado a estudiar programación en MQL5, les recomiendo, para practicar, que prueben a añadir una mayor cantidad de símbolos y a cambiar la lógica del algoritmo comercial.

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

Archivos adjuntos |
755.set (1.59 KB)
Cuentos de robots comerciales: ¿mejor poco, pero mejor? Cuentos de robots comerciales: ¿mejor poco, pero mejor?
Hace dos años, en el artículo "La última cruzada" usted y yo, querido lector, vimos juntos un método (bastante interesante y poco usado en la actualidad) de representación de la información en el mercado, el gráfico de punto y forma. Ahora le propongo intentar escribir un robot comercial, basado en patrones que se pueden ver en los gráficos de punto y forma.
Recetas MQL5 - Desarrollo de un indicador multidivisa para el análisis de la divergencia de precios Recetas MQL5 - Desarrollo de un indicador multidivisa para el análisis de la divergencia de precios
En este artículo veremos el desarrollo de un indicador multidivisa para el análisis de la divergencia de precios en un periodo de tiempo determinado. Ya hemos visto muchos momentos importantes en el anterior artículo sobre la programación de indicadores multidivisa: "Desarrollo de un indicador multidivisa de volatilidad en MQL5". Por eso, esta vez sólo nos detendremos en las funciones nuevas, o bien en aquellas funciones que hayan sufrido cambios significativos. Si es la primera vez que se encuentra con el tema de los indicadores multidivisa, entonces le recomendamos que lea en primer lugar el artículo anterior.
Usar WinInet.dll para el intercambio de datos entre terminales por internet Usar WinInet.dll para el intercambio de datos entre terminales por internet
En este artículo se describen los principios para trabajar con internet mediante las peticiones HTTP y el intercambio de datos entre terminales, usando un servidor intermedio. Se presenta una clase de la librería MqlNet para trabajar con los recursos de internet en el entorno MQL5. Seguir los precios por distintos brokers, intercambiar mensajes con otros traders sin salir del terminal, buscar informaciones en internet; estos son solamente algunos ejemplos que se repasan en este artículo.
Fundamentos de programación en MQL5 - Cadenas de caracteres Fundamentos de programación en MQL5 - Cadenas de caracteres
Este artículo se ocupa de todo lo que se puede hacer con las cadenas de caracteres en el lenguaje MQL5. El artículo puede ser interesante en primer lugar para los principiantes que se han puesto a estudiar la programación en MQL5. Mientras que los programadores experimentados tienen una buena oportunidad de generalizar y sistematizar sus conocimientos.