English Русский 中文 Deutsch 日本語 Português
Experto comercial universal: trabajando con órdenes pendientes y cobertura (parte 5)

Experto comercial universal: trabajando con órdenes pendientes y cobertura (parte 5)

MetaTrader 5Ejemplos | 28 junio 2016, 12:48
1 678 1
Vasiliy Sokolov
Vasiliy Sokolov

Índice


Introducción

La serie de artículos "Experto comercial universal" continúa describiendo la funcionalidad de CStrategy, un motor comercial especial con cuya ayuda se puede escribir con bastante facilidad un experto comercial que posea incluso una lógica bastante compleja. El conjunto de clases de CStrategy se desarrolla y perfecciona de forma activa. Entre los componentes del motor comercial han aparecido nuevos algoritmos que hacen el comercio aún más sencillo y efectivo. Esto es en gran parte gracias a los muchos usuarios que leen esta serie de artículos y hacen preguntas, tanto a través del sistema de mensajes personales de esta página, como en la sección de discusiones generales sobre este material. Una de las preguntas más frecuentes estaba relacionada con el trabajo con órdenes pendientes. Realmente, en las anteriores partes del artículo no se habló de este tema, y el propio CStrategy tampoco proporcionaba mecanismos cómodos para trabajar con órdenes pendientes. Este artículo introduce complementos sustanciosos en el motor comercial CStrategy. Gracias a estos cambios, CStrategy ha comenzado a proponer nuevos instrumentos para trabajar con órdenes pendientes. Precisamente sobre ellos vamos a hablar más abajo.

Además, las últimas versiones de MetaTrader 5 han comenzado a dar soporte al comercio en direcciones opuestas en las cuentas con cobertura (lea el artículo "Se ha añadido a MetaTrader 5 el sistema de cobertura de registro de posiciones"). Esto también ha requerido introducir determinados cambios en el código de CStrategy, para que las últimas versiones de este complejo puedan funcionar correctamente en los nuevos tipos de cuenta. Solo se ha requerido hacer unos pocos cambios en el código para dar soporte a la posibilidad de cobertura, lo que indica que inicialmente se seleccionó el enfoque correcto: ningún cambio o ampliación, no importa lo global que sea, conduce a una pérdida de rendimiento del motor comercial. Al contrario, con los nuevos instrumentos MetaTrader 5, tales como el comercio en direcciones contrarias, surgen muchas oportunidades interesantes, que serán a buen seguro implementadas en las nuevas versiones de CStrategy.

Además, CStrategy ahora da soporte a métodos intuitivos, comprensibles y demandados para obtener los precios Ask, Bid y Last actuales. El primer apartado de este material se dedica a la descripción de esos métodos.

En este artículo hay una gran cantidad de material. Esto está relacionado con los múltiples cambios tanto en el propio CStrategy, como en MetaTrader 5 durante este periodo. El presente artículo rellena muchos espacios en blanco que quedaron vacíos en anteriores artículos. Espero que le resulte interesante a los lectores.


Acceso a los precios actuales a través de los métodos Ask, Bid y Last. Redefiniendo la función Digits

Con bastante frecuencia el tráder tiene que obtener acceso a los precios actuales. Las anteriores versiones de CStrategy no contenían métodos de acceso a semejantes datos. En lugar de esto se entendía que el usuario utilizaría las funciones estándar para solicitar los precios actuales. Así, por ejemplo, para concer el precio Ask actual, había que escribir el código siguiente:

double ask = SymbolInfoDouble(ExpertSymbol(), SYMBOL_ASK);
int digits = SymbolInfoInteger(ExpertSymbol(), SYMBOL_DIGITS);
ask = NormalizeDouble(ask, digits);

Notemos que aunque la obtención del precio ask requiere usar solo una función SymbolInfoDouble, para que el funcionamiento sea fiable es necesario también normalizar el valor obtenido hasta que alcance la precisión del instrumento actual. Por eso la obtención del precio ask factual en realidad necesita una gran cantidad de acciones. Lo mismo ocurre con los algoritmos de obtención de los precios Bid y Last.

Para simplificar el trabajo de los usuarios, en la clase CStrategy han sido introducidos tres métodos: Ask(), Bid() y Last(). Cada uno de ellos recibe el precio correspondiente y lo normaliza de acuerdo con el instrumento actual: 

//+------------------------------------------------------------------+
//| Retorna el precio Ask.                                             |
//+------------------------------------------------------------------+
double CStrategy::Ask(void)
  {
   double ask = SymbolInfoDouble(ExpertSymbol(), SYMBOL_ASK);
   int digits = (int)SymbolInfoInteger(ExpertSymbol(), SYMBOL_DIGITS);
   ask = NormalizeDouble(ask, digits);
   return ask;
  }
//+------------------------------------------------------------------+
//| Retorna el precio Bid.                                             |
//+------------------------------------------------------------------+
double CStrategy::Bid(void)
  {
   double bid = SymbolInfoDouble(ExpertSymbol(), SYMBOL_BID);
   int digits = (int)SymbolInfoInteger(ExpertSymbol(), SYMBOL_DIGITS);
   bid = NormalizeDouble(bid, digits);
   return bid;
  }
//+------------------------------------------------------------------+
//| Retorna el precio Last.                                             |
//+------------------------------------------------------------------+
double CStrategy::Last(void)
  {
   double last = SymbolInfoDouble(ExpertSymbol(), SYMBOL_LAST);
   int digits = (int)SymbolInfoInteger(ExpertSymbol(), SYMBOL_DIGITS);
   last = NormalizeDouble(last, digits);
   return last;
  }

Estos métodos han sido definidos en una nueva versión de la clase CStrategy y ahora están disponibles para su uso directamente en las clases de estrategia derivadas. En el futuro vamos a utilizarlos en ejemplos de escritura de estrategias.

Aparte de organizar el acceso a los precios Ask, Bid y Last actuales a través de los métodos homónimos, CStrategy redefine la función de sistema Digits. Esta función retorna el número de dígitos tras la coma para el instrumento actual. Podría parecer que la redefinición de esta función es absurda, pero no es así. El asunto es que el símbolo con el trabaja el ejemplar puede diferenciarse del símbolo en el que se ha inciado el módulo ejecutable que contiene la estrategia. En este caso, la llamada de la función de sistema Digits puede provocar confusión. Se retornará el número de decimales tras la coma, no para el instrumento con el que se trabaja, sino para aquel en el que ha sido iniciado el propio experto. Para que esto no suceda, la función Digits en CStartegy ha sido redefinida con el método homónimo. Al recurrir a esta función, en la práctica se llama a este método. El método retorna el número de dígitos tras la coma precisamente para el símbolo del experto. Aquí tenemos el código fuente de este método:

//+------------------------------------------------------------------+
//| Retorna el número de dígitos tras la coma para el símbolo con el que trabaja  |
//| el instrumento                                                      |
//+------------------------------------------------------------------+
int CStrategy::Digits(void)
  {
   int digits = (int)SymbolInfoInteger(ExpertSymbol(), SYMBOL_DIGITS);
   return digits;
  }

Es necesario recordar esta particularidad y entender el valor de esta redefinifición.

 

Soporte de cuentas con cobertura

Desde hace poco MetaTrader 5 da soporte a las llamadas cuentas con cobertura. Se trata de cuentas en las que se pueden abrir simultáneamente varias posiciones, incluidas las opuestas, tanto de compra como de venta. Recordemos que todas las acciones con posiciones en CStrategy se procesan en los manejadores especiales  SupportBuy y SupportSell. En estos métodos se transmiten por turno las posiciones que se encuentran en la lista de la estrategia actual. No importa si hay una posición o varias. Pueden estar abiertas tanto en un símbolo como en varios. El mecanismo de procesamiento y transmisión de estas posiciones será único. Por eso los cambios que se requerirán para el soporte de las cuentas con cobertura serán muy pocos. Ante todo, se deberá reescribir el método RebuildPosition. Al cambiar el entorno comercial (ejecución de nuevas operaciones), este método formará de nuevo todas las posiciones de la lista. Dependiendo del modo de cuenta que se use, la nueva formación será diferente. Para las cuentas con compensación se usará el algoritmo de elección de posición por símbolo. Para las cuentas con cobertura, el algoritmo de elección de la posición usará el índice de la posición en la lista general.

Mostramos la versión antigua del método RebuildPosition:

//+------------------------------------------------------------------+
//| Construye nuevamente las listas de posiciones.                                    |
//+------------------------------------------------------------------+
void CStrategy::RebuildPositions(void)
{
   ActivePositions.Clear();
   for(int i = 0; i < PositionsTotal(); i++)
   {
      string symbol = PositionGetSymbol(i);
      PositionSelect(symbol);
      CPosition* pos = new CPosition();
      ActivePositions.Add(pos);
   }
}

En la nueva versión de RebuildPosition, dependiendo del tipo de cuenta, se usarán dos algoritmos de elección de posición:

//+------------------------------------------------------------------+
//| Construye nuevamente las listas de posiciones.                                    |
//+------------------------------------------------------------------+
void CStrategy::RebuildPositions(void)
  {
   ActivePositions.Clear();
   ENUM_ACCOUNT_MARGIN_MODE mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
     {
      for(int i=0; i<PositionsTotal(); i++)
        {
         string symbol=PositionGetSymbol(i);
         PositionSelect(symbol);
         CPosition *pos=new CPosition();
         ActivePositions.Add(pos);
        }
     }
   else
     {
      for(int i=0; i<PositionsTotal(); i++)
        {
         ulong ticket=PositionGetTicket(i);
         PositionSelectByTicket(ticket);
         CPosition *pos=new CPosition();
         ActivePositions.Add(pos);
        }
     }
  }

Preste atención, tanto para las cuentas con cobertura como para las clásicas, se usa la misma clase de posición: CPosition. Después de elegir la posición, el acceso a sus propiedades se realiza a través de los métodos PositionGetInteger, PositionGetDouble y PositionGetString. Además, no tiene importancia si había sido elegida una posición con cobertura o común. Tanto en un caso como en el otro, el acceso a las propiedades de la posición es el mismo. Precisamente por eso en diferentes tipos de cuenta resulta posible usar la misma clase de posición: CPosition. 

Notemos también que tampoco hay ningún otro método dentro de la clase CStrategy que requiera ser reescrito. CStrategy se ha proyectado de tal forma que el funcionamiento de las estrategias que se basan en este motor depende del contexto. Esto significa que si la estrategia trabaja en las cuentas con cobertura abriendo varias posiciones en una dirección, entonces llevará estas posiciones paralelamente, percibiendo cada una de ellas como una clase CPosition por separado. Si se da el caso puesto, en la cuenta puede estar abierta solo una posición, y la estrategia acompañará solo a esta, pero en forma del propio objeto CPosition.  

Aparte de la adición del método RebuildPosition, es imprescindible cambiar el contenido interno de ciertos métodos CPosition. Al ubicarse en el archivo PositionMT5.mqh, esta clase contiene métodos que se basan en la llamada a funciones de sistema. Asimismo, CPosition usa de forma activa la clase comercial estándar CTrade. En la última versión de CTrade se introdujeron cambios adicionales que permiten utilizar las propiedades de las posiciones con cobertura. Por ejemplo, se puede cerrar una posición con cobertura con una posición en dirección contraria, para ello hay que llamar al nuevo método CTrade::PositionCloseBy. Más abajo se muestran los métodos CPosition que han cambiado su contenido en comparación con las versiones anteriores de CPosition:

//+------------------------------------------------------------------+
//|                                                  PositionMT5.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Object.mqh>
#include "Logs.mqh"
#include <Trade\Trade.mqh>
#include "Trailing.mqh"
//+------------------------------------------------------------------+
//| Clase de posición activa, para las estrategias clásicas               |
//+------------------------------------------------------------------+
class CPosition : public CObject
  {
   ...
  };
...
//+------------------------------------------------------------------+
//| Retorna el nivel absoluto de stop-loss para la posición actual.    |
//| Si el nivel de stop-loss no ha sido establecido, retorna 0.0            |
//+------------------------------------------------------------------+
double CPosition::StopLossValue(void)
{
   if(!IsActive())
      return 0.0;
   return PositionGetDouble(POSITION_SL);
}
//+------------------------------------------------------------------+
//| Establece el nivel absoluto de stop-loss                      |
//+------------------------------------------------------------------+
bool CPosition::StopLossValue(double sl)
{
   if(!IsActive())
      return false;
   return m_trade.PositionModify(m_id, sl, TakeProfitValue());
}
//+------------------------------------------------------------------+
//| Retorna el nivel absoluto de stop-loss para la posición actual.    |
//| Si el nivel de stop-loss no ha sido establecido, retorna 0.0            |
//+------------------------------------------------------------------+
double CPosition::TakeProfitValue(void)
{
   if(!IsActive())
      return 0.0;
   return PositionGetDouble(POSITION_TP);
}
//+------------------------------------------------------------------+
//| Establece el nivel absoluto de stop-loss                      |
//+------------------------------------------------------------------+
bool CPosition::TakeProfitValue(double tp)
  {
   if(!IsActive())
      return false;
   return m_trade.PositionModify(m_id, StopLossValue(), tp);
  }
//+------------------------------------------------------------------+
//| Cierra la posición actual según el mercado, estableciendo un comentario     |
//| de cierre, igual a comment                                        |
//+------------------------------------------------------------------+
bool CPosition::CloseAtMarket(string comment="")
  {
   if(!IsActive())
      return false;
   ENUM_ACCOUNT_MARGIN_MODE mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
      return m_trade.PositionClose(m_symbol);
   return m_trade.PositionClose(m_id);
  }
//+------------------------------------------------------------------+
//| Retorna el volumen actual de la posición.                                |
//+------------------------------------------------------------------+
double CPosition::Volume(void)
  {
   if(!IsActive())
      return 0.0;
   return PositionGetDouble(POSITION_VOLUME);
  }
//+------------------------------------------------------------------+
//| Retorna las ganancias actuales de la posición en la divisa del depósito.            |
//+------------------------------------------------------------------+
double CPosition::Profit(void)
  {
   if(!IsActive())
      return 0.0;
   return PositionGetDouble(POSITION_PROFIT);
  }
//+------------------------------------------------------------------+
//| Retrona verdadero si la posición está activa. Retorna falso         |
//| en caso contrario.                                              |
//+------------------------------------------------------------------+
bool CPosition::IsActive(void)
{
   ENUM_ACCOUNT_MARGIN_MODE mode=(ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode!=ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
      return PositionSelect(m_symbol);
   else
      return PositionSelectByTicket(m_id);
}
//+------------------------------------------------------------------+

Como se puede ver, la base de todos estos métodos es la llamada de otro método IsActive. Este método retorna verdadero si la posición activa presentada por el objeto CPosition existe realmente en el sistema. Pero en realidad, todo lo que hace el método es elegir la posición de uno de los dos métodos, dependiendo del tipo de cuenta. Si la cuenta es clásica, con posiciones por compensación, la elección de posición tiene lugar con la ayuda de la función PositionSelect, con la que se debe indicar el símbolo de la posición. Si la cuenta es con cobertura, la posición se elige según su ticket, con la ayuda de la nueva función PositionSelectByTicket. El resultado de la elección (true o false) se retorna al procedimiento que ha llamado al método. Precisamente este método establece el contexto posterior de trabajo con la posición. Puesto que todas las funciones comerciales CPosition se basan en la clase comercial CTrade, cambiar los algoritmos comerciales no será necesario. CTrade modifica, abre y cierra perfectamente tanto las posiciones habituales como las dirigidas en dirección opuesta.  

 

Trabajando con órdenes pendientes en las versiones anteriores de CStrategy

El trabajo con órdenes pendientes es una parte importante de muchos algoritmos comerciales. Tras la publicación de la primera versión del motor comercial CStrategy, surgieron muchas preguntas sobre el trabajo con órdenes pendientes. En este apartado y en los siguientes responderemos a las preguntas relacionadas con el trabajo en este modo.

Originalmente, el motor comercial CStrategy se creó sin tener en cuenta el trabajo con órdenes pendientes. Sin embargo, esto no significaba que en el marco de una estrategia basada en la clase CStrategy, el trabajo con las órdenes pendientes fuera algo imposible. Simplemente, para trabajar con ellas, era necesario usar las funciones estándar de MetaTrader 5 como OrdersTotal() y OrderSelect.

Supongamos que el experto en cada nueva barra coloca o modifica una orden stop pendiente anterior de forma que su precio de activación sea un 0.25% mayor (para la compra) o menor (para la venta) al precio actual. La idea es la siguiente: si durante la barra el precio ha tenido un movimiento brusco (impulso), entonces esta orden será ejecutada, y el experto entrará en el momento de movimiento fuerte. Si el movimiento no ha sido lo suficientemente fuerte, entonces durante la barra actual la orden no se ejecutará, y será necesario trasladarla a un nuevo nivel a la espera de un nuevo impulso. Podrá familiarizarse con la implementación completa de esta estrategia más abajo, en los apartados dedicados a la descripción de este algoritmo, llamado CImpulse. Se trata de un sistema sencillo, y como se puede deducir por su nombre, se basa en el impulso. Necesita una única entrada: la activación de una orden pendiente. Como ya sabemos, en CStrategy, para entrar en la posición existen los métodos especiales redefinidos BuyInit y SellInit. Por consiguiente, precisamente en estos métodos es necesario ubicar los algoritmos de trabajo con órdenes pendientes. Sin el soporte directo de CStrategy, este código de compra tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//| Lógica del trabajo con órdenes pendientes de compra.                 |
//+------------------------------------------------------------------+
void CMovingAverage::InitBuy(const MarketEvent &event)
  {
   if(!IsTrackEvents(event))return;                      // ¡Procesamos solo el evento necesario!
   if(positions.open_buy > 0) return;                    // ¡Si hay aunque sea solo una posición larga, no hay que comprar más, puesto que ya hemos comprado!
   int buy_stop_total = 0;
   for(int i = OrdersTotal()-1; i >= 0; i--)
   {
      ulong ticket = OrderGetTicket(i);
      if(!OrderSelect(ticket))continue;
      ulong magic = OrderGetInteger(ORDER_MAGIC);
      if(magic != ExpertMagic())continue;
      string symbol = OrderGetString(ORDER_SYMBOL);
      if(symbol != ExpertSymbol())continue;
      ENUM_ORDER_TYPE order_type = (ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE);
      if(order_type == ORDER_TYPE_BUY_STOP)
      {
         buy_stop_total++;
         Trade.OrderModify(ticket, Ask()*0.0025, 0, 0, 0);
      }
   }
   if(buy_stop_total == 0)
      Trade.BuyStop(MM.GetLotFixed(), Ask() + Ask()*0.0025, ExpertSymbol(), 0, 0, NULL);
  }

El método IsTrackEvents determina si el evento anterior se corresponde con la apertura de una nueva barra en el símbolo actual. A continuación, el experto mira la cantidad de posiciones abiertas en la dirección de la compra. Si hay aunque sea una posición abierta, entonces no hace falta comprar más, y la lógica es completada. Después viene la comprobación de las órdenes pendientes actuales. En el ciclo se produce la búsqueda de todos sus índices. Cada una de estas órdenes se elige por su índice, y después se analiza su número mágico y su símbolo. Si estos dos parámetros se corresponden con los parámetros del experto, se considerará que la orden pertenece al asesor comercial actual, y el contador del número de solicitudes aumentará en una unidad. La propia orden se modifica: su precio de entrada cambia al precio actual + 0.25% del mismo. Si no hay órdenes pendientes, cosa que sabremos porque el contador buy_stop_order será igual acero, entonces se coloca una orden pendiente a una distancia del 0.25% del precio actual.

Notemos que no hay apertura inmediata de posición en InitBuy. CStrategy no limita de tal forma a los métodos de apertura de posición. De manera formal, en estos métodos se ubica cualquier lógica del experto. Sin embargo, para que el procesamiento sea correcto es necesario que esta lógica se relacione precisamente con la apertura de posiciones o bien de órdenes de mercado. 

Como podemos ver por el ejemplo adjunto, el trabajo con las órdenes pendietes se ejecuta según el mismo principio que el trabajo con las posiciones normales. La exigencia principal sigue siendo la misma: es necesario separar la lógica de la compra y la lógica de la venta, describiéndolas en los métodos aparte BuyInit y SellInit, respectivamente. En BuyInit es necesario procesar solo las órdenes pendientes de compra. En SellInit es necesario procesar solo las órdenes pendientes de venta. En todo lo demás, la lógica de trabajo con órdenes recuerda al esquema clásico de trabajo adoptado en MetaTrader 4: búsqueda entre las órdenes, elección de órdenes pertenencientes al experto actual, análisis del entorno comercial y toma de decisiones sobre la apertura o modificación de una orden ya existente. 

 

Trabajando con órdenes pendientes con ayuda de CPendingOrders y COrdersEnvironment

Las funciones estándar en MetaTrader 5 para trabajar con órdenes pendientes suponen un sistema bastante cómodo para controlar las órdenes pendientes de forma completa, y lo que es más importante, sencilla. Sin embargo, como muchos han notado ya, CStrategy es un conjunto de clases orientadas a objetos para constuir un sistema comercial.  Todas las acciones realizadas en CStrategy, son acciones orientadas a objetos, es decir, se realizan sobre objetos que a su vez ejecutan acciones comerciales. Este enfoque da varias ventajas. Vamos a enumerar algunas de ellas.

  • Reducción del tamaño de los códigos fuente de usuario. Muchas acciones, tales como normalizar los precios o preparar los arrays de destino para las funiones de la clase CopyBuffer, se realizan "entre bastidores", el propio método presenta un resultado ya preparado. Además, no se requerirá escribir procedimientos adicionales de comprobación y otras acciones intermedias que son inevitables al trabajar directamente con las funciones de sistema de MetaTrader 5.
  • Independencia de la plataforma. Puesto que todas las clases y métodos presentados han sido en general escritos en el el lenguaje MQL, se hace posible recibir alguna propiedad con la ayuda del método universal. Sin embargo, la implementación inmediata de este método para diferentes plataformas será distinta. Pero a nivel de usuario esto no tendrá importancia. De esta forma, un experto escrito para una plataforma teóricamente podrá compilarse en la otra plataforma. Sin embargo, en la práctica hay muchos detalles que se deben superar, y nosotros no vamos a tocar el tema de la independiencia de la plataforma en este artículo.
  • Funcionalidad. El conjunto de funciones de MQL constituye una funcionalidad básica, combinando la cual es posible crear algoritmos complejos y funciones útiles. Cuando estos algoritmos está unidos en una única biblioteca de clases, lo que en esencia es CStartegy, el acceso a esta funcionalidad se convierte en algo más sencillo, y la adición de nuevas funciones no conlleva complicaciones en el funcionamiento, puesto que simplemente aparecen nuevos módulos que se pueden usar según la discreción del escritor del experto.

Para dar soporte a estas posibilidades ahora y también en lo sucesivo, se ha decidio ampliar la funcionalidad de CStartegy para conseguir un cómodo mecanismo orientado a objetos para trabajar con órdenes pendientes. Este mecanismo está representado por dos clases: CPendingOrderCOrdersEnvironment. COrder representa un cómodo objeto que contiene todas las propiedades de una orden pendiente que se pueden obtener a través de las funciones OrderGetInteger, OrderGetDouble y OrderGetString. La misión de COrdersEnvironment se explicará más abajo.

Supongamos que el objeto CPendingOrder representa una orden pendiente que verdaderamente existe en el sistema. Si eliminamos esta orden pendiente, ¿qué deberá suceder con el propio objeto CPendingOrder que lo representa? Si el objeto permanece después de que la orden pendiente factual haya sido eliminada, esto conllevará serios errores. El experto recurrirá al objeto CPendingOrder y tras encontrarlo, "decidirá" erróneamente que la orden pendiente sigue existiendo en el sistema. Para evitar esto, es necesario garantizar la sincronización del entorno comercial con el entorno del objeto CStrategy. En otras palabras, es necesario crear un mecanismo que permita garantizar el acceso solo a aquellos objetos que existen en realidad. Precisamente a esto se dedica la clase COrdersEnvironment. Esta implementación es bastante sencilla, sin embargo, permite obtener acceso a aquellos objetos de CPendingOrders que representan órdenes pendientes reales.

La base de la clase  COrdersEnvironment son los métodos GetOrder y Total. El primero retorna el objeto CPendingOrders, que corresponde a la orden pendiente con un índice determinado en el sistema de órdenes pendientes de MetaTrader 5. El segundo método retorna la cantidad total de órdenes pendientes. Ha llegado el momento de echar un vistazo con más detalle a esta clase. Mostramos su código fuente más abajo:

//+------------------------------------------------------------------+
//| Clase para trabajar con órdenes pendientes                          |
//+------------------------------------------------------------------+
class COrdersEnvironment
{
private:
   CDictionary    m_orders;         // Número total de todas las órdenes pendientes
public:
                  COrdersEnvironment(void);
   int            Total(void);
   CPendingOrder* GetOrder(int index);
};
//+------------------------------------------------------------------+
//| Es necesario conocer el símbolo actual y el número mágico del experto      |
//+------------------------------------------------------------------+
COrdersEnvironment::COrdersEnvironment(void)
{
}
//+------------------------------------------------------------------+
//| Retorna la orden pendiente                                      |
//+------------------------------------------------------------------+
CPendingOrder* COrdersEnvironment::GetOrder(int index)
{
   ulong ticket = OrderGetTicket(index);
   if(ticket == 0)
      return NULL;
   if(!m_orders.ContainsKey(ticket))
      return m_orders.GetObjectByKey(ticket);
   if(OrderSelect(ticket))
      return NULL;
   CPendingOrder* order = new CPendingOrder(ticket);
   m_orders.AddObject(ticket, order);
   return order;
}
//+------------------------------------------------------------------+
//| Retorna el número de órdenes pendientes                         |
//+------------------------------------------------------------------+
int COrdersEnvironment::Total(void)
{
   return OrdersTotal();   
}

El método Total retorna el número de órdenes pendientes que existen realmente en el sistema en este momento. El método nunca se equivoca, porque retorna el valor de sistema obtenido de la función OrdersTotal().

El método GetOrder necesita para su trabajo que se indique el índice de una orden pendiente en el sistema. Dado que siempre se conoce exactamente la cantidad total de órdenes obtenida con el método Total, el índice de la orden necesaria también será siempre conocido y se corresponderá al detalle con el índice de la orden pendiente factual en el sistema. A continuación, el método GetOrder obtiene el identificador de la orden pendiente según su índice. Si por algún motivo la orden ha sido eliminada, entonces el identificador de la orden será igual a cero, y por consiguiente, al experto se le retornará la constante NULL, señalizando que no se ha encontrado la orden con el índice solicitado.

Cada objeto creado dinámicamente necesita ser eliminado claramente con la ayuda del operador especial delete. Puesto que GetOrder crea los objetos CPendingOrders dinámicamente con la ayuda del operador new, también es necesario eliminar estos objetos. Para ahorrar al usuario la necesidad de eliminar el objeto tras obtenerlo, se ha usado un procedimiento especial que ubica el objeto creado en un diccionario-contenedor especial dentro del objeto COrdersEnvironment. En el diccionario, el acceso al elemento se realizar según su clave única, en este caso, el identificador de la orden. Así, si la orden sigue existiendo, el objeto creado con anterioridad que representa esta orden probablemente ya habrá sido creado y ubicado en un contenedor. Precisamente este se retorna con la función GetOrder. Si la llamada a la orden con este identificador se da por primera vez, entonces se crea un nuevo objeto CPendingOrder, después de lo cual se ubica en el diccionario, y el enlace al mismo se retorna al usuario.

¿Qué obtenemos con este enfoque? Ante todo, el método GetOrder garantiza que retornará solo aquel objeto que represente una orden pendiente que realemente existe. Será responsable de ello la función de sistema OrderGetTicket. En segundo lugar, el objeto se creará solo en el caso de que no haya sido creado con anterioridad. Esto permitirá ahorrar recursos adicionales a la computadora. Y en tercer lugar, este algoritmo evita al usuario eliminar el objeto obtenido. Puesto que todos los objetos se guardan en el diccionario, serán eliminados automáticamente después de la desinicialización del propio COrdersEnvironment. 

 

Referencia a las órdenes pendientes en el código del experto

Ha llegado el momento de reescribir la lógica del trabajo con órdenes pendientes representada en el anterior apartado "Trabajando con órdenes pendientes en las versiones anteriores de CStrategy". Usando las clases CPendingOrder y COrdersEnvironment, el código tendrá el aspecto siguiente:

//+------------------------------------------------------------------+
//| Compramos cuando la media móvil rápida esté por encima de la lenta.       |
//+------------------------------------------------------------------+
void CMovingAverage::InitBuy(const MarketEvent &event)
  {
   if(!IsTrackEvents(event))return;                      // ¡Procesamos solo el evento necesario!
   if(positions.open_buy > 0) return;                    // ¡Si hay aunque sea solo una posición larga, no hay que comprar más, puesto que ya hemos comprado!
   int buy_stop_total = 0;
   for(int i = PendingOrders.Total()-1; i >= 0; i--)
     {
      CPendingOrder* Order = PendingOrders.GetOrder(i);
      if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic()))
         continue;
      if(Order.Type() == ORDER_TYPE_BUY_STOP)
       {
         buy_stop_total++;
         Order.Modify(Ask() + Ask()*0.0025);
       }
       //delete Order; ¡No hay que eliminar el objeto Order!
     }
   if(buy_stop_total == 0)
      Trade.BuyStop(MM.GetLotFixed(), Ask() + Ask()*0.0025, ExpertSymbol(), 0, 0, NULL);
  }

El objeto PendingsOrders constituye una clase COrdersEnvironment. La búsqueda de órdenes pendientes comienza de la misma forma que al usar las funciones de sistema. A continuación, se hace un intento de obtener un objeto-orden según un índice de órdenes pendientes igual a la variable i. Si la orden por algún motivo no se ha recibido o pertenece a otro experto, la búsqueda de órdenes continuará desde la nueva orden. Para ello existe el método especial IsMain del objeto CPendingorder, que retorna el valor true si la orden tiene el mismo símbolo y número mágico que el propio experto, determinando al mismo tiempo si pertenece la orden actual a este experto.

Si el tipo de orden se corresponde con el tipo ORDER_TYPE_BUY_STOP, esto significa que la orden pendiente colocada no se ha activado, y se deberá cambiar su nivel de acuerdo con la fórmula: precio actual + 0.25%. El método Modify y su versión recargada son los responsables de modificar la orden pendiente. Permite indicar el precio y otros parámetros que se deben modificar.

Preste atención: después de que haya finalizado el trabajo con una orden pendiente, no se debe eliminar el propio objeto de la orden. El enlace a la orden se debe dejar como está. El contenedor PendingOrders controla de forma autónoma los objetos del tipo CPendingOrder y no necesita eliminar los objetos retornados en las funciones del usuario. 

Si no hay órdenes pendientes y el contador buy_stop_total es igual a cero, se coloca una nueva orden pendiente. Para ello se usa el módulo comercial Trade, que ha sido descrito en las partes anteriores del artículo. 

En el enfoque orientado a objetos, el acceso a las propiedades de la orden pendiente se lleva a cabo a través de los métodos correspondientes del objeto CPendingOrders. Gracias a ello, el volumen del código se reduce y su fiabilidad permanece al nivel anterior, ya que PendingOrders garantiza que a través de él se recibirá solo el objeto-orden correspondiente que existe realemente en el sistema como orden pendiente.

 

Lógica comercial del experto CImpulse, que trabaja con órdenes pendientes 


Hemos analizado el trabajo con órdenes pendientes y ahora podemos crear un experto completo que use las posibilidades del motor comercial para trabajar con ellas. Nuestra estrategia consistirá en entrar en los momentos de movimiento fuerte en la dirección de este movimiento, por eso se llamará CImpulse. En la apertura de cada nueva barra se medirá una cierta distancia con respecto al precio actual, expresado en tanto por ciento. A la misma distancia del precio actual se colocarán las órdenes pendientes BuyStop y SellStop. La distancia  se establecerá en tanto por ciento. Si dentro de una barra se ejecuta una de las órdenes, esto significará que el precio en este intervalo de tiempo ha recorrido una gran distancia, lo que es señal de un impulso en el mercado. La orden se ejecutará y se transformará en una posición abierta.

Las posiciones abiertas se acompañarán con una media móvil sencilla. Si el precio retorna a ella, la posición se cerrará. En la captura de pantalla se muestra la entrada típica en una posición larga al activarse una orden pendiente BuyStop:


Fig. 1. Posición larga de la estrategia CImpulse.
 

En la captura de pantalla se ve que en el inicio de la barra, marcado con un triángulo negro, se han colocado dos órdenes pendientes a una distancia de 0.1% del precio de apertura de la barra. Una de ellas, la orden BuyStop, se ha activado. Se ha abierto una nueva posición larga. En cuanto el precio de cierre de una de las barras se ha puesto por debajo de la media móvil representada como una línea roja, el experto ha cerrado la posición. En la figura 1, el momento del cierre se representa con un triángulo azul.

Si dentro de una barra la orden pendiente no se ha activado, esta se desplaza a un nuevo nivel calculado por el experto a partir del actual precio, que ya es nuevo.

La estrategia descrita tiene una peculiaridad a la hora de trabajar con órdenes pendientes. El asunto es que el nivel de una orden BuyStop puede resultar inferior al valor actual de la media. En este caso, justo después de la entrada en la posición tendrá lugar su cierre, puesto que el precio actual está por debajo del nivel de la media. Lo mismo vale para la posición corta: el precio de activación de una orden SellStop puede resultar superior al nivel de la media. Para que esto no suceda, en los métodos BuyInit y SellInit es imprescindible comprobar adicionalmente el nivel de la media. El algoritmo colocará órdenes pendientes BuyStop solo en el caso de que estén por encima de la media móvil. Lo mismo se aplica para las órdenes SellStop: se colocarán solo cuando se encuentren por debajo de la media.

Asimismo, utilizaremos la nueva capacidad de MetaTrader 5, el trabajo con cuentas con cobertura. Esta particularidad significará que dentro de una barra se puede abrir de forma simultánea tanto una posición corta, como una larga. Sin embargo, la lógica del experto expuesta inicialmente, que separa el acompañamiento de posiciones cortas y largas, permite no cambiar en absoluto el código de esta estrategia comercial. Independientemente del número de posiciones, todas las posiones cortas se cerrarán cuando el precio de apertura de una barra esté por encima de la media móvil, y las largas se cerrarán cuando el precio sea menor al precio de la media móvil. Además, no importará si comerciamos con cuentas con cobertura o con compensación. 

Para trabajar con una cuenta con cobertura, es necesario en primer lugar abrirla poniendo la bandera correspondiente en la ventana de diálogo "Abrir una cuenta":

 

Fig. 2. Abrir una cuenta con cobertura 

Después de haberla creado, podremos trabajar con ella. Ante todo, necesitaremos escribir la clase comercial CImpulse, que implementa la lógica del algoritmo comercial analizado. 

 

Clase de la estrategia CImpulse

A continuación, se muestra la lista de la clase CImpulse, que describe la lógica de nuestro nuevo experto comercial. Para entender mejor cómo funcionan las órdenes pendientes, esta clase se ha aligerado al máximo y no contiene procedimientos de trabajo con el registro, así como los métodos especiales de parseo de los parámetros de la estrategia del archivo XML:

//+------------------------------------------------------------------+
//|                                                      Impulse.mqh |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#include <Strategy\Strategy.mqh>
#include <Strategy\Indicators\MovingAverage.mqh>

input double StopPercent = 0.05;
//+------------------------------------------------------------------+
//| Determina la acción que se debe ejecutar con una orden   |
//| pendiente.                                                         |
//+------------------------------------------------------------------+
enum ENUM_ORDER_TASK
{
   ORDER_TASK_DELETE,   // Eliminar una orden pendiente
   ORDER_TASK_MODIFY    // Modificar una orden pendiente
};
//+------------------------------------------------------------------+
//| Estrategia CImpulse                                               |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // Tanto por ciento del nivel de la orden pendiente
   bool              IsTrackEvents(const MarketEvent &event);
protected:
   virtual void      InitBuy(const MarketEvent &event);
   virtual void      InitSell(const MarketEvent &event);
   virtual void      SupportBuy(const MarketEvent &event,CPosition *pos);
   virtual void      SupportSell(const MarketEvent &event,CPosition *pos);
   virtual void      OnSymbolChanged(string new_symbol);
   virtual void      OnTimeframeChanged(ENUM_TIMEFRAMES new_tf);
public:
   double            GetPercent(void);
   void              SetPercent(double percent);
   CIndMovingAverage Moving;
};
//+------------------------------------------------------------------+
//| Trabajo con las órdenes pendientes BuyStop para abrir una posición larga       |
//|                                                           |
//+------------------------------------------------------------------+
void CImpulse::InitBuy(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                      
   if(positions.open_buy > 0) return;                    
   int buy_stop_total = 0;
   ENUM_ORDER_TASK task;
   double target = Ask() + Ask()*(m_percent/100.0);
   if(target < Moving.OutValue(0))                    // El precio de activación de la orden deberá ser superior a la media móvil
      task = ORDER_TASK_DELETE;
   else
      task = ORDER_TASK_MODIFY;
   for(int i = PendingOrders.Total()-1; i >= 0; i--)
   {
      CPendingOrder* Order = PendingOrders.GetOrder(i);
      if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic()))
         continue;
      if(Order.Type() == ORDER_TYPE_BUY_STOP)
      {
         if(task == ORDER_TASK_MODIFY)
         {
            buy_stop_total++;
            Order.Modify(target);
         }
         else
            Order.Delete();
      }
   }
   if(buy_stop_total == 0 && task == ORDER_TASK_MODIFY)
      Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);
}
//+------------------------------------------------------------------+
//| Trabajo con las órdenes pendientes SellStop para abrir una posición corta     |
//|                                                           |
//+------------------------------------------------------------------+
void CImpulse::InitSell(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                      
   if(positions.open_sell > 0) return;                    
   int sell_stop_total = 0;
   ENUM_ORDER_TASK task;
   double target = Bid() - Bid()*(m_percent/100.0);
   if(target > Moving.OutValue(0))                    // El precio de activación de la orden deberá ser superior a la media móvil
      task = ORDER_TASK_DELETE;
   else
      task = ORDER_TASK_MODIFY;
   for(int i = PendingOrders.Total()-1; i >= 0; i--)
   {
      CPendingOrder* Order = PendingOrders.GetOrder(i);
      if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic()))
         continue;
      if(Order.Type() == ORDER_TYPE_SELL_STOP)
      {
         if(task == ORDER_TASK_MODIFY)
         {
            sell_stop_total++;
            Order.Modify(target);
         }
         else
            Order.Delete();
      }
   }
   if(sell_stop_total == 0 && task == ORDER_TASK_MODIFY)
      Trade.SellStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);
}
//+------------------------------------------------------------------+
//| Acompañamiento de una posición larga con la media móvil Moving       |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Acompañamiento de una posición corta con la media móvil Moving      |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Filtra los eventos entrantes. Si el evento transmitido     |
//| no es procesado por la estrategia, retorna falso, si se procesa,  |
//| retorna verdadero.                                        |
//+------------------------------------------------------------------+
bool CImpulse::IsTrackEvents(const MarketEvent &event)
  {
//Procesamos solo la apertura de una nueva barra en el instrumento y marco temporal en el que trabajamos
   if(event.type != MARKET_EVENT_BAR_OPEN)return false;
   if(event.period != Timeframe())return false;
   if(event.symbol != ExpertSymbol())return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Reaccionamos al cambio de símbolo                                   |
//+------------------------------------------------------------------+
void CImpulse::OnSymbolChanged(string new_symbol)
  {
   Moving.Symbol(new_symbol);
  }
//+------------------------------------------------------------------+
//| Reaccionamos al cambio de marco temporal                                |
//+------------------------------------------------------------------+
void CImpulse::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf)
  {
   Moving.Timeframe(new_tf);
  }
//+------------------------------------------------------------------+
//| Retorna el tanto por ciento del nivel de ruptura                             |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| Establece el tanto por ciento del nivel de ruptura                          |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

Archivo estándar del experto mq5, que configura e inicia esta estrategia como el experto presentado más abajo:

//+------------------------------------------------------------------+
//|                                                ImpulseExpert.mq5 |
//|                                 Copyright 2016, Vasiliy Sokolov. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Strategy\StrategiesList.mqh>
#include <Strategy\Samples\Impulse.mqh>

CStrategyList Manager;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   CImpulse* impulse = new CImpulse();
   impulse.ExpertMagic(1218);
   impulse.Timeframe(Period());
   impulse.ExpertSymbol(Symbol());
   impulse.ExpertName("Impulse");
   impulse.Moving.MaPeriod(28);                      
   impulse.SetPercent(StopPercent);
   if(!Manager.AddStrategy(impulse))
      delete impulse;
//---
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   Manager.OnTick();
  }
//+------------------------------------------------------------------+

 

Análisis del funcionamiento de la estrategia CImpulse

Para comprender mejor el funcionamiento del algoritmo, hemos incluido un vídeo especial que demuestra el trabajo con órdenes pendientes en el momento de simulación de la estrategia. Se ve que las órdenes BuyStop y SellStop se desplazan en el momento de la apertura de una nueva barra a una cierta distancia del precio actual, formando un corredor dinámico. Cuando el nivel de una orden pendiente de compra está por debajo de la línea media, se elimina por completo. Sin embargo, cuando el precio de activación se pone por encima de la media, la orden pendiente aparece de nuevo. La misma regla funciona con las órdenes SellStop:


En la captura de pantalla de más abajo, se puede ver el momento en el que se forma la situación de cobertura, cuando teniendo una orden de compra abierta, se ha activado el cierre de la posición corta. Después de ello, la posición larga ha continuado existiendo y se ha cerrado de acuerdo con la lógica que implica que el precio de apertura de la barra se encuentre por debajo de la media móvil:

 

Fig. 3. Acompañamiento de posiciones en una cuenta con cobertura.

La misma lógica del experto comercial ejecutada en el tipo clásico de cuenta, dará un resultado parecido, pero causará una situación ligeramente distinta:

 

Fig. 4. Acompañamiento de posiciones en una cuenta clásica con compensación. 

Se puede ver que durante el movimiento fuerte se ejecutó una operación de compra que cerró una posición corta que existía con anterioridad. De esta forma, todas las posiciones abiertas han sido liquidadas. Por eso ya en la siguiente barra el experto comenzó a colocar nuevas órdenes de compra, una de las cuales se ha ejecutado y transformado en una nueva posición larga, que a su vez, se ha cerrado de la misma forma que en la cuenta con cobertura.

Podemos cambiar un poco la lógica del experto haciéndolo polimorfo, es decir, dependiendo del tipo de cuenta, se procesará el algoritmo correspondiente. Resulta obvio que para una cuenta con compensación, solo puede haber una posición. Para evitar las situaciones en las que la apertura de una nueva posición cierra la posición opuesta que la precedía, es necesario dotar a todas las posiciones con compensación de un stop-loss que se corresponda exactamente con el nivel de las órdenes de ruptura de direcciones opuestas. De esta forma, la activación de una de las órdenes stop indicará la activación del stop-loss para la posición opuesta, si dicha posición está abierta en este momento.  Esta lógica se incluirá solo en las cuentas con compensación. Se la puede ubicar con comodidad en los métodos BuySupport y SellSupport. Aquí tenemos el código de los métodos de apoyo de la estrategia corregidos:

//+------------------------------------------------------------------+
//| Acompañamiento de una posición larga con la media móvil Moving       |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Bid() - Bid()*(m_percent/100.0);
      if(target < Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Acompañamiento de una posición corta con la media móvil Moving      |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE);
   if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING)
   {
      double target = Ask() + Ask()*(m_percent/100.0);
      if(target > Moving.OutValue(0))
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}

Los resultados de la simulación de la estrategia con esta funcionalidad cambian ligeramente de nuevo. Ahora en las cuentas con compensación el experto se comportará como un experto clásico que trabaje solo con una posición:


Fig. 5. Acompañamiento de posiciones con un experto polimorfo en una cuenta clásica con compensación.

El artíclo adjunta la última versión de la estrategia CImpulse, que ejecuta una lógica diferente para distintos tipos de cuenta.   

Notemos que todas las variantes de lógica comercial son ciertas. Sin embargo, la forma concreta en la que funcionará una estrategia comercial depende ante todo de sí misma. CStrategy supone solo una interfaz unificada de trabajo con posiciones, independientemente del tipo al que pertenezcan. 

 

Conclusión 

Hemos analizado las nuevas posibilidades del motor comercial CStrategy. Este abarca el soporte de nuevos tipos de cuenta, el trabajo objetivo con órdenes pendientes y un conjunto ampliado de funciones para trabajar con los precios actuales.

Los nuevos métodos introducidos en CStrategy permiten obtener acceso de forma rápida y sencilla a los nuevos precios del tipo Ask, Bid y Last. El método redefinido Digits ahora retorna de forma garantizada el número correcto de dígitos del instrumento con el que trabajamos. 

El trabajo con órdenes pendientes con la ayuda de las clases especiales CPendingOrders y COrdersEnvironment simplifica la lógica comercial. El experto obtiene acceso a la orden pendiente a través de un objeto especial del tipo CPendingOrder. Modificando sus propiedades, por ejemplo, el nivel del precio de activación de la orden, es capaz de medir la propiedad análoga de la orden real que corresponde a este objeto. El modelo de los objetos se ha construido de forma fiable. No es posible acceder a un objeto al que no le corresponda ni una sola orden pendiente factual en el sistema. El trabajo con órdenes tiene lugar en los métodos BuyInit y SellInit, que deben ser redefinidos en la estrategia. En BuyInit es necesario trabajar solo con órdenes pendientes del tipo BuyStop o BuyLimit. En SellInit es necesario solo trabajar con órdenes SellStop o SellLimit.  

El trabajo con las cuentas con cobertura en el marco del motor comercial CStrategy prácticamente no se diferencia del trabajo con el tipo clásico de cuenta. Las operaciones con posiciones no dependen del tipo de posición y están disponibles a través de la clase especial CPosition. La diferencia a la hora de operar con estos tipos de cuenta se encuentra en la propia lógica de la estrategia. Si la estrategia funciona en el contexto de una única posición, implementará la lógica de trabajo con una sola posición. Si la estrategia funciona con varias posiciones simultáneamente, su lógica deberá tener en cuenta que en los métodos BuySupport y SellSupport correspondientes se pueden transmitir varias posiciones consecutivas de golpe. El propio motor comercial no implementa ninguna lógica comercial, proporciona el tipo de posición que corresponde al tipo de cuenta.

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

Archivos adjuntos |
Miguel Angel Vico Alba
Miguel Angel Vico Alba | 6 jul. 2016 en 19:20
Interfaces gráficas VI: Controles "Slider" y "Slider doble" (Capítulo 2) Interfaces gráficas VI: Controles "Slider" y "Slider doble" (Capítulo 2)
En el artículo anterior nuestra librería ha sido completada con cuatro controles bastante frecuentes en las interfaces gráficas: “checkbox”, “campo de edición”, “campo de edición con checkbox” y “combobox con checkbox”. El segundo capítulo de la sexta parte estará dedicado a los controles como Slider y Slider doble.
Interfaces gráficas VI: Controles "Casilla de verificación", "Campo de edición" y sus tipos combinados (Capítulo 1) Interfaces gráficas VI: Controles "Casilla de verificación", "Campo de edición" y sus tipos combinados (Capítulo 1)
Este artículo empieza la sexta parte de la serie sobre el desarrollo de la librería para la creación de las interfaces gráficas en los terminales MetaTrader. En el primer capítulo hablaremos sobre los siguientes controles: “casilla de verificación”, “campo de edición” y los tipos combinados de estos controles.
Experto comercial universal: Trabajando con trailing-stops personalizados (parte 6) Experto comercial universal: Trabajando con trailing-stops personalizados (parte 6)
La sexta parte del artículo sobre el experto comercial universal describe el funcionamiento de los trailing-stops. Después de leerlo, usted aprenderá cómo usar normas unificadas para crear su propio módulo de trailing-stop y conectarlo al motor comercial de tal forma que el control de la posición realizado por este suceda automáticamente.
Interfaces gráficas V: Control "Lista combinada" (Capítulo 3) Interfaces gráficas V: Control "Lista combinada" (Capítulo 3)
En dos primeros capítulos de la quinta parte sobre las interfaces gráficas hemos desarrollado las clases para crear la barra de desplazamiento y la lista. En este capítulo vamos a hablar de la clase para la creación del control llamado “Lista combinada”. Éste también es un control compuesto que incluye los controles analizados en dos primeros capítulos de la quinta parte.