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

Experto comercial universal: Indicador CUnIndicator y trabajo con órdenes pendientes (parte 9)

MetaTrader 5Ejemplos | 18 octubre 2017, 08:31
1 035 0
Vasiliy Sokolov
Vasiliy Sokolov

Índice

Introducción

Continuamos desarrollando la base de los códigos fuente del Asesor Experto universal. La mayoría de los enfoques introducidos en el motor comercial de CStrategy han demostrado su eficacia, conveniencia y sencillez en la práctica. Sin embargo, en el proceso del uso real, hubo que revisar algunos momentos en el trabajo del Asesor Experto.

Uno de estos momentos es el trabajo con los indicadores. En el tercer artículo de esta serie, fue propuesto el enfoque clásico orientado a objetos que se aplicaba al trabajo con los indicadores. Su idea consiste en que cada indicador representa una clase orientada a objetos que cuenta con sus métodos de establecimiento y obtención de unas u otras propiedades. No obstante, la implementación de su propia clase-envoltorio es difícil de realizar en la práctica. Aquí, se examina un nuevo enfoque del trabajo con los indicadores en el estilo POO que al mismo tiempo no requiere la escritura de las clases-módulos separadas.

El segundo cambio que se describe en este artículo está relacionado con la introducción del procedimiento de la gestión completa de las órdenes pendientes. Si antes había que gestionar las órdenes pendientes directamente en el código de la estrategia final, ahora una parte de estas funciones fue delegada al motor de CStrategy. Ahora, la clase-estrategia final puede redefinir los métodos SupportBuyPending y SupportSellPending y empezar a gestionar las órdenes pendientes, igualmente como el control de las posiciones activas.

Acceso a los indicadores en las versiones anteriores de CStrategy

Para comprender la problemática del asunto, vamos a dirigirnos a la solución para el trabajo con los indicadores en el tercer artículo mencionado de la serie. En este artículo, se proponía trabajar con cualquier indicador usando la clase-envoltorio. Así, por ejemplo, para trabajar con el indicador iMA, en el ejemplo se usaba la clase-envoltorio CIndMovingAverage. La clase CIndMovingAverage estaba compuesta de los métodos que establecían o devolvían alguna propiedad del indicador, además incluía el método Init que llamaba a la función de sistema iMA. A pesar de que la clase del indicador tenía una estructura simple, ella ocupaba un volumen del código bastante grande, que tenía que implementarse por el usuario. A continuación, se muestra el código fuente de esta clase para evaluar el volumen de tareas de las que se encarga:

//+------------------------------------------------------------------+
//|                                                MovingAverage.mqh |
//|                                 Copyright 2015, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#include <Strategy\Message.mqh>
#include <Strategy\Logs.mqh>
//+------------------------------------------------------------------+
//| defines                                                          |
//+------------------------------------------------------------------+
class CIndMovingAverage
  {
private:
   int               m_ma_handle;         // Handle del indicador
   ENUM_TIMEFRAMES   m_timeframe;         // Timeframe
   int               m_ma_period;         // Período
   int               m_ma_shift;          // Desplazamiento
   string            m_symbol;            // Instrumento
   ENUM_MA_METHOD    m_ma_method;         // Método de la media móvil
   uint              m_applied_price;     // Handle del indicador en el que hace falta calcular el valor de Moving Average,
                                          // o uno de los valores de precios de ENUM_APPLIED_PRICE
   CLog*             m_log;               // Registro
   void              Init(void);
public:
                     CIndMovingAverage(void);

/*Params*/
   void              Timeframe(ENUM_TIMEFRAMES timeframe);
   void              MaPeriod(int ma_period);
   void              MaShift(int ma_shift);
   void              MaMethod(ENUM_MA_METHOD method);
   void              AppliedPrice(int source);
   void              Symbol(string symbol);

   ENUM_TIMEFRAMES   Timeframe(void);
   int               MaPeriod(void);
   int               MaShift(void);
   ENUM_MA_METHOD    MaMethod(void);
   uint              AppliedPrice(void);
   string            Symbol(void);

/*Out values*/
   double            OutValue(int index);
  };
//+------------------------------------------------------------------+
//| Constructor por defecto                                          |
//+------------------------------------------------------------------+
CIndMovingAverage::CIndMovingAverage(void) : m_ma_handle(INVALID_HANDLE),
                                             m_timeframe(PERIOD_CURRENT),
                                             m_ma_period(12),
                                             m_ma_shift(0),
                                             m_ma_method(MODE_SMA),
                                             m_applied_price(PRICE_CLOSE)
  {
   m_log=CLog::GetLog();
  }
//+------------------------------------------------------------------+
//| Inicialización                                                   |
//+------------------------------------------------------------------+
CIndMovingAverage::Init(void)
  {
   if(m_ma_handle!=INVALID_HANDLE)
     {
      bool res=IndicatorRelease(m_ma_handle);
      if(!res)
        {
         string text="Realise iMA indicator failed. Error ID: "+(string)GetLastError();
         CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text);
         m_log.AddMessage(msg);
        }
     }
   m_ma_handle=iMA(m_symbol,m_timeframe,m_ma_period,m_ma_shift,m_ma_method,m_applied_price);
   if(m_ma_handle==INVALID_HANDLE)
     {
      string params="(Period:"+(string)m_ma_period+", Shift: "+(string)m_ma_shift+
                    ", MA Method:"+EnumToString(m_ma_method)+")";
      string text="Create iMA indicator failed"+params+". Error ID: "+(string)GetLastError();
      CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text);
      m_log.AddMessage(msg);
     }
  }
//+------------------------------------------------------------------+
//| Establecimiento del timeframe                                    |
//+------------------------------------------------------------------+
void CIndMovingAverage::Timeframe(ENUM_TIMEFRAMES tf)
  {
   m_timeframe=tf;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Devuelve el timeframe actual                                     |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CIndMovingAverage::Timeframe(void)
  {
   return m_timeframe;
  }
//+------------------------------------------------------------------+
//| Establece el período promedio de la media móvil                  |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaPeriod(int ma_period)
  {
   m_ma_period=ma_period;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Devuelve el período promedio actual de la media móvil            |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaPeriod(void)
  {
   return m_ma_period;
  }
//+------------------------------------------------------------------+
//| Establece el tipo de la media móvil                              |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaMethod(ENUM_MA_METHOD method)
  {
   m_ma_method=method;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Devuelve el tipo de la media móvil                               |
//+------------------------------------------------------------------+
ENUM_MA_METHOD CIndMovingAverage::MaMethod(void)
  {
   return m_ma_method;
  }
//+------------------------------------------------------------------+
//| Devuelve el desplazamiento de la media móvil                     |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaShift(void)
  {
   return m_ma_shift;
  }
//+------------------------------------------------------------------+
//| Establece el desplazamiento de la media móvil                    |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaShift(int ma_shift)
  {
   m_ma_shift=ma_shift;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Establece el tipo del precio para el que se calcula la media     |
//+------------------------------------------------------------------+
void CIndMovingAverage::AppliedPrice(int price)
  {
   m_applied_price = price;
   if(m_ma_handle != INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Devuelve el tipo del precio para el que se calcula la media      |
//+------------------------------------------------------------------+
uint CIndMovingAverage::AppliedPrice(void)
  {
   return m_applied_price;
  }
//+------------------------------------------------------------------+
//| Establece el símbolo en el que debe ser calculado el indicador   |
//+------------------------------------------------------------------+
void CIndMovingAverage::Symbol(string symbol)
  {
   m_symbol=symbol;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Devuelve el símbolo para el que se calcula el indicador          |
//+------------------------------------------------------------------+
string CIndMovingAverage::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
//| Devuelve el valor de la media móvil por el índice index          |
//+------------------------------------------------------------------+
double CIndMovingAverage::OutValue(int index)
  {
   if(m_ma_handle==INVALID_HANDLE)
      Init();
   double values[];
   if(CopyBuffer(m_ma_handle,0,index,1,values))
      return values[0];
   return EMPTY_VALUE;
  }

El volumen del código impresiona, ¿verdad? Y es que se trata sólo del ejemplo de uno de los indicadores no muy complicados. Dicho enfoque se complica también con el hecho de que para MetaTrader han sido creados centenares de diferentes indicadores (como los indicadores estándar, tanto los personalizados). Cada uno de ellos tiene su conjunto de propiedades y parámetros relativamente único. Si seguimos el enfoque propuesto, es necesario escribir su propio envoltorio para cada uno de los indicadores de este tipo.

En CStrategy se puede acceder a los indicadores directamente, sin usar ningunas clases. Por eso, en la práctica yo mismo a menudo llamaba a una determinada función de sistema directamente en el código de la estrategia comercial. Es que resulta mucho más fácil llamar al indicador usando los métodos estándar, que gastar el tiempo en la escritura de la clase correspondiente.

Función IndicatorCreate como la base de la interfaz universal

Como suele pasar, la solución ha sido encontrada después de la experiencia práctica del uso de CStrategy. Ha resultado evidente que el mecanismo del acceso al indicador debe poseer las siguientes propiedades:

  • Universalidad. El acceso a cualquier indicador debe basarse en el procedimiento generalizado del acceso, en vez de usar varias clases de los envoltorios.
  • Comodidad y sencillez El acceso a los valores del indicador debe ser cómodo y fácil. No tiene que depender del tipo del indicador.

La práctica del uso ha demostrado que el acceso más fácil (y lo más importante, universal) normalmente se realiza a través de las funciones iCustom e IndicatorCreate.

Ambas funciones permiten crear los indicadores como estándar, tanto personalizados. La función iCustom requiere la especificación de los parámetros reales del indicador en el momento de la compilación del programa. La función IndicatorCreate está organizada de otra manera. Ella recibe el array de las estructuras MqlParams como parámetros. Precisamente gracias al formato de la segunda función se hace posible crear el procedimiento generalizado del acceso a un indicador aleatorio. En este caso, no es necesario saber de antemano los parámetros del indicador. Debido a eso, el procedimiento del acceso se hace realmente universal.

Veremos un ejemplo real del trabajo con IndicatorCreate. Vamos a crear a través de ella el handle del indicador MovingAverage. Será el mismo indicador que se devuelve por la función iMa:

//+------------------------------------------------------------------+
//|                                                        Test2.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // Obtenemos el handle del indicador a través de iMA
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // Obtenemos el mismo handle del indicador a través de IndicatorCreate
   MqlParam ma_params_1[4];
   ma_params_1[0].type = TYPE_INT;
   ma_params_1[0].integer_value = 13;
   ma_params_1[1].type = TYPE_INT;
   ma_params_1[1].integer_value = 0;
   ma_params_1[2].type = TYPE_INT;
   ma_params_1[2].integer_value = MODE_SMA;
   ma_params_1[3].type = TYPE_INT;
   ma_params_1[3].integer_value = PRICE_CLOSE;
   int h_ma_c = IndicatorCreate(Symbol(), Period(), IND_MA, 4, ma_params_1);
   if(h_ima == h_ma_c)
      printf(«Los handles de los indicadores son iguales»);
   else
      printf(«Los handles de los indicadores son iguales»);
}

Este script obtiene el acceso al mismo indicador a través de dos interfaces diferentes: funciones iMA y IndicatorCreate. Ambas funciones devuelven el mismo handle, lo que se comprueba fácil: el inicio del indicador mostrará el mensaje «Los handles de los indicadores son iguales». Sin embargo, el acceso al indicador a través de IndicatorCreate comporta una dificultosa configuración del array MqlParams. Hay que establecer dos propiedades para cada elemento de MqlParam: el tipo de la variable y el valor de esta variable. Debido a esta corpulencia, la función IndicatorCreate se utiliza raras veces. Sin embargo, esta interfaz de la llamada permite obtener el acceso absolutamente a cualquier indicador de MQL. Precisamente por esta razón lo vamos a utilizar.

CUnIndicator es el indicador universal de CStrategy

Gracias a la Programación orientada a objetos (POO) podemos ocultar del usuario la mayor parte de la configuración de los elementos del array MqlParams, ofreciéndole una interfaz cómoda para establecer un parámetro aleatorio. Vamos a crear CUnIndicator, la clase-envoltorio sobre la función IndicatorCreate. A través de ella, se podrá crear consecutivamente un número aleatorio de parámetros para el indicador. En este caso, no habrá que especificar el tipo de los parámetros. El tipo traspasado será identificado automáticamente gracias a las plantillas. Además, nuestra clase va a tener unos cómodos indexadores en forma de corchetes '[]', dentro de los cuales se podrá indicar como el índice del valor del indicador, tanto la hora para cuyo momento habrá que obtener su valor. 

El trabajo con CUnIndicator se reducirá a las siguientes fases.

  • Establecimiento de los parámetros necesarios a través del método SetParameter
  • Creación del indicador a través del método Create
  • Establecimiento del búfer necesario a través deSetBuffer (opcional)
  • Acceso al valor del indicador por el índice aleatorio i usando el indexador []
  • Eliminación del indicador a través del método IndicatorRelease (opcional).

Escribiremos un pequeño script que crea el indicador Moving Average a través de CUnIndicator:

//+------------------------------------------------------------------+
//|                                                        Test2.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Strategy\Indicators.mqh>
CUnIndicator UnMA;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // Obtenemos el handle del indicador a través de iMA
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // Obtenemos el handle del indicador a través de CUnIndicator
   UnMA.SetParameter(13);
   UnMA.SetParameter(0);
   UnMA.SetParameter(MODE_SMA);
   int handle = UnMA.Create(Symbol(), Period(), "Examples\\Custom Moving Average");
}

Ahora la variable 'handle' contiene el handle del indicador creado, y el propio objeto UnMA permite trabajar con los valores del indicador. Por ejemplo, para obtener el valor del indicador en la barra anterior, basta con escribir el siguiente código:

double value = UnMA[1];

Vamos a ver un ejemplo más complicado. Creamos el indicador que contiene varios búferes, por ejemplo, el MACD estándar. Comentaremos detalladamente cada línea:

//+------------------------------------------------------------------+
//|                                                        Test2.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Strategy\Indicators.mqh>
input int FastEMA = 12;
input int SlowEMA = 26;
input int SignalSMA = 9;

CUnIndicator UnMACD;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   UnMACD.SetParameter(FastEMA);                            // Establecemos el valor para la media móvil rápida
   UnMACD.SetParameter(SlowEMA);                            // Establecemos el valor para la media móvil lenta
   UnMACD.SetParameter(SignalSMA);                          // Establecemos el período de la línea de señal
   int handle = UnMACD.Create(Symbol(), Period(),           // Creamos el indicador estableciendo el símbolo y timeframe para él
                "Examples\\MACD", PRICE_CLOSE);
   UnMACD.SetBuffer(MAIN_LINE);                             // Establecemos el búfer por defecto - valores de MACD
   double macd = UnMACD[1];                                 // Obtenemos el valor de MACD en la barra anterior
   UnMACD.SetBuffer(SIGNAL_LINE);                           // Establecemos la línea de señal como búfer por defecto
   double signal = UnMACD[1];                               // Obtenemos el valor de la línea de señal en la barra anterior
   datetime time_span = TimeCurrent() - PeriodSeconds();    // Calculamos la hora de apertura de la barra anterior
   double signal_by_time = UnMACD[time_span];               // Obtenemos el valor de la línea de señal para el tiempo
   printf("MACD: " + DoubleToString(macd, Digits()) +       // Mostramos los valores de MACD y la línea de señal en la barra anterior
         "; Signal: " + DoubleToString(signal, Digits()));
   if(signal == signal_by_time)                             // El acceso por la hora y el índice va a coincidir
      printf("los valores cogidos por el índice y la hora son iguales ");
}
//+------------------------------------------------------------------+

El momento más interesante en este ejemplo es la conmutación de los búferes internos del indicador a través del método SetBuffer. Así, el valor devuelto por UnMACD[1] va a diferenciarse dependiendo del búfer establecido en el momento actual. La primera vez UnMACD[1] devuelve los valores de MACD en la barra anterior. Sin embargo, si colocamos por defecto SIGNAL_LINE como búfer, UnMACD[1] devuelve el valor de la línea de señal.

El acceso a los valores del indicador es posible tanto por el índice, como por la hora. En el ejemplo se calcula la hora time_span que es igual a la apertura de la barra anterior. Si en vez del índice de UnMACD se indica esta hora, será devuelto el mismo valor que en caso de UnMACD[1].

Estructura interna de CUnIndicator

Ha llegado el momento para analizar la estructura interna de CUnIndicator. Vamos a mostrar el código fuente completo de esta clase:

//+------------------------------------------------------------------+
//|                                                   Indicators.mqh |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#include "Message.mqh"
#include "Logs.mqh"
//+------------------------------------------------------------------+
//| Clase base del indicador                                         |
//+------------------------------------------------------------------+
class CUnIndicator
{
private:
   MqlParam m_params[];
   int      m_params_count;
   int      m_current_buffer;
   int      m_handle;
   static   CLog*    Log;
   bool     m_invalid_handle;
   void     PushName(string name);
public:
            CUnIndicator(void);
   void     SetBuffer(int index);
   template <typename T>
   bool     SetParameter(T value);
   int      Create(string symbol, ENUM_TIMEFRAMES period, string name);
   int      Create(string symbol, ENUM_TIMEFRAMES period, string name, int app_price);
   int      Create(string symbol, ENUM_TIMEFRAMES period, ENUM_INDICATOR ind_type);
   int      Create(string symbol, ENUM_TIMEFRAMES period, ENUM_INDICATOR ind_type, int app_price);
   void     InitByHandle(int handle);
   void     IndicatorRelease(void);
   double   operator[](int index);
   double   operator[](datetime time);
   int      GetHandle(void);
};
CLog        *CUnIndicator::Log;
//+------------------------------------------------------------------+
//| Inicialización sin indicar el nombre                             |
//+------------------------------------------------------------------+
CUnIndicator::CUnIndicator(void) : m_params_count(0),
                                   m_handle(INVALID_HANDLE),
                                   m_current_buffer(0),
                                   m_invalid_handle(false)
{
   Log = CLog::GetLog(); 
}

//+------------------------------------------------------------------+
//| Deinicialización del indicador                                   |
//+------------------------------------------------------------------+
CUnIndicator::IndicatorRelease(void)
{
   if(m_handle != INVALID_HANDLE)
      IndicatorRelease(m_handle);
   ArrayResize(m_params, 1);
   m_params_count = 1;
   m_current_buffer = 0;
   m_handle = INVALID_HANDLE;
}

template <typename T>
bool CUnIndicator::SetParameter(T value)
{
   
   string type = typename(value);
   MqlParam param;
   if(type == "string")
   {
      param.type = TYPE_STRING;
      param.string_value = (string)value;
   }
   else if(type == "int")
   {
      param.type = TYPE_INT;
      param.integer_value = (long)value;
   }
   else if(type == "double")
   {
      param.type = TYPE_DOUBLE;
      param.double_value = (double)value;
   }
   else if(type == "bool")
   {
      param.type = TYPE_BOOL;
      param.integer_value = (int)value;
   }
   else if(type == "datetime")
   {
      param.type = TYPE_DATETIME;
      param.integer_value = (datetime)value;
   }
   else if(type == "color")
   {
      param.type = TYPE_COLOR;
      param.integer_value = (color)value;
   }
   else if(type == "ulong")
   {
      param.type = TYPE_ULONG;
      param.integer_value = (long)value;
   }
   else if(type == "uint")
   {
      param.type = TYPE_UINT;
      param.integer_value = (uint)value;
   }
   else
   {
      param.type = TYPE_INT;
      param.integer_value = (int)value;
   }
   m_params_count++;
   if(ArraySize(m_params) < m_params_count)
      ArrayResize(m_params, m_params_count);
   m_params[m_params_count-1].double_value = param.double_value;
   m_params[m_params_count-1].integer_value = param.integer_value;
   m_params[m_params_count-1].string_value = param.string_value;
   m_params[m_params_count-1].type = param.type;
   return true;
}
//+------------------------------------------------------------------+
//| Devuelve el handle del indicador                                 |
//+------------------------------------------------------------------+
int CUnIndicator::GetHandle(void)
{
   return m_handle;
}
//+------------------------------------------------------------------+
//| Establece el búfer actual del indicador                          |
//+------------------------------------------------------------------+
void CUnIndicator::SetBuffer(int index)
{
   m_current_buffer = index;
}
//+------------------------------------------------------------------+
//| Inicializa el indicador (crea su handle). Devuelve el handle     |
//| del indicador en caso del éxito, o devuelve INVALID_HANDLE       |
//| si no se ha podido crear el indicador                            |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol, ENUM_TIMEFRAMES period, string name)
{
   PushName(name);
   m_handle = IndicatorCreate(symbol, period, IND_CUSTOM, m_params_count, m_params);
   if(m_handle == INVALID_HANDLE && m_invalid_handle == false)
   {
      string text = "CUnIndicator '" + m_params[0].string_value + "' was not created. Check it's params. Last error:" + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      m_invalid_handle = true;
   }
   return m_handle;
}
//+------------------------------------------------------------------+
//| Inicializa el indicador (crea su handle). Devuelve el handle     |
//| del indicador en caso del éxito, o devuelve INVALID_HANDLE       |
//| si no se ha podido crear el indicador                            |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol, ENUM_TIMEFRAMES period, ENUM_INDICATOR ind_type)
{
   if(ind_type == IND_CUSTOM)
   {
      string text = "CUnIndicator '" + m_params[0].string_value + "' was not created. Indicator type can not be IND_CUSTOM";
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      m_invalid_handle = true;
      return INVALID_HANDLE;
   }
   m_handle = IndicatorCreate(symbol, period, ind_type, m_params_count, m_params);
   if(m_handle == INVALID_HANDLE && m_invalid_handle == false)
   {
      string text = "CUnIndicator '" + m_params[0].string_value + "' was not created. Check it's params. Last error:" + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      m_invalid_handle = true;
   }
   return m_handle;
}
//+------------------------------------------------------------------+
//| Inicializa el indicador (crea su handle). Devuelve el handle     |
//| del indicador en caso del éxito, o devuelve INVALID_HANDLE       |
//| si no se ha podido crear el indicador                            |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol,ENUM_TIMEFRAMES period,ENUM_INDICATOR ind_type,int app_price)
{
   SetParameter(app_price);
   return Create(symbol, period, ind_type);
}
//+-------------------------------------------------------------------------+
//| Coloca el nombre del indicador por el índice cero del array m_params[]  |
//+-------------------------------------------------------------------------+
void CUnIndicator::PushName(string name)
{
   int old_size = ArraySize(m_params);
   int size = ArrayResize(m_params, ArraySize(m_params) + 1);
   for(int i = 0; i < old_size; i++)
      m_params[i+1] = m_params[i];
   m_params[0].type = TYPE_STRING;
   m_params[0].string_value = name;
}
//+------------------------------------------------------------------+
//| Inicializa el indicador (crea su handle). Devuelve el handle     |
//| del indicador en caso del éxito, o devuelve INVALID_HANDLE,      |
//| si no se ha podido crear el indicador                            |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol, ENUM_TIMEFRAMES period, string name, int app_price)
{
   SetParameter(app_price);
   return Create(symbol, period, name);
}
//+------------------------------------------------------------------+
//| Inicializa la clase del indicador a base del                     |
//| handle existente del indicador                                   |
//+------------------------------------------------------------------+
void CUnIndicator::InitByHandle(int handle)
{
   this.IndicatorRelease();
   m_handle = handle;
}
//+------------------------------------------------------------------+
//| Devuelve el valor del indicador por el índice 'index'            |
//+------------------------------------------------------------------+
double CUnIndicator::operator[](int index)
{
   double values[];
   if(m_handle == INVALID_HANDLE)
      return EMPTY_VALUE;
   if(CopyBuffer(m_handle, m_current_buffer, index, 1, values) == 0)
   {
      string text = "Failed copy buffer of indicator. Last error: " + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      return EMPTY_VALUE;
   }
   return values[0];
}
//+---------------------------------------------------------------------+
//| Devuelve el valor del indicador para el momento del tiempo 'time'   |
//+---------------------------------------------------------------------+
double CUnIndicator::operator[](datetime time)
{
   double values[];
   if(m_handle == INVALID_HANDLE)
      return EMPTY_VALUE;
   
   if(CopyBuffer(m_handle, m_current_buffer, time, 1, values) == 0)
   {
      string text = "Failed copy buffer of indicator. Last error: " + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      return EMPTY_VALUE;
   }
   return values[0];
}

Como se puede ver del listado del código, el método SetParameter es de plantilla. Recibe el argumento universal T en la entrada, dependiendo del cuyo tipo se elige el tipo necesario del parámetro ENUM_DATATYPE. Este parámetro se establece para la estructura MqlParam. Las múltiples comprobaciones string no son óptimas desde el punto de vista de la velocidad, pero eso no influye en el rendimiento porque la función SetParameter tiene que llamarse sólo una vez —en el momento de la inicialización del Asesor Experto.

La clase está provista de varias versiones del método Create. Gracias a eso se puede crear tanto los indicadores personalizados (indicando en este caso el nombre string del indicador), como los indicadores estándar, cuyo tipo se puede establecer a través de INDICATOR_TYPE. Por ejemplo, se puede crear la media móvil como el indicador personalizado de la siguiente manera:

UnMA.Create(Symbol(), Period(), "Examples\\Custom Moving Average");

Aquí, UnMA es la instancia de CUnIndicator. La creación del mismo indicador en forma del estándar se hace de una manera un poco diferente:

UnMA.Create(Symbol(), Period(), IND_MA);

Además, la clase CUnIndicator contiene el método InitByHandle. Vamos a analizarla más detalladamente. Como se sabe, muchos indicadores pueden calcularse no sólo a base de los precios del instrumento, sino también a base de los datos de otro indicador. Gracias a eso, se puede crear una cadena de indicadores que calculan sus valores basándose en los valores de salida del indicador anterior. Supongamos que es necesario calcular los valores del Stochastic a base de la media móvil. Para eso hace falta inicializar dos indicadores: uno para calcular la media móvil, y el otro, para calcular el estocástico. Se puede eso de la siguiente manera:

//+------------------------------------------------------------------+
//|                                                        Test3.mq5 |
//|                                 Copyright 2017, Vasiliy Sokolov. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com"
#property version   "1.00"
#include <Strategy\Indicators.mqh>
input int MaPeriod = 12;
input int StochK = 3;
input int StochD = 12;
input int StochSmoth = 3;

CUnIndicator UnSMA;
CUnIndicator UnStoch;
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // Establecemos los parámetros y creamos el indicador SMA
   UnSMA.SetParameter(12);
   UnSMA.SetParameter(0);
   UnSMA.SetParameter(MODE_SMA);
   UnSMA.SetParameter(PRICE_CLOSE);
   int sma_h = UnSMA.Create(Symbol(), Period(),
      "\\Examples\Custom Moving Average");
   // Establecemos los parámetros y creamos el indicador Stoch,
   // calculados a base de los datos de SMA
   UnStoch.SetParameter(StochK);
   UnStoch.SetParameter(StochD);
   UnStoch.SetParameter(StochSmoth);
   UnStoch.InitByHandle(sma_h);
   double stoch_on_sma = UnStoch[1];
}
//+------------------------------------------------------------------+

El código de arriba nos muestra que el handle del indicador creado por el método Create se guarda y se utiliza para la creación del indicador Stochastic. En caso cuando se utiliza el indicador personalizado, no es obligatorio especificar la fuente de datos. No obstante, es necesario indicar esta fuente para los indicadores de sistema. Eso se puede hacer de dos maneras: usando el método SetParameter:

UnMA.SetParameter(PRICE_CLOSE);

Y también a través de la versión sobrecargada del método Create:

UnMA.Create(Symbol(), Period(), IND_MA, PRICE_CLOSE);

Mas tarde crearemos el sistema comercial de demostración que utiliza el acceso a los valores del indicador a través de la clase CUnIndicator.

Trabajo mejorado con órdenes pendientes

En uno de los artículos anteriores dedicados a CStrategy, fue presentada la clase orientada a objetos CPendingOrders que representaba una orden pendiente en el marco de CStrategy. CPendingOrders representa una clase-interfaz. No tiene ningunos miembros internos, salvo un campo que guarda el ticket de la orden. Todos sus métodos obtienen las propiedades correspondientes a través de las llamada a tres principales funciones de sistema: OrderGetInterer, OrderGetDouble y OrderGetString. La organización de este tipo permite garantizar la integridad de la representación de datos. A cada orden pendiente en MetaTrader 5 le corresponde una instancia de CPendingOrders, el ticket de la cual es igual a esta orden real. Si la orden pendiente se cancela por alguna razón (por el EA o por el usuario), el motor comercial de CStrategy elimina la instancia correspondiente de la clase CPendingOrders de la lista de las órdenes pendientes. La lista de CPendingOrders se guarda en forma de la clase especial COrdersEnvironment. Cada estrategia tiene su propia instancia única de COrdersEnvironment, llamada PendingOrders. La estrategia podía dirigirse directamente a este objeto y seleccionar de él una orden pendiente necesaria por el índice. 

Si la estrategia necesitaba abrir una orden pendiente en vez de una orden de mercado, simplemente enviaba una directiva comercial usando el módulo CTrade. En este sentido, la colocación de una orden pendiente no se diferenciaba en nada de la colocación de una orden de mercado. Pero luego se empezaban las diferencias que no se tomaban en cuenta en CStrategy. CStrategy está organizada de tal manera que cada posición comercial se pasa al código-manejador una por una. Así, para las posiciones tipo POSITION_TYPE_BUY este manejador es el método SupportBuy, y para las posiciones tipo POSITION_TYPE_SELL es el método SupportSell. Con las órdenes pendientes, todo era diferente. Cada una de estas órdenes formaba parte de la colección de PendingOrders disponible para el EA, pero estas órdenes no tenían su propio handle. Se suponía que las órdenes pendientes tenían que procesarse en algún otro lugar, por ejemplo, en OnEvent, BuySupport/SellSupport o incluso en BuyInit/SellInit. Y si no había posiciones abiertas, entonces no había llamadas a BuySupport/SellSupport, y por tanto, era posible procesar las órdenes pendientes con seguridad solamente en OnEvent. Pero el procesamiento en este método alteraba la secuencia general de las acciones. Resultaba que una parte de las posiciones se procesaba consecutivamente por turno, organizado por CStrategy, y otra parte de las posiciones (órdenes pendientes) se procesaba a la antigua, en el bloque único de OnEvent.

Por esta razón, en la nueva versión de CStrategy han sido introducidos dos métodos adicionales, SupportPendingBuy y SupportPendingSell:

class CStrategy
{
protected:
   ...   
   virtual void      SupportPendingBuy(const MarketEvent &event, CPendingOrder* order);
   virtual void      SupportPendingSell(const MarketEvent &event, CPendingOrder* order);
   ...
};

Su la signatura de la llamada parece a los método SupportBuy y SupportSell: el evento MarketEvent se pasa como primer parámetro, el segundo es el puntero a la orden actual seleccionado por CStrategy. La selección de la orden se realiza por el motor de CStrategy de forma consecutiva, usando el método de repaso. El repaso se hace del final de la lista hasta su inicio en el método CallSupport:

//+------------------------------------------------------------------------------------+
//| Llama a la lógica del acompañamiento de las posiciones con la condición de que     |
//| el estado comercial no sea igual a TRADE_WAIT.                                     |
//+------------------------------------------------------------------------------------+
void CStrategy::CallSupport(const MarketEvent &event)
  {
   ...
   for(int i = PendingOrders.Total()-1; i >= 0; i--)
   {
      CPendingOrder* order = PendingOrders.GetOrder(i);
      if(order.ExpertMagic()!=m_expert_magic)continue;
      if(order.Direction() == POSITION_TYPE_BUY)
         SupportPendingBuy(event, order);
      else
         SupportPendingSell(event, order);
   }
}

De esta manera: la llamada a los handles de las órdenes pendientes se realiza de la misma manera como en el caso con las posiciones de mercado: para cada orden pendiente de compra se llama al método SupportPendingBuy, y para cada orden de venta, el método SupportPendingSell.

El ciclo completo de la estrategia que trabaja con las órdenes pendientes resulta ser más largo que el ciclo completo de la estrategia que se basa sólo en las órdenes de mercado. En el segundo caso, se usa la secuencia compuesta de dos handles para cada dirección:

  1. apertura de una posición larga en InitBuy / apertura de una posición corta en InitSell;
  2. acompañamiento de una posición larga en SupportBuy / acompañamiento de una posición corta en SupportSell.

En caso de usar la estrategia de las órdenes pendientes, es necesario atraer tres handles para cada dirección:

  1. colocación de una posición larga en InitBuy / colocación de una posición corta en InitSell;
  2. acompañamiento de una posición pendiente larga en SupportPendingBuy hasta el momento de la activación de la orden o su cancelación / acompañamiento de una posición pendiente corta en SupportPendingSell hasta el momento de la activación de la orden o su cancelación;
  3. acompañamiento de una posición larga en SupportBuy / acompañamiento de una posición corta en SupportSell.

En realidad, la gestión de las órdenes pendientes siempre es una parte independiente de la lógica de la estrategia comercial. Por eso, la gestión separada entre las órdenes pendientes y las posiciones de mercado ha permitido disminuir la complejidad del desarrollo de semejantes estrategias.

Estrategia CImpulse 2.0 

La mejor manera de comprender las nuevas modificaciones consiste en reescribir el ejemplo ya conocido de la estrategia comercial CImpulse, que, como recordaré, ha sido presentado en la quinta parte del artículo. Su esencia consiste en lo siguiente: en cada barra se coloca una orden Stop a una cierta distancia de la media móvil. Esta distancia se expresa en por cientos. Para la compra se coloca una orden BuyStop cuyo nivel de apertura está por encima de la media móvil en N por cientos. Todo lo contrario para la venta, se coloca una orden SellStop cuyo nivel de apertura está por debajo de la media móvil en N por cientos. Nosotros vamos a cerrar la posición cuando el precio de cierre se coloque por debajo (para la compra) o por encima (para la venta) de la media.

En general, es la misma estrategia que ha sido presentada antes, pero en la nueva versión, reescrita por completo. En su ejemplo se puede apreciar los cambios realizados en CStrategy y comprender cómo usar nuevas posibilidades en la práctica. Pero primero, vamos a dirigirnos al código del EA de la versión anterior. Vamos a mostrarlo aquí enteramente para poder comparar luego dos versiones de la sintaxis:

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

input double StopPercent = 0.20;
//+------------------------------------------------------------------+
//| Determina la acción que hace falta realizar con                  |
//| la orden pendiente.                                              |
//+------------------------------------------------------------------+
enum ENUM_ORDER_TASK
{
   ORDER_TASK_DELETE,   // Eliminar la orden pendiente
   ORDER_TASK_MODIFY    // Modificar la orden pendiente
};
//+------------------------------------------------------------------+
//| Estrategia CImpulse                                              |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // Porcentaje 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;  // El trabajo con la media móvil se realiza a través de una clase escrita especialmente para ella
};
//+------------------------------------------------------------------+
//| Trabajo con ó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 debe estar por encima de la media móvil
      task = ORDER_TASK_DELETE;
   else
      task = ORDER_TASK_MODIFY;
   for(int i = PendingOrders.Total()-1; i >= 0; i--) // Se hace el repaso de las órdenes pendientes en la sección InitBuy, lo que no está muy bien
   {
      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) // El trabajo con las órdenes pendientes se realiza a través del sistema de estados
         {
            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 ó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 debe estar por encima de 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 la posición larga por 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 la posición corta por la media móvil Moving    |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Filtrar los eventos entrantes. Si el evento pasado               |
//| no se procesa por la estrategia, devuelve false,                 |
//| si se procesa, devuelve true.                                    |
//+------------------------------------------------------------------+
bool CImpulse::IsTrackEvents(const MarketEvent &event)
  {
//Procesamos sólo la apertura de la barra nueva en el instrumento y timeframe de trabajo
   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 del símbolo                               |
//+------------------------------------------------------------------+
void CImpulse::OnSymbolChanged(string new_symbol)
  {
   Moving.Symbol(new_symbol);
  }
//+------------------------------------------------------------------+
//| Reaccionamos al cambio del timeframe                             |
//+------------------------------------------------------------------+
void CImpulse::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf)
  {
   Moving.Timeframe(new_tf);
  }
//+------------------------------------------------------------------+
//| Devuelve el por ciento del nivel de ruptura                      |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| Establece el por ciento del nivel de ruptura                     |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

Los momentos más problemáticos en la implementación de esta estrategia están marcados con color amarrillo.

Primero, el trabajo con el indicador se realiza a través de la clase escrita anteriormente, CIndMovingAverage. Ya hemos dicho que este enfoque es irracional. Los indicadores son muchos para escribir la clase para cada uno de ellos.

Segundo, el trabajo con las órdenes pendientes se hace a través del repaso completo de las órdenes pendientes en los bloques BuyInit/SellInit. En la estrategia tan simple como CImpulse, eso no supone dificultades, pero en caso con el acompañamiento más complejo de las órdenes, los problemas sí que pueden surgir. Es mejor separar la colocación de las órdenes pendientes y el proceso de su acompañamiento en unos métodos separados, como eso está hecho en la nueva versión de CStrategy.

Si nos fijamos con atención en el código de CImpulse, se hace claro que CImpulse se encarga de una parte de la funcionalidad que debe proporcionar CStrategy. CStrategy debe marcar el sistema de los estados para gestionar las órdenes pendientes, sin embargo, no lo hace: CImpulse implementa el sistema personalmente.

Vamos a reescribir el código considerando las nuevas posibilidades de CStrategy:

//+------------------------------------------------------------------+
//|                                                  Impulse 2.0.mqh |
//|           Copyright 2017, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/es/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2017, Vasiliy Sokolov."
#property link      "https://www.mql5.com/es/users/c-4"
#include "Strategy\Strategy.mqh"
#include "Strategy\Indicators.mqh"

input int PeriodMA = 12;
input double StopPercent = 0.05;

//+------------------------------------------------------------------+
//| Estrategia CImpulse                                              |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // Por ciento del nivel de la orden pendiente
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      SupportPendingBuy(const MarketEvent &event,CPendingOrder *order);
   virtual void      SupportPendingSell(const MarketEvent &event,CPendingOrder* order);
   virtual bool      OnInit(void);
public:
   double            GetPercent(void);
   void              SetPercent(double percent);
   CUnIndicator      UnMA;
};
//+------------------------------------------------------------------+
//| Inicializamos la media móvil                                     |
//+------------------------------------------------------------------+
bool CImpulse::OnInit(void)
{
   UnMA.SetParameter(12);
   UnMA.SetParameter(0);
   UnMA.SetParameter(MODE_SMA);
   UnMA.SetParameter(PRICE_CLOSE);
   m_percent = StopPercent;
   if(UnMA.Create(Symbol(), Period(), IND_MA) != INVALID_HANDLE)
      return true;
   return false;
}
//+------------------------------------------------------------------+
//| Colocación de las órdenes pendientes BuyStop                     |
//+------------------------------------------------------------------+
void CImpulse::InitBuy(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // Creamos las órdenes pendientes sólo en la apertura de una barra nueva
   if(PositionsTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)   // No tiene que haber ninguna posición larga abierta
      return;
   if(OrdersTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)      // Ni tiene que haber ninguna orden pendiente de compra
      return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // Calculamos el nivel de la orden pendiente nueva
   if(target < UnMA[0])                                                       // El precio de activación de la orden debe estar por encima de la media móvil
      return;
   Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);       // Colocamos la nueva orden BuyStop
}
//+------------------------------------------------------------------+
//| Trabajo con órdenes pendientes BuyStop para abrir                |
//| una posición corta                                               |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingBuy(const MarketEvent &event,CPendingOrder *order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // Calculamos el nuevo nivel de la orden pendiente
   if(UnMA[0] > target)                                                       // Si el nuevo nivel está por debajo de la media actual
      order.Delete();                                                         // - lo eliminamos
   else                                                                       // De lo contrario, modificamos reemplazando por el precio nuevo
      order.Modify(target);
}
//+------------------------------------------------------------------+
//| Trabajo con órdenes pendientes SellStop para abrir               |
//| una posición corta                                               |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingSell(const MarketEvent &event,CPendingOrder* order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() - WS.Ask()*(m_percent/100.0);                     // Calculamos el nuevo nivel de la orden pendiente
   if(UnMA[0] < target)                                                       // Si el nuevo nivel está por encima de la media actual
      order.Delete();                                                         // - lo eliminamos
   else                                                                       // De lo contrario, modificamos reemplazando por el precio nuevo
      order.Modify(target);
}
//+------------------------------------------------------------------+
//| Colocación de las órdenes pendientes SellStop                    |
//+------------------------------------------------------------------+
void CImpulse::InitSell(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // Creamos las órdenes pendientes sólo en la apertura de una barra nueva
   if(PositionsTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)  // No tiene que haber ninguna posición corta abierta
      return;
   if(OrdersTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)     // No tiene que haber ninguna orden pendiente de venta
      return;
   double target = WS.Bid() - WS.Bid()*(m_percent/100.0);                     // Calculamos el nivel de la nueva orden pendiente
   if(target > UnMA[0])                                                       // El precio de activación de la orden debe estar por debajo de la media móvil
      return;  
   Trade.SellStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);      // Colocamos la nueva orden BuyStop
}
//+------------------------------------------------------------------+
//| Acompañamiento de la posición larga por la media móvil Moving    |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   int bar_open = WS.IndexByTime(pos.TimeOpen());
   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 = WS.Bid() - WS.Bid()*(m_percent/100.0);
      if(target < UnMA[0])
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(WS.Bid() < UnMA[0])
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Acompañamiento de la posición corta por 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 = WS.Ask() + WS.Ask()*(m_percent/100.0);
      if(target > UnMA[0])
         pos.StopLossValue(target);
      else
         pos.StopLossValue(0.0);
   }
   if(WS.Ask() > UnMA[0])
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| Devuelve el por ciento del nivel de ruptura                      |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| Establece el por ciento del nivel de ruptura                     |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   CImpulse* impulse = new CImpulse();
   impulse.ExpertMagic(140578);
   impulse.ExpertName("Impulse 2.0");
   impulse.Timeframe(Period());
   impulse.ExpertSymbol(Symbol());
   Manager.AddStrategy(impulse);
   return(INIT_SUCCEEDED);
   
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnStart()
{
   Manager.OnTick();
}
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
{
   Manager.OnChartEvent(id, lparam, dparam, sparam);
   ChartRedraw(0);
}

Un fragmento de la simulación de la estrategia CIpmulse 2.0 en el Probador de estrategias se muestra en la siguiente captura de pantalla. Ahí se puede observar las órdenes pendientes colocadas y el trabajo con ellas:


Fig. 1. Trabajo con las órdenes pendientes durante la prueba de la estrategia Impulse 2.0

A pesar de que la lógica sigue siendo la misma, el código anterior y el actual han salido diferentes. Vamos a enumerar las diferencias de la nueva versión de la antigua:

  • El acceso al instrumento actual se realiza a través de la instancia de CSymbol WS (work symbol). Ahora a esta clase se le delegan todas las propiedades del instrumento.
  • Los datos del EA se inicializan en el método OnInit. Cuando se escribía la quinta parte del artículo, este método todavía no existía.
  • Ahora, en vez del indicador CIndMovingAverage, se utiliza el indicador universal CUnIndicator.
  • La colocación de las órdenes pendientes se realiza en InitBuy/InitSell, y su modificación y eliminación, en SupportPendingBuy/SupportPendingSell.
  • El repaso de las órdenes pendientes ya no se utiliza. Esta función se delega a CStrategy.
  • Para calcular el número de las posiciones actuales y órdenes pendientes, se usan los métodos PositionsTotal y OrdersTotal, en vez de la estructura position.

El listado presentado contiene el código de la estrategia y las funciones básicas del EA. Es decir, este ejemplo está presentado en forma de un archivo mq5 único. Eso se debe al hecho de que la estructura del proyecto ha sido reorganizada considerablemente. Pues, hablaremos de eso a continuación.

Nueva estructura del proyecto CStrategy

En las versiones anteriores, el motor comercial de CStrategy se encontraba en varias subcarpetas MQL5. Así, por ejemplo, el propio motor comercial y sus archivos auxiliares se encontraban en MQL5\Include\Strategy. Los códigos fuente responsables de la implementación del panel comercial del motor estaban ubicados en MQL5\Include\Panel. El código del Asesor Experto podía encontrarse en MQL5\Include\Strategy\Samples, y el archivo mq5 del inicio del Asesor Experto, en MQL5\Experts\Article08. Y todo eso, sin contar que diferentes componentes auxiliares como Dictionary.mqh o XmlBase.mqh también estaban distribuidos por varias subcarpetas de la carpeta Include.

Es evidente que los enlaces formados en el proyecto se han hecho más complejos, y la ubicación de los archivos y carpetas a menudo se duplica. Eso complica un posible conocimiento del motor comercial CStrategy. El usuario, sobre todo un principiante, puede confundirse fácilmente y no comprender de dónde viene una u otra cosa y cómo al final se realiza el proceso de la compilación. Por esa razón, a partir de la versión actual del motor comercial, sus archivos van a ubicarse de otra manera.

Ahora el proyecto entero va a encontrarse en el catálogo MQL5\Experts\UnExpert. Ahí se encuentra la carpeta Strategy y los archivos de la estrategia con la extensión .mq5. Ahora la estrategia final es el único archivo mq5 en el que se encuentran tanto las funciones-manejadores estándar de eventos (como OnInit  y OnTick), como la propia clase de la estrategia basada en CStrategy.

Todos los archivos auxiliares también se ubican en MQL5\Experts\UnExpert\Strategy. Eso también se refiere a los archivos para el trabajo con XML y con los archivos de la infraestructura, como Dictionary.mqh. Para compilar el ejemplo, basta con abrir el archivo, por ejemplo "MQL5\Experts\UnExpert\Impulse 2.0.mqh", y pulsar el botón «Compilar».

En la versión presentada en esta parte del artículo como ejemplo, se usan sólo dos estrategias: Impulse 1.0 y Impulse 2.0. Se trata de la misma estrategia, pero que ha sido escrita en el estilo antiguo y nuevo de CStrategy. Ha sido hecho a propósito para poder comparar ambos enfoques en acción y comprender las diferencias descritas en este artículo. Otros ejemplos de las estrategias que han sido incluidas en las versiones anteriores de CStrategy no están disponibles en la versión actual. Eso se debe a que ellas se basan en la sintaxis antigua y por eso no pueden ser presentadas como ejemplos. Probablemente aparezcan en la siguientes versiones, pero ya con la sintaxis modificada.

Conclusión

Hemos analizado los nuevos componentes de CStrategy. Se trata de la clase CUnIndicator que implementa una interfaz universal en el estilo de POO para trabajar con cualquier indicador MQL5, tanto de sistema, como personalizado, así como el sistema de acompañamiento de las órdenes pendientes a base de los métodos SupportPendingBuy y SupportPendingSell. En conjunto, todos estos elementos dan un potente efecto sinérgico al escribir el Asesor Experto comercial. El usuario no tendrá que pensar en las operaciones de niveles bajos. Podrá acceder prácticamente a todo el entorno comercial a través de las clases concisas e intuitivamente comprensibles, mientras que la propia lógica comercial se establece con la redifinición sencilla de los métodos correspondientes predefinidos. 

El propio proyecto ahora se ubica en el mismo lugar, y sus referencias están limitadas por el catálogo MQL5\Experts\UnExpert. Ahora no hay necesidad de ubicar los archivos en diferentes carpetas del directorio MQL5. Esta novedad también debe estimular a los usuario a pasar a CStrategy o, por lo menos, a un interesante estudio de sus posibilidades.

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

Archivos adjuntos |
UnExpert.zip (680.21 KB)
Asesor Experto multiplataforma: Niveles stop Asesor Experto multiplataforma: Niveles stop
En este artículo se analiza la implementación de niveles stop en el asesor comercial, la implementación es compatible con las plataformas MetaTrader 4 y MetaTrader 5.
Examinamos en la práctica el método adaptativo del seguimiento del mercado Examinamos en la práctica el método adaptativo del seguimiento del mercado
La principal diferencia del sistema de trading que se propone en el artículo consiste en el uso de las herramientas matemáticas para analizar las cotizaciones bursátiles. En este sistema, se aplica la filtración digital y la estimación espectral de las series temporales discretas. Han sido descritos los aspectos teóricos de la estrategia y ha sido construido el Asesor Experto (EA) para testearla.
Evaluación del riesgo en la secuencia de transacciones con un activo Evaluación del riesgo en la secuencia de transacciones con un activo
En este artículo se describe el uso de los métodos del cálculo de probabilidades y la estadística matemática en el análisis de los sistemas comerciales.
Neuroredes profundas (Parte II). Desarrollo y selección de predictores Neuroredes profundas (Parte II). Desarrollo y selección de predictores
En este segundo artículo de la serie sobre redes neuronales profundas se analizarán la transformación y la selección en el proceso de preparación de los datos para el entrenamiento del modelo.