English Русский 中文 Deutsch 日本語 Português
Asesor Experto multiplataforma: Gestor de órdenes

Asesor Experto multiplataforma: Gestor de órdenes

MetaTrader 5Integración | 4 julio 2017, 16:32
4 518 0
Enrico Lambino
Enrico Lambino

Índice

Introducción

Como ya se decía en los artículos anteriores de esta serie (1, 2, 3), MetaTrader 4 y MetaTrader 5 tienen algunas diferencias que hacen difícil un simple copiado del archivo fuente de MQL4 y su procesamiento por parte del compilador de MQL5. Una de las diferencias más evidentes entre las plataformas consiste en distintos modos de realizar las operaciones comerciales. En este artículo se considera la cuestión de la creación de la clase COrderManager. Solo o junto con otras clases auxiliares, se encargaría de ejecutar las operaciones comerciales, así como de mantener las operaciones en las que ha entrado el Asesor Experto (EA).


Objetivos

El gestor de órdenes de este artículo será capaz de ejecutar las siguientes operaciones:

  1. Cálculo del tamaño del lote
  2. Cálculo de Stop Loss y Take Profit
  3. Diferentes parámetros necesarios para entrar en la operación (plaza de vencimiento, comentarios, números mágicos de las órdenes)
  4. Algunas condiciones previas antes de enviar la orden
  5. Gestión de órdenes y del historial de órdenes

A menudo, es mejor traspasar la parte que se encarga del cálculo del tamaño del lote en el campo del objeto, porque existen varias maneras de calcular el tamaño óptimo del lote de la siguiente operación (dependiendo de la estrategia comercial que se utiliza). Lo mismo se puede decir también sobre el cálculo de los niveles de Stop Loss y Take Profit.

Los demás parámetros necesarios para la apertura de la orden, como el plazo de vencimiento, comentario y los magic, requieren menos complejidad y por eso se procesan perfectamente por el gestor de órdenes personalmente.

Antes de que el EA abra una operación en el mercado, es necesario que se cumplan algunas condiciones previas. Se trata de las señales comerciales que se observan en las condiciones del mercado, limitaciones temporales, así como el número máximo de las operaciones activas en un momento dado y el número máximo de operaciones que puede ser abierto durante el período completo del trabajo del EA (entre OnInit y OnDeinit). Para el gestor de órdenes, las últimas dos condiciones serán incluidas como las condiciones finales antes del envío de las órdenes en el mercado. Eso será realizado de tal manera que durante el trabajo del EA no puedan abrirse dos operaciones similares que repiten una a otra. Por otro lado, las demás condiciones previas pueden ser delegadas a otros componentes del EA, aparte del gestor de órdenes.


Implementación básica

Igual como en el caso con otros objetos de la clase descritos en esta serie de artículos, es necesario encontrar una base común para MQL4 y MQL5 e implementarla en la clase base. Entre tanto, las partes cuya implementación se diferencia en los lenguajes serán implementadas en los descendientes separados de la clase base. Durante el desarrollo de la clase base, tenemos que aclarar los siguientes momentos del procesamiento de la solicitud comercial:

  1. Cómo se diferencia el envío de las solicitudes comerciales
  2. Cómo se diferencian las acciones comerciales a documentar
  3. En MQL5 hay algunas funciones que no tienen ningún equivalente en MQL

Hay algunos momentos que son diferentes en MQL4 y MQL5. Vamos a considerar la función OrderSend (mql4, mql5), tal como se muestra en la documentación para ambas plataformas:

(MQL4)

int  OrderSend(
   string   symbol,              // símbolo
   int      cmd,                 // operación
   double   volume,              // volumen
   double   price,               // precio
   int      slippage,            // deslizamiento
   double   stoploss,            // stop loss
   double   takeprofit,          // take profit
   string   comment=NULL,        // comentario
   int      magic=0,             // magic
   datetime expiration=0,        // plazo de vencimiento de la orden pendiente
   color    arrow_color=clrNONE  // color
   );

(MQL5)

bool  OrderSend(
   MqlTradeRequest&  request,      // estructura de la solicitud
   MqlTradeResult&   result        // estructura de la respuesta
   );

La función tiene un enfoque más simple en MQL4 . En MQL5 la función es más compleja, pero se ha reducido a dos el número de parámetros que contienen los datos (struct) sobre la solicitud y el resultado, respectivamente. Este obstáculo fue descrito en su mayor parte en el artículo anterior que trataba de la importación de algunos componentes de la librería estándar MQL5 a MQL4, en particular, las clases CExpertTrade y CExpertTradeX. De esta manera, nuestro gestor de órdenes simplemente utiliza estas clases para asegurar la compatibilidad entre dos lenguajes a la hora de realizar una solicitud comercial.

Otro aspecto es el modo de procesamiento de la salida de la operación o cancelación de la orden en MetaTrader 4 y MetaTrader 5. No hay gran diferencia en la manera de eliminar las órdenes pendientes, pero hay una diferencia enorme en los modos con los cuales las órdenes de mercado (MQL4) o posiciones (MQL5) se eliminan del mercado. En MQL4, el cierre de una orden de mercado se consigue mediante la llamada a la función OrderClose. En MetaTrader 5, el mismo efecto se realiza con la llamada a la función PositionClose o con el envío de una solicitud comercial para la posición con el mismo volumen que la actual, pero en dirección contraria.

En MetaTrader 5, cada acción comercial se registra. Independientemente de que si es la entrada en la operación, modificación o la salida, esta acción dejará su huella en el historial de trading, y los datos estarán disponibles para el EA. En MetaTrader 4, no es así. Por ejemplo, cuando a una orden pendiente se le asigna el ID, a menudo el mismo ID va a utilizarse durante toda la vida de la orden, incluso si ha sido alcanzado el precio de activación y se ha convertido en la orden de mercado, antes de cerrarse. Para poder ver el desarrollo completo de alguna operación, hay que acceder al registro del EA, lo que puede ser una tarea bastante complicada. Además, los archivos de los logs sirven para la lectura por una persona, y en MQL4 no hay función incorporada que facilite el acceso a esta información para el EA.

En MQL5 hay algunas funciones que simplemente no están disponibles en MQL4. Un ejemplo de eso es el tipo del relleno de la orden. En MQL5 hay las siguientes opciones de relleno del volumen solicitado de la orden:

  • ORDER_FILLING_FOK — cuando el volumen solicitado no puede ser llenado, la orden se cancela
  • ORDER_FILLING_IOC — cuando el volumen solicitado no puede ser llenado, se usa el volumen máximo disponible y el resto se cancela
  • ORDER_FILLING_RETURN — cuando el volumen solicitado no puede ser llenado, se usa el volumen máximo disponible. La orden con el volumen restante se queda en el mercado.

En MetaTrader 4, la solicitud comercial simplemente se rellena o se cancela: en realidad, eso equivale a la opción ORDER_FILLING_FOK. Otros dos tipos del llenado no están disponibles.

Sea como sea, estas políticas del llenado de las órdenes se realizan sólo si el volumen solicitado excede el volumen disponible en el mercado. Eso ocurre no muy a menudo, sobre todo en caso de los ajustes de riesgos bajos y un balance reducido de la cuenta. Es difícil implementar el comportamiento de ORDER_FILLING_IOC y ORDER_FILLING_RETURN en MQL4, o mejor dicho es imposible. La razón principal de eso consiste en que en los EAs no hay manera de determinar qué volumen está disponible en el mercado ahora para una solicitud comercial determinada. Pero si incluso hubiera una manera, esta información sería muy volátil, cambiaría con mucha frecuencia.

De esta manera, con el fin de garantizar la compatibilidad de MQL4 y MQL5, ORDER_FILLING_FOK será el único modo utilizado (como está predefinido en MetaTrader 5). Entretanto, existen eventos cuando el EA calcula el tamaño del lote para la solicitud comercial que supera SYMBOL_VOLUME_MAX, volumen máximo permitido para cualquier operación establecido por el bróker. En MetaTrader 5 eso se realiza automáticamente partiendo la operación en varias operaciones, pero esta posibilidad no está disponible en MetaTrader 4 (en este caso la solicitud comercial simplemente se cancela). Por eso, en el EA multiplataforma es mejor comprobar de antemano la correspondencia de la solicitud al volumen máximo permitido. Se recomienda hacerlo después de recibir o calcular el volumen de la operación, pero antes de enviar la solicitud comercial para la entrada en el mercado con el uso del gestor de órdenes.

En la imagen de abajo se muestra como el gestor de órdenes va a realizar la entrada en la operación:

Diagrama de la entrada

Como se muestra en la imagen, el método se empieza con la preparación de los datos necesarios para la operación. Si la posición está disponible para la apertura y la condiciones previas están satisfechas, el método se pone a abrir la orden. De lo contrario, el proceso se termina. Antes de enviar la solicitud comercial, hay que calcular los valores necesarios. Si la solicitud se confirma, el resultado se comprueba y se crea una nueva instancia de COrder, que luego se añade a la lista de las órdenes/posiciones actuales (m_orders).

Los métodos que realizan los cálculos puros pueden ser encontrados en la clase base. Los métodos que llaman a las funciones que son diferentes en dos lenguajes extienden los métodos base dentro de las clases correspondientes específicas para el lenguaje. Pero las diferencias en la ejecución de las operaciones son muy pocas en este método. Por eso el método de la clase base es exclusivamente virtual, y las implementaciones pueden ser separadas en dos versiones. Vamos a buscar la implementación del método de la clase base de la siguiente manera:

COrder* COrderManagerBase::TradeOpen(const string,ENUM_ORDER_TYPE)
  {
   return NULL;
  }

Cálculo del volumen de trading

Como ya se ha dicho antes, es mejor «delegar» el cálculo del volumen comercial para la siguiente operación a otros objetos de la clase que van a ser miembros de la clase del gestor de órdenes. Este enfoque ya se utiliza en la librería de los expertos de la Librería estándar MQL5. Abajo se muestra el código del método LotSizeCalculate que calcula el volumen de la siguiente operación:

double COrderManagerBase::LotSizeCalculate(const double price,const ENUM_ORDER_TYPE type,const double stoploss)
  {
   if(CheckPointer(m_moneys))
      return m_moneys.Volume(m_symbol.Name(),0,type,stoploss);
   return m_lotsize;
  }

El método comprueba el puntero a la instancia de CMoneys, que simplemente disempeña el papel de un contenedor para los objetos de la Gestión de Capital realizado por el gestor de órdenes (igual que COrders es el contenedor para las instancias de COrder). Estos objetos de la Gestión de Capital serán descritos en un artículo separado. En esta fase de trabajo, es suficiente saber por lo menos que existe un componente separado que calcula el tamaño del lote y que el tamaño calculado del lote será válido. Si la instancia para la Gestión de Capital no ha sido traspasada al gestor de órdenes, entonces el gestor simplemente va a usar el tamaño del lote predefinido a través de su miembro de la clase, m_lotsize.

Cálculo de Stop Loss y Take Profit

Stop Loss y Take Profit se calculan por los métodos StopLossCalculate y TakeProfitCalculate, respectivamente. Los siguientes fragmentos del código muestran cómo ha sido implementada cada uno de estos métodos en el gestor de órdenes:

double COrderManagerBase::StopLossCalculate(const ENUM_ORDER_TYPE type,const double price)
  {
   if(CheckPointer(m_main_stop))
      return m_main_stop.StopLossTicks(type,price);
   return 0;
  }
double COrderManagerBase::TakeProfitCalculate(const ENUM_ORDER_TYPE type,const double price)
  {
   if(CheckPointer(m_main_stop))
      return m_main_stop.TakeProfitTicks(type,price);
   return 0;
  }

El cálculo de los niveles Stop es delegado a los objetos separados de la clase que serán miembros del gestor de órdenes (de eso también va a tratarse en un artículo separado). Los objetos Stop también van a tener su implementación para MetaTrader 4 y MetaTrader 5. Sin embargo, si los punteros al objeto Stop no se traspasan al gestor de órdenes , por defecto Stop Loss/Take Profit calculados será igual a cero (es decir, no habrá SL/TP).

Cierre de la orden o posición

La imagen de abajo muestra como el gestor de órdenes va a cerrar las posiciones y eliminar las órdenes:

Diagrama para la salida

Como se muestra en la imagen, primero el método comprueba si el puntero a la instancia COrder es vigente. Luego pasa a la recepción de las instancias correctas de los objetos de símbolos y operaciones que serán necesarios para procesar la solicitud para la salida. Después, elimina o cierra la orden dependiendo de su tipo. Después del cierre o eliminación de la orden, la instancia también se traspasa de la lista de órdenes activas a la lista del historial del gestor de órdenes (se mete en archivo comprimido). Además el método coloca las banderas que marcan el objeto como cerrado.

Verificación de ajustes

La comprobación de los ajustes del gestor de órdenes se realiza a través del métodos Validate de la clase. Abajo se muestra el código de este método:

bool COrderManagerBase::Validate(void) const
  {
   if(CheckPointer(m_moneys)==POINTER_DYNAMIC)
     {
      if(!m_moneys.Validate())
         return false;
     }
   if(CheckPointer(m_stops)==POINTER_DYNAMIC)
     {
      if(!m_stops.Validate())
         return false;
     }
   return true;
  }

Este código parece al método ValidationSettings que a menudo está presente en algunas clases de la Librería estándar MQL5. Él simplemente llama a los métodos Validate de sus miembros del objeto y devuelve false cada vez que no se pueda realizar la comprobación del objeto (al final, llevando al rechazo de la función OnInit del Asesor Experto o devolviendo INIT_FAILED). El método sirve para la llamada durante la ejecución de la función Initialization del Asesor Experto.

Cálculo del número de operaciones

Bajo el número total de las operaciones se entiende el número total de las transacciones en las que ha entrado el gestor de órdenes, inclusive las que ya se encuentran en el historial, desde el inicio del EA o script. El número total de las operaciones se refiere al número de transacciones actuales en la cuenta, y el historial total de las órdenes se refiere a las transacciones que se encuentran en la lista general del propio historial de órdenes del gestor. De esta manera:

int COrderManagerBase::OrdersTotal(void) const
  {
   return m_orders.Total();
  }
int COrderManagerBase::OrdersHistoryTotal(void) const
  {
   return m_orders_history.Total();
  }
int COrderManagerBase::TradesTotal(void) const
  {
   return m_orders.Total()+m_orders_history.Total()+m_history_count;
  }
En ambos casos, durante la cuenta de las órdenes, usamos la ideología estándar de MetaTrader 4 (cada orden es una posición, independientemente de que si es una orden de mercado o pendiente). Por ejemplo, en la implementación en MQL4, las primeras dos líneas del método TradeOpen tienen el siguiente aspecto:
int trades_total =TradesTotal();
int orders_total = OrdersTotal();

Es importante decir que aquí OrdersTotal se refieren al método específico para la clase, y no para la función OrdersTotal, que está presente tanto en MQL4, como en MQL5. Si en vez de eso se usan sus propias funciones de los lenguajes, OrdersTotal debe invocarse por el operador del área de visibilidad que precede al nombre de la función.

int orders_total = ::OrdersTotal();

Archivamiento de la instancia COrder

Puesto que el gestor de órdenes va a guardar su lista independiente de las operaciones abiertas, formándola igual que Metatrader 4 y Metatrader 5 (pero compatible con ambas plataformas), él debe tener la posibilidad de marcar las instancias COrder como ya colocadas en el historial. En función de que si las órdenes pertenecen al historial o siguen activas en el mercado, se almacenan en las instancias de la clase COrders m_orders o m_orders_history. Como consecuencia, el Asesor Experto multiplataforma tendrá que comprobar para ambas versiones si la operación o la orden ha cerrado o no.

Debido a las diferencias de cómo las plataformas documentan las operaciones abiertas en el mercado, el gestor de órdenes debe guardarlas en su propia lista independiente. Después de una exitosa apertura de la orden, se crea la instancia COrder que al final se añade a m_orders. Poco tiempo después de que la orden o la posición sale del mercado, el gestor de órdenes debe trasladar esta instancia a m_orders_history. El método ArchiveOrder de la clase que va a usarse en ambas versiones se muestra a continuación:

bool COrderManagerBase::ArchiveOrder(COrder *order)
  {
   return m_orders_history.Add(order);
  }

Implementación específica de MQL4

Apertura de la orden o posición

El fragmento del código de abajo demuestra el método TradeOpen del heredero específico de MQL4 de la clase CorderManagerBase:

COrder* COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type)
  {
   int trades_total = TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol = m_symbol_man.Get(symbol);
   if (!CheckPointer(m_symbol))
      return NULL;
   if(!IsPositionAllowed(type))
      return NULL;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      ENUM_ORDER_TYPE ordertype = type;
      double price=PriceCalculate(ordertype);
      double sl=0,tp=0;
      if(CheckPointer(m_main_stop)==POINTER_DYNAMIC)
        {
         sl = m_main_stop.StopLossCustom()?m_main_stop.StopLossCustom(symbol,type,price):m_main_stop.StopLossCalculate(symbol,type,price);
         tp = m_main_stop.TakeProfitCustom()?m_main_stop.TakeProfitCustom(symbol,type,price):m_main_stop.TakeProfitCalculate(symbol,type,price);
        }
      double lotsize=LotSizeCalculate(price,type,sl);
      ulong ticket = SendOrder(type,lotsize,price,sl,tp);
      if (ticket>0)
      {
         if (OrderSelect((int)ticket,SELECT_BY_TICKET))
            return m_orders.NewOrder(OrderTicket(),OrderSymbol(),OrderMagicNumber(),(ENUM_ORDER_TYPE)::OrderType(),::OrderLots(),::OrderOpenPrice());
      }            
     }
   return NULL;
  }

La función recibe dos parámetros: nombre del símbolo o instrumento y el tipo de la orden a abrir. Ella se empieza con la obtención de los valores necesarios para el procesamiento de la solicitud: el número total de operaciones, número de órdenes y el objeto del símbolo de la orden (mostrado en en el primer argumento del método).

Si se cumplen dos condiciones previas (número máximo de órdenes y número máximo de operaciones), el método empieza a calcular los niveles de Stop Loss y Take Profit, así como el volumen de la operación (usando los miembros especiales del objeto). Finalmente, envía la orden y después de que esta operación se complete con éxito, crea la nueva instancia de COrder y la guarda en la lista de órdenes activas (dentro del gestor de órdenes).

Cierre de la orden o posición

El siguiente fragmento del código demuestra el método CloseOrder del heredero específico de MQL4 de la clase COrferManagerBase:

bool COrderManager::CloseOrder(COrder *order,const int index=-1)
  {
   bool closed=true;
   if(CheckPointer(order)==POINTER_DYNAMIC)
     {
      if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),order.Symbol())!=0)
         m_symbol=m_symbol_man.Get(order.Symbol());
      if(CheckPointer(m_symbol))
         m_trade=m_trade_man.Get(order.Symbol());
      if(order.Volume()>0)
        {
         if(order.OrderType()==ORDER_TYPE_BUY || order.OrderType()==ORDER_TYPE_SELL)
            closed=m_trade.OrderClose((ulong)order.Ticket());
         else
            closed=m_trade.OrderDelete((ulong)order.Ticket());
        }
      if(closed)
        {
         int idx = index>=0?index:FindOrderIndex(GetPointer(order));
         if(ArchiveOrder(m_orders.Detach(idx)))
           {
            order.Close();
            order.Volume(0);
           }
        }
     }
   return closed;
  }

Como veremos más adelante, la versión de MQL4 es más simple que MQL5, en primer lugar, porque tiene sólo un modo del cálculo del margen (cobertura o hedging). Incluso si la cobertura está deshabilitada por el bróker, el proceso del cierre de la orden quedará sin alterar: la orden pendiente se elimina, la orden de mercado se cierra.

La función recibe dos parámetros: el objeto de la orden y su índice en la lista de las órdenes activas/posiciones abiertas. Si el puntero al objeto de la clase Order es válido, entonces obtenemos la instancia correcta de CExpertTradeX y CSymbolInfo para cerrar la orden, y luego la colocamos en el historial de la terminal mediante la llamada a la función correspondiente.

Una vez cerrada la orden o la posición, el objeto COrder requerirá la actualización. Primero se elimina de la lista de órdenes activas, y luego se desplaza al final de la lista de las órdenes en el historial. Luego el objeto se marca como cerrado y su volumen se pone a cero.

El segundo argumento del método de la clase recibe un parámetro opcional (índice). Es necesario para acelerar el procesamiento de la solicitud en caso si el índice de la instancia en el array de órdenes ya se sabe de antemano (es una situación típica ya que a menudo se necesita procesar las órdenes en el ciclo). En caso cuando el índice no se sabe, el método se invoca sólo por un argumento. Este método a su vez llamará a otro método de la clase, FindOrderIndex, que será responsable de determinar la posición de la instancia COrder en el array de las órdenes.


Implementación específica de MQL5

Apertura de la orden o posición

El siguiente fragmento del código demuestra el método TradeOpen del descendiente específico MQL5 de COrderManagerBase:

COrder* COrderManager::TradeOpen(const string symbol,ENUM_ORDER_TYPE type)
  {
   double lotsize=0.0,price=0.0;
   int trades_total =TradesTotal();
   int orders_total = OrdersTotal();
   m_symbol=m_symbol_man.Get(symbol);
   if(!IsPositionAllowed(type))
      return NULL;
   if(m_max_orders>orders_total && (m_max_trades>trades_total || m_max_trades<=0))
     {
      price=PriceCalculate(type);
      lotsize=LotSizeCalculate(price,type,m_main_stop==NULL?0:m_main_stop.StopLossCalculate(symbol,type,price));
      if (SendOrder(type,lotsize,price,0,0))
         return m_orders.NewOrder((int)m_trade.ResultOrder(),m_trade.RequestSymbol(),    (int)m_trade.RequestMagic(),m_trade.RequestType(),m_trade.ResultVolume(),m_trade.ResultPrice());
     }      
   return NULL;
  }

Como podemos ver la implementación no se diferencia mucho de su análogo en MQL4. No obstante, este código contiene una diferencia clave: es que no necesitamos obtener los valores para los niveles de Stop Loss y Take Profit. La causa es que el comportamiento de los niveles Stop es diferente en MetaTrader 5 en comparación con su comportamiento en la plataforma anterior. El programador que tiene experiencia de trabajo con EAs escritos en MQL5 se dará cuenta de que en esta librería vamos a usar las órdenes pendientes como análogo de los niveles Stop en MQL4, establecidos en el lado del bróker.

Cierre de la orden o posición

El siguiente fragmento del código demuestra el método CloseOrder del heredero específico de MQL5 de la clase COrderManagerBase:

bool COrderManager::CloseOrder(COrder *order,const int index=-1)   {    bool closed=true;    COrderInfo ord;    if(!CheckPointer(order))       return true;    if(order.Volume()<=0)       return true;    if(!CheckPointer(m_symbol) || StringCompare(m_symbol.Name(),order.Symbol())!=0)       m_symbol=m_symbol_man.Get(order.Symbol());    if(CheckPointer(m_symbol))       m_trade=m_trade_man.Get(order.Symbol());    if(ord.Select(order.Ticket()))    {       closed=m_trade.OrderDelete(order.Ticket());    }      else      {       ResetLastError();       if(IsHedging())       {          closed=m_trade.PositionClose(order.Ticket());       }         else         {          if(COrder::IsOrderTypeLong(order.OrderType()))             closed=m_trade.Sell(order.Volume(),0,0,0);          else if(COrder::IsOrderTypeShort(order.OrderType()))             closed=m_trade.Buy(order.Volume(),0,0,0);         }      }    if(closed)      {

      if(ArchiveOrder(m_orders.Detach(index)))         {          order.Close();          order.Volume(0);         }      }    return closed;   }

Al cerrar una orden o posición en MQL5, tenemos que considerar el modo del margen (compensación (netting) o cobertura (hedging)). Pero primero nos hace falta determinar qué es lo que se está cerrando: una orden MQL5 o una posición MQL5. Para conseguirlo, podemos usar las fucniones OrderSelect y HistoryOrderSelect. Pero para reducir el código de este método y simplificar el proceso, simplemente usamos la clase COrderInfo desde la Librería estándar MQL5.

La orden aparece en MQL5 como resultado de una solicitud comercial, lo que a menudo provoca la aparición de una operación o un conjunto de operaciones (equivale aproximadamente a una orden de mercado en MetaТrader 4). Además, si la solicitud no se ejecuta en el modo Instant Execution, se refiere a una orden pendiente (en contraposición a la situación en MetaТrader 4, donde las órdenes pueden ser pendientes o de mercado). Ahora, para determinar un elemento para salir del mercado, el método primero comprueba si se trata de una orden pendiente, usando COrderInfo. Si es así, la orden pendiente se elimina. Si la comprobación a través de COrderInfo falla, nos aseguramos de que es una orden de mercado o una posición. Para el modo de hadging, la posición simplemente se cierra a través de la función PositionClose. En el modo de netting, simplemente «neutralizamos» la posición abriendo una posición opuesta con el mismo volumen.

Creación de una instancia de COrder

Ya hemos considerado cómo el gestor de órdenes abre y cierra las operaciones. En el artículo anterior, ya hemos conocido la manera de modificar la clase CExpertTrade para que se haga compatible con ambas plataformas comerciales. Ahora, vemos que hay diferencia de como están implementados el Stop Loss y el Take Profit en ambas plataformas, y que eso se procesa por el gestor de órdenes con mucha dificultad. El resto del proceso se refiere a la inicialización de la instancia de COrder, que se invoca por el método NewOrder de la clase COrders. Abajo se muestra el código del método Init de la clase COrdersBase:

COrder* COrdersBase::NewOrder(const ulong ticket,const string symbol,const int magic,const ENUM_ORDER_TYPE type,const double volume,const double price)
  {
   COrder *order=new COrder(ticket,symbol,type,volume,price);
   if(CheckPointer(order)==POINTER_DYNAMIC)
      if(InsertSort(GetPointer(order)))
      {  
         order.Magic(magic);
         order.Init(GetPointer(this),m_stops);
         return order;
      }  
   return NULL;
  }

Como podemos ver, el método Init de la clase COrder recibe un cierto objeto personalizado (CStops) como el segundo argumento. Es un contenedor para los objetos Stop (como m_main_stop mostrado antes). Este objeto de la clase será analizado en un artículo separado.

Modificación de la orden o posición

Hasta ahora no hemos mostrado ningún código que permite al gestor de órdenes modificar las posiciones existentes. Eso se puede delegar a otro objeto Stop (CStop y CorderStop), que será discutido en un artículo separado. Estos objetos tendrán que encargarse de cualquier actualización o modificación de los niveles Stop de la posición, así como de la coordinación con el objeto de la clase COrder a la que pertenecen.

En MetaTrader 4, el precio de la apertura de la orden pendiente puede cambiarse tantas veces que sea necesario. Pero eso no funciona en MetaTrader 5. Esta vez, la versión MQL5 desempeña el papel de un componente limitador, por eso vamos a adaptarla como un etándar. La modificación de una orden pendiente requerirá la eliminación de la orden pendiente existente y la creación de una orden nueva con características modificadas.

Ejemplo

Como ejemplo, implementamos un EA usando los objetos de la clase ya descritos en esta serie de artículos. Después de crear el archivo fuente del Asesor Experto en MetaEditor, empezaremos con el acceso a la librería:

#include "MQLx\Base\OrderManager\OrderManagerBase.mqh"

Nótese que en esta declaración usamos las commillas en vez de "<" y ">". Vamos a colocar la librería en la misma carpeta que el archivo del código fuente del EA.

Para este EA vamos a necesitar por lo menos tres punteros que deben declararse a nivel global en el programa: son COrderManager, CSymbolInfo y CSymbolManager:

COrderManager *order_manager;
CSymbolManager *symbol_manager;
CSymbolInfo *symbol_info;

Dentro de la función OnInit, tendremos que inicializar estos tres punteros, sobre todo para la instancia CSymbolInfo, a la que es necesario asignar un determinado nombre del instrumento durante la inicialización.

int OnInit()
  {
//---
   order_manager = new COrderManager();
   symbol_manager = new CSymbolManager();
   symbol_info = new CSymbolInfo();
   if (!symbol_info.Name(Symbol()))
   {
      Print("symbol not set");
      return (INIT_FAILED);
   }   
   symbol_manager.Add(GetPointer(symbol_info));   
   order_manager.Init(symbol_manager,NULL);
//---
   return(INIT_SUCCEEDED);
  }

Dentro de la función OnDeinit, tendremos que eliminar estos tres punteros para no «coman» la memoria (por lo menos dentro de la plataforma comercial):

void OnDeinit(const int reason)
  {
//---
   delete symbol_info;
   delete symbol_manager;
   delete order_manager;
  }

En la función OnTick, tenemos que implementar la estrategia actual. En este ejemplo el EA va a usar un método simple de detectar una barra nueva (según los resultados de la comprobación del número de las barras en el gráfico). El número de las barras anteriores debe guardarse en una variable estática (o en una variable global). Lo mismo es cierto para la variable direction que va a usarse para almacenar la dirección anterior en la que el EA ha abierto la operación (o cero, si es la primera operación). Sin embargo, puesto que la función para contar las barras en el gráfico es diferente en dos versiones del lenguaje, tenemos que dividir la implementación de la siguiente manera:

static int bars = 0;
static int direction = 0;
int current_bars = 0;
#ifdef __MQL5__
   current_bars = Bars(NULL,PERIOD_CURRENT);
#else 
   current_bars = Bars;
#endif

Para la versión MQL4, simplemente usamos la variable predefinida Bars (también se puede usar la llamada a la función iBars). Por otro lado, usamos la llamada a la función Bars para la versión MQL5.

El fragmento del código de abajo implementa el comportamiento actual del EA que podemos observar. Si hay una discrepancia entre la barra anterior y la actual, el EA empieza la inicialización de las cotizaciones de los símbolos (CSymbolInfo), con el fin de usarlas para las siguientes acciones. Luego se comprueba si hay una operación anterior para el cierre. Si hay una, el EA la cierra y procede a procesar otras operaciones, basándose en la dirección anterior de la entrada. El código se termina con la actualización del número de las barras en el Asesor Experto.  

if (bars<current_bars)
   {   
      symbol_info.RefreshRates();
      COrder *last = order_manager.LatestOrder();
      if (CheckPointer(last) && !last.IsClosed())
         order_manager.CloseOrder(last);
      if (direction<=0)
      {
         Print("Entering buy trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_BUY,symbol_info.Ask());
         direction = 1;
      }
      else
      {
         Print("Entering sell trade..");
         order_manager.TradeOpen(Symbol(),ORDER_TYPE_SELL,symbol_info.Bid());
         direction = -1;
      }   
      bars = current_bars;
   }

Para estar seguro de que se utiliza el mismo código en la versión inicial del EA y en todas las versiones posteriores, vamos a mover el código escrito para el momento actual al archivo de cabecera, y luego vamos a referirse a él en el archivo fuente principal (tanto en la versión MQL4, como en MQL5) Ambos archivos fuente (test_ordermanager.mq4 o test_ordermanager.mq5, en función de la versión de la plataforma) van a tener una línea del código que se refiere al archivo de cabecera principal:

#include "test_ordermanager.mqh"

Las tablas de abajo se muestran los resultados del inicio del EA en MetaTrader 4, así como en los modos de compensación (netting) y de cobertura (hedging) de MetaТrader 5, y según vayan apareciendo, en los informes correspondientes del Probador de Estrategias. Para ahorrar el espacio, en este artículo se muestran sólo las diez primeras operaciones (puede encontrar el informe completo en los archivos adjuntos).

MT4:

# Hora Tipo Orden Volumen Precio S / L T / P Beneficio Balance
1. 2017.01.02 00:00 Buy 1. 0.10 1.05102 0.00000 0.00000
2 2017.01.02 01:00 Close 1. 0.10 1.05172 0.00000 0.00000 7.00 10007.00
3 2017.01.02 01:00 Sell 2 0.10 1.05172 0.00000 0.00000
4 2017.01.02 02:00 Filled 2 0.10 1.05225 0.00000 0.00000 -5.30 10001.70
5 2017.01.02 02:00 Buy 3 0.10 1.05225 0.00000 0.00000
6 2017.01.02 03:00 Close 3 0.10 1.05192 0.00000 0.00000 -3.30 9998.40
7 2017.01.02 03:00 Sell 4 0.10 1.05192 0.00000 0.00000
8 2017.01.02 04:00 Close 4 0.10 1.05191 0.00000 0.00000 0.10 9998.50
9 2017.01.02 04:00 Buy 5 0.10 1.05191 0.00000 0.00000
10 2017.01.02 05:00 Close 5 0.10 1.05151 0.00000 0.00000 -4.00 9994.50
11 2017.01.02 05:00 Sell 6 0.10 1.05151 0.00000 0.00000
12 2017.01.02 06:00 Close 6 0.10 1.05186 0.00000 0.00000 -3.50 9991.00
13 2017.01.02 06:00 Buy 7 0.10 1.05186 0.00000 0.00000
14 2017.01.02 07:00 Close 7 0.10 1.05142 0.00000 0.00000 -4.40 9986.60
15 2017.01.02 07:00 Sell 8 0.10 1.05142 0.00000 0.00000
16 2017.01.02 08:00 Close 8 0.10 1.05110 0.00000 0.00000 3.20 9989.80
17 2017.01.02 08:00 Buy 9 0.10 1.05110 0.00000 0.00000
18 2017.01.02 09:00 Close 9 0.10 1.05131 0.00000 0.00000 2.10 9991.90
19 2017.01.02 09:00 Sell 10 0.10 1.05131 0.00000 0.00000
20 2017.01.02 10:00 Close 10 0.10 1.05155 0.00000 0.00000 -2.40 9989.50


MT5 (netting):

Hora de apertura Orden Símbolo Tipo Volumen Precio S / L T / P Hora Estado Comentario
2017.01.02 00:00:00 2 EURUSD Buy 0.10 / 0.10 1.05140 2017.01.02 00:00:00 llenado
2017.01.02 01:00:00 3 EURUSD Sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 llenado
2017.01.02 01:00:00 4 EURUSD Sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 llenado
2017.01.02 02:00:00 5 EURUSD Buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 llenado
2017.01.02 02:00:00 6 EURUSD Buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 llenado
2017.01.02 03:00:00 7 EURUSD Sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 llenado
2017.01.02 03:00:00 8 EURUSD Sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 llenado
2017.01.02 04:00:00 9 EURUSD Buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 llenado
2017.01.02 04:00:00 10 EURUSD Buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 llenado
2017.01.02 05:00:00 11 EURUSD Sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 llenado
2017.01.02 05:00:00 12 EURUSD Sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 llenado
2017.01.02 06:00:00 13 EURUSD Buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 llenado
2017.01.02 06:00:00 14 EURUSD Buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 llenado
2017.01.02 07:00:00 15 EURUSD Sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 llenado
2017.01.02 07:00:00 16 EURUSD Sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 llenado
2017.01.02 08:00:00 17 EURUSD Buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 llenado
2017.01.02 08:00:00 18 EURUSD Buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 llenado
2017.01.02 09:00:00 19 EURUSD Sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 llenado
2017.01.02 09:00:00 20 EURUSD Sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 llenado
2017.01.02 10:00:00 21 EURUSD Buy 0.10 / 0.10 1.05164 2017.01.02 10:00:00 llenado


MT5 (hedging):














Hora de apertura Orden Símbolo Tipo Volumen Precio S / L T / P Hora Estado Comentario
2017.01.02 00:00:00 2 EURUSD Buy 0.10 / 0.10 1.05140 2017.01.02 00:00:00 llenado
2017.01.02 01:00:00 3 EURUSD Sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 llenado
2017.01.02 01:00:00 4 EURUSD Sell 0.10 / 0.10 1.05172 2017.01.02 01:00:00 llenado
2017.01.02 02:00:00 5 EURUSD Buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 llenado
2017.01.02 02:00:00 6 EURUSD Buy 0.10 / 0.10 1.05293 2017.01.02 02:00:00 llenado
2017.01.02 03:00:00 7 EURUSD Sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 llenado
2017.01.02 03:00:00 8 EURUSD Sell 0.10 / 0.10 1.05192 2017.01.02 03:00:00 llenado
2017.01.02 04:00:00 9 EURUSD Buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 llenado
2017.01.02 04:00:00 10 EURUSD Buy 0.10 / 0.10 1.05234 2017.01.02 04:00:00 llenado
2017.01.02 05:00:00 11 EURUSD Sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 llenado
2017.01.02 05:00:00 12 EURUSD Sell 0.10 / 0.10 1.05151 2017.01.02 05:00:00 llenado
2017.01.02 06:00:00 13 EURUSD Buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 llenado
2017.01.02 06:00:00 14 EURUSD Buy 0.10 / 0.10 1.05230 2017.01.02 06:00:00 llenado
2017.01.02 07:00:00 15 EURUSD Sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 llenado
2017.01.02 07:00:00 16 EURUSD Sell 0.10 / 0.10 1.05142 2017.01.02 07:00:00 llenado
2017.01.02 08:00:00 17 EURUSD Buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 llenado
2017.01.02 08:00:00 18 EURUSD Buy 0.10 / 0.10 1.05169 2017.01.02 08:00:00 llenado
2017.01.02 09:00:00 19 EURUSD Sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 llenado
2017.01.02 09:00:00 20 EURUSD Sell 0.10 / 0.10 1.05131 2017.01.02 09:00:00 llenado
2017.01.02 10:00:00 21 EURUSD Buy 0.10 / 0.10 1.05164 2017.01.02 10:00:00 llenado


Obsérvese que los resultados de la prueba en los modos de cobertura y compensación en MetaTrader 5 han salido iguales. A pesar de que la implementación base es igual, la diferencia consiste en que, en el modo de compensación, la posición se neutraliza mediante la apertura de una posición opuesta del mismo volumen, mientras que en el modo de cobertura, la posición se cierra de la manera a la que la mayoría de los traders están acostumbrados en MetaTrader 4. En el modo de cobertura, podemos ver los mensajes parecidos a los que se muestran a continuación:

PE      0       16:19:15.747    Trade   2017.01.02 01:00:00   instant sell 0.10 EURUSD at 1.05172, close #2 (1.05172 / 1.05237 / 1.05172)
GP      0       16:19:15.747    Trades  2017.01.02 01:00:00   deal #3 sell 0.10 EURUSD at 1.05172 done (based on order #3)
DS      0       16:19:15.747    Trade   2017.01.02 01:00:00   deal performed [#3 sell 0.10 EURUSD at 1.05172]

Fíjese en la declaración que dice "close #2" a la derecha en la primera línea. El modo de cobertura indica cuál de las operaciones debe ser neutralizada (cerrada). En el modo de compensación, en este caso veremos sólo el mensaje parecido a lo siguiente:

PG      0       16:20:51.958    Trade   2017.01.02 01:00:00   instant sell 0.10 EURUSD at 1.05172 (1.05172 / 1.05237 / 1.05172)
MQ      0       16:20:51.958    Trades  2017.01.02 01:00:00   deal #3 sell 0.10 EURUSD at 1.05172 done (based on order #3)
KN      0       16:20:51.958    Trade   2017.01.02 01:00:00   deal performed [#3 sell 0.10 EURUSD at 1.05172]

En este modo, es menos evidente si la operación ha sido ejecutada para la apertura de una orden nueva, o simplemente para el cierre de la posición existente.

Vista general de la estructura

La clase COrderManager es uno de los objetos más complicados que serán descritos en esta serie de artículos. Para dar una idea de cómo será la versión final del gestor de órdenes con sus miembros de objetos, analizaremos el siguiente diagrama.

Vista general de la estructura del gestor de órdenes

Para ser más claro, el gestor de órdenes va a contener dos instancias de COrders (actual e histórico), que van a servir de contenedores de las órdenes introducidas en el mercado por el gestor de órdenes (COrder). Cada una de estas órdenes puede contener también los niveles Stop (uno o varios), y cada de estos niveles puede tener sus propios métodos del trailing (uno o varios). A pesar de que la mayoría de los EAs no necesitan ese grado de complejidad, esta estructura será útil en algunas estrategias, especialmente las que trabajan con niveles múltiples de soporte y resistencia. Estos miembros de la clase serán considerados en las siguientes artículos de la serie.

Conclusión

En este artículo, hemos discutido la clase COrderManager que se encarga de gestionar las operaciones comerciales del EA. La clase COrderManager ha sido desarrollada para poder trabajar tanto con MQL4, como con MQL5. Así que los traders y los desarrolladores que diseñan los EAs pueden persuadirse de la compatibilidad multiplataforma del código que ellos escriben en el archivo fuente principal o en el archivo de cabecera.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/2961

Archivos adjuntos |
Asesor Experto multiplataforma: Señales Asesor Experto multiplataforma: Señales
En este artículo, se discuten las clases CSignal y CSignals que serán utilizadas en los Asesores Expertos multiplataforma. Han sido analizadas las diferencias entre MQL4 y MQL5 respecto a la organización de los datos necesarios para evaluar las señales comerciales obtenidas. Como resultado, tenemos el código compatible con los compiladores de ambas versiones.
Ejemplo del indicador que construye las líneas de soporte y resistencia Ejemplo del indicador que construye las líneas de soporte y resistencia
En este artículo se muestra el ejemplo de la implementación del indicador para construir las líneas de soporte y resistencia a base las condiciones formalizadas. Usted podrá no sólo aplicar el indicador, sino también comprenderá qué fácil implementar eso. Ahora Usted podrá formular personalmente las condiciones para construir las líneas que considere necesarias, haciendo pequeñas modificaciones en el código del indicador a su medida.
Creación de indicadores personalizados usando la clase CCanvas Creación de indicadores personalizados usando la clase CCanvas
En el artículo se analiza un ejemplo de creación de indicadores de dibujado personalizados con la ayuda de primivitas gráficas de la clase CCanvas.
Clasificador bayesiano ingenuo para las señales de un conjunto de indicadores Clasificador bayesiano ingenuo para las señales de un conjunto de indicadores
En el artículo se analiza la aplicación de la fórmula bayesiana para aumentar la fiabilidad de los sistemas comerciales usando las señales de varios indicadores independientes. Los cálculos teóricos se comprueban con la ayuda de un sencillo experto universal, adaptable para trabajar con indicadores aleatorios.