English Русский 中文 Español Deutsch 日本語
Expert Advisor universal: indicador CUnIndicator e trabalho com ordens pendentes (parte 9)

Expert Advisor universal: indicador CUnIndicator e trabalho com ordens pendentes (parte 9)

MetaTrader 5Exemplos | 17 outubro 2017, 13:51
1 765 0
Vasiliy Sokolov
Vasiliy Sokolov

Sumário

Introdução

Continua o desenvolvimento da base do código fonte para o Expert Advisor Universal. A maioria das abordagens por trás do motor de negociação do CStrategy, na prática, têm demonstrado sua eficiência, conveniência e simplicidade. No entanto, durante o uso real, tiveram de ser revistos alguns momentos do trabalho com o expert.

Um desses momentos foi o trabalho com os indicadores. O terceiro artigo desta série foi oferecido à abordagem clássica orientada a objetos para trabalhar com indicadores. Nele se falava que cada indicador é uma classe orientada a objetos com métodos próprios de instalação e obtenção de propriedades. No entanto, na prática, é difícil de levar a cabo a implementação de uma classe wrapper própria para cada indicador. Aqui, consideramos uma nova forma de trabalhar com indicadores no estilo POO que não exigem que você escreva módulos de classe separados.

A segunda alteração, descrita neste artigo, tem a ver com a introdução de procedimentos de controle total de ordens pendentes. Antigamente as ordens pendentes tinham de ser controladas diretamente no código da estratégia final, agora parte dessas funções são delegadas ao motor do CStrategy. Atualmente, a estratégia-classe final pode redefinir os métodos SupportBuyPending e SupportSellPending e começar a gerir os pedidos pendentes, semelhante ao controle de posições ativas.

Acesso aos indicadores em versões anteriores do CStrategy

Para entender o problema da questão, procederemos a resolver o trabalho com os indicadores do terceiro artigo da série, mencionado acima. Ele oferecia trabalhar com qualquer indicador através da classe wrapper. Assim, por exemplo, para trabalhar com o indicador IMA, no modelo era utilizada a classe de encapsulamento CIndMovingAverage. A própria classe CIndMovingAverage consistia nos métodos que definiam ou retornavam certa propriedade do indicador, bem como o método Init que chamava a função de sistema IMA. Apesar de a classe do indicador que ter tido uma estrutura simples, ocupava uma quantidade substancial de código que devia ser implementado pelo próprio usuário. Aqui está o código fonte desta classe, para avaliar a quantidade de trabalho atribuído a ele:

//+------------------------------------------------------------------+
//|                                                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;         // Identificador do indicador
   ENUM_TIMEFRAMES   m_timeframe;         // Timeframe
   int               m_ma_period;         // Período
   int               m_ma_shift;          // Deslocamento
   string            m_symbol;            // Instrumento
   ENUM_MA_METHOD    m_ma_method;         // Método de média móvel
   uint              m_applied_price;     // Identificador do indicador em que é exigido calcular o valor do Moving Average,
                                          // ou um dos valores de preços ENUM_APPLIED_PRICE
   CLog*             m_log;               // Login
   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);
  };
//+------------------------------------------------------------------+
//| Construtor por padrão                                            |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+
//| Inicialização                                                    |
//+------------------------------------------------------------------+
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);
     }
  }
//+------------------------------------------------------------------+
//| Definição do timeframe                                           |
//+------------------------------------------------------------------+
void CIndMovingAverage::Timeframe(ENUM_TIMEFRAMES tf)
  {
   m_timeframe=tf;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Retorna o timeframe atual                                        |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CIndMovingAverage::Timeframe(void)
  {
   return m_timeframe;
  }
//+------------------------------------------------------------------+
//| Define o período médio de média móvel                            |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaPeriod(int ma_period)
  {
   m_ma_period=ma_period;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Retorna o período atual médio de média móvel                     |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaPeriod(void)
  {
   return m_ma_period;
  }
//+------------------------------------------------------------------+
//| Define o tipo de média móvel                                     |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaMethod(ENUM_MA_METHOD method)
  {
   m_ma_method=method;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Retorna o tipo de média móvel                                    |
//+------------------------------------------------------------------+
ENUM_MA_METHOD CIndMovingAverage::MaMethod(void)
  {
   return m_ma_method;
  }
//+------------------------------------------------------------------+
//| Retorna o deslocamento da média móvel                            |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaShift(void)
  {
   return m_ma_shift;
  }
//+------------------------------------------------------------------+
//| Define o deslocamento da média móvel                             |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaShift(int ma_shift)
  {
   m_ma_shift=ma_shift;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Define o tipo de preço, para o qual é calculada a média          |
//+------------------------------------------------------------------+
void CIndMovingAverage::AppliedPrice(int price)
  {
   m_applied_price = price;
   if(m_ma_handle != INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Retorna o tipo de preço, para o qual é calculada a média         |
//+------------------------------------------------------------------+
uint CIndMovingAverage::AppliedPrice(void)
  {
   return m_applied_price;
  }
//+------------------------------------------------------------------+
//| Define o símbolo em que deve ser calculado o indicador           |
//+------------------------------------------------------------------+
void CIndMovingAverage::Symbol(string symbol)
  {
   m_symbol=symbol;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| Retorna o símbolo, para o qual é calculado indicador             |
//+------------------------------------------------------------------+
string CIndMovingAverage::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
//| Retorna o valor da média móvel de acordo com o í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;
  }

O volume do código é impressionante, embora seja somente o exemplo de um indicador simples. Essa abordagem se torna mais complexa, uma vez que, para o MetaTrader, existem centenas de diferentes indicadores, tanto padrão quanto personalizados. Cada um deles tem seu próprio conjunto relativamente único de propriedades e parâmetros. Se nós seguirmos a abordagem sugerida, será necessário escrever um wrapper próprio para cada um desses indicadores. 

No CStrategy pode se acessar aos indicadores diretamente, sem ter de usar classes. Por isso, na prática, eu mesmo muitas vezes chamei diretamente uma função específica de sistema no código da estratégia de negociação. É que é muito mais fácil chamar o indicador por métodos padrão, em vez de gastar tempo escrevendo a classe correspondente.

Função IndicatorCreate, a base da interface universal

A solução, como tantas vezes acontece, apareceu após experimentar com o CStrategy. Tornou-se evidente que o mecanismo de acesso ao indicador deve ter as seguintes propriedades:

  • Universalidade. O acesso a qualquer indicador deve se basear num procedimento de acesso generalizado, em vez de usar muitas classes de encapsulamento.
  • Usabilidade. Acesso aos valores do indicador deve ser confortável e fácil. Ele não deve depender do tipo de indicador.

A prática tem demonstrado que o mais simples, e mais importante, acesso universal ao indicador geralmente ocorre através das funções iCustom e IndicatorCreate.

Ambos os recursos permitem criar indicadores padrão e personalizados. A função iCustom precisa que se definam os parâmetros reais do indicador no momento da compilação do programa. A função IndicatorCreate está organizada de forma diferente. Como parâmetros ela aceita a matriz de estruturas MqlParams. É devido ao formato da segunda função que é possível criar um procedimento de acesso generalizado ao indicador arbitrário. Além disso, não é necessário conhecer os parâmetros do indicador, graças a isso, o procedimento de acesso se torna verdadeiramente universal.

Examinemos um exemplo específico de trabalho com IndicatorCreate. Criamos com sua ajuda o identificador do indicador MovingAverage. Ele será o mesmo indicador que é retornado pela função 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()
{
   // Obtemos o identificador para o indicador através da função iMA
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // Obtemos o mesmo identificador do indicador através da função 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("Identificadores dos indicadores são iguais");
   else
      printf("Identificadores dos indicadores não são iguais");
}

O script apresentado obtém acesso ao mesmo indicador através de duas interfaces diferentes, isto é, as funções IMA e IndicatorCreate. Ambas as funções retornam o mesmo identificador, oque pode ser facilmente verificado: a inicialização do indicador faz com que se exiba a mensagem "Os identificadores dos indicadores são iguais". No entanto, o acesso ao indicador através da IndicatorCreate vem acompanhado de uma configuração tediosa da matriz MqlParams. Cada elemento da MqlParam precisa de duas propriedades: o tipo de variável e valor dessa variável. Devido ao caráter dispendioso da função IndicatorCreate, ela é utilizada com pouca frequência. No entanto, essa interface de chamada permite o acesso a absolutamente qualquer indicador em MQL. Por isso, é que vamos usá-lo.

CUnIndicator, o indicador universal do CStrategy

Graças à programação orientada a objetos, podemos ocultar ao usuário a maior parte da configuração dos elementos da matriz MqlParams, fornecendo-lhe com uma interface amigável para definir um parâmetro arbitrário. Criamos o CUnIndicator, ele é uma classe wrapper da função IndicatorCreate. Com ele será possível definir um número arbitrário de parâmetros para o indicador, de maneira sequencial. Ao fazer isso, não será necessário definir o tipo de parâmetro. Graças aos modelos, o tipo transmitido modelos será determinado automaticamente. Além disso, nossa classe terá uns confortáveis indexadores na forma de '[]' colchetes, dentro dos quais será possível especificar o valor do índice do indicador, bem como o momento em que será necessário obter esse valor. 

O trabalho com a CUnIndicator se reduz às seguintes etapas.

  • Definição dos parâmetros requeridos através do método SetParameter
  • Criação direta do indicador pelo método Create
  • Definição do buffer exigido através do método SetBuffer (opcional)
  • Acesso ao valor do indicador pelo índice arbitrário i através do indexador []
  • Remoção do indicador pelo método IndicatorRelease (opcional).

Escrevamos um pequeno script para criar o indicador Moving Average através da 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()
{
   // Obtemos o identificador para o indicador através da função iMA
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // Obtemos o identificador do indicador através da classe CUnIndicator
   UnMA.SetParameter(13);
   UnMA.SetParameter(0);
   UnMA.SetParameter(MODE_SMA);
   int handle = UnMA.Create(Symbol(), Period(), "Examples\\Custom Moving Average");
}

Agora a variável 'handle' contém o identificador do indicador criado, enquanto o objeto UnMA em si permite trabalhar com os valores do indicador. Por exemplo, para obter o valor do indicador na barra anterior, basta escrever o seguinte código:

double value = UnMA[1];

Consideremos o exemplo mais complexo. Criamos um indicador contendo vários buffers, por exemplo, o MACD padrão. Fornecemos comentários detalhados a cada cadeia de caracteres:

//+------------------------------------------------------------------+
//|                                                        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);                            // Definimos o valor da média móvel rápida
   UnMACD.SetParameter(SlowEMA);                            // Definimos o valor da média móvel lenta
   UnMACD.SetParameter(SignalSMA);                          // Definimos o período da linha de sinal
   int handle = UnMACD.Create(Symbol(), Period(),           // Criamos o indicador definindo o símbolo e timeframe para ele
                "Examples\\MACD", PRICE_CLOSE);
   UnMACD.SetBuffer(MAIN_LINE);                             // Definimos o buffer por padrão, isto é, os valores do MACD
   double macd = UnMACD[1];                                 // Obtemos o valor do MACD na barra anterior
   UnMACD.SetBuffer(SIGNAL_LINE);                           // Definimos a linha de sinal como buffer padrão
   double signal = UnMACD[1];                               // Obtemos o valor da linha de sinal na barra anterior
   datetime time_span = TimeCurrent() - PeriodSeconds();    // Calculamos o tempo de abertura da barra anterior
   double signal_by_time = UnMACD[time_span];               // Obtemos os valores da linha de sinal para o tempo
   printf("MACD: " + DoubleToString(macd, Digits()) +       // Exibimos os valores do MACD e da linha de sinal na barra anterior
         "; Signal: " + DoubleToString(signal, Digits()));
   if(signal == signal_by_time)                             // O tempo de acesso ao índice será o mesmo
      printf("os valores tomados pelo índice e o tempo são iguais");
}
//+------------------------------------------------------------------+

O momento mais interessante, neste exemplo, é a alternância de buffers internos através do método SetBuffer. Assim, o valor devolvido pelo UnMACD[1] variará dependendo do buffer definido atualmente. Pela primeira vez a UnMACD[1] retorna o valor do MACD na barra anterior. No entanto, se estiver definido SIGNAL_LINE como buffer padrão, o UnMACD[1] retornará o valor da linha de sinal.

O acesso aos valores do indicador é possível pelo índice e o tempo. No exemplo, calcula-se se o tempo time_span é igual à abertura da barra anterior. Se, em vez do índice UnMACD, for especificado esse tempo, será devolvido o mesmo valor do UnMACD[1].

Dispositivo interno do CUnIndicator

É hora de desmontar o dispositivo interno do CUnIndicator. Aqui está o código fonte completo dessa classe:

//+------------------------------------------------------------------+
//|                                                   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"
//+------------------------------------------------------------------+
//| Classe base do 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;
//+------------------------------------------------------------------+
//| Inicialização sem especificar o nome                             |
//+------------------------------------------------------------------+
CUnIndicator::CUnIndicator(void) : m_params_count(0),
                                   m_handle(INVALID_HANDLE),
                                   m_current_buffer(0),
                                   m_invalid_handle(false)
{
   Log = CLog::GetLog(); 
}

//+------------------------------------------------------------------+
//| Exclusão da inicialização do 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;
}
//+------------------------------------------------------------------+
//| Retorna o identificador do indicador                             |
//+------------------------------------------------------------------+
int CUnIndicator::GetHandle(void)
{
   return m_handle;
}
//+------------------------------------------------------------------+
//| Define o buffer atual do indicador                               |
//+------------------------------------------------------------------+
void CUnIndicator::SetBuffer(int index)
{
   m_current_buffer = index;
}
//+----------------------------------------------------------------------------+
//| Inicializa o indicador (cria seu identificador). Retorna o identificador   |
//| do indicador, em caso de sucesso, caso contrário, retorna INVALID_HANDLE,  |
//| se não foi possível criar o 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 o indicador (cria seu identificador). Retorna o identificador   |
//| do indicador, em caso de sucesso, caso contrário, retorna INVALID_HANDLE,  |
//| se não foi possível criar o 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 o indicador (cria seu identificador). Retorna o identificador    |
//| do indicador, em caso de sucesso, caso contrário, retorna INVALID_HANDLE    |
//| se não foi possível criar o 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 o nome do indicador numa matriz de índice zero 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 o indicador (cria seu identificador). Retorna o identificador   |
//| do indicador, em caso de sucesso, caso contrário, retorna INVALID_HANDLE,  |
//| se não foi possível criar o indicador                                      |
//+----------------------------------------------------------------------------+
int CUnIndicator::Create(string symbol, ENUM_TIMEFRAMES period, string name, int app_price)
{
   SetParameter(app_price);
   return Create(symbol, period, name);
}
//+------------------------------------------------------------------+
//| Inicializa a classe do indicador com base em uma já existente    |
//| do identificador do indicador                                    |
//+------------------------------------------------------------------+
void CUnIndicator::InitByHandle(int handle)
{
   this.IndicatorRelease();
   m_handle = handle;
}
//+------------------------------------------------------------------+
//| Retorna o valor do indicador de acordo com o í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];
}
//+------------------------------------------------------------------+
//| Retorna o valor do índicador no tempo '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];
}

A partir da listagem do código torna-se evidente que o método SetParameter é genérico. Ele aceita para entrada o argumento genérico T, e dependendo de seu tipo é selecionado o tipo de parâmetro necessário ENUM_DATATYPE. Esse parâmetro é definido para a estrutura MqlParam. Inúmeras verificações de cadeia não são ideais em termos de velocidade, mas o desempenho não é afetado, uma vez que a função SetParameter deve ser chamada apenas uma vez, no momento da inicialização do expert.

A classe vem acompanhada de numerosas variações do método Create. Graças a isso, podem-se criar indicadores personalizados (indicando o nome de cadeia do indicador) e indicadores padrão cujo tipo pode ser definido através do INDICATOR_TYPE. Por exemplo, a criação de uma média móvel como um indicador personalizado pode ser realizada da seguinte maneira:

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

Aqui UnMA é uma instância da CUnIndicator. A criação do mesmo indicador sob a forma padrão ocorre um pouco diferente:

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

Também a classe CUnIndicator contém o método InitByHandle. Iremos considerá-lo mais detalhadamente. Como você sabe, muitos indicadores podem ser calculados não só sobre os preços dos instrumentos, mas também nos dados de outro indicador. Graças a isso, pode-se criar uma cadeia de indicadores que calculam seu valor sobre o valores de saída dos anteriores. Suponhamos que é necessário calcular o valor do Stochastic na média móvel. Para fazer isso, é preciso inicializar os dois indicadores, ou seja, um para o cálculo da média móvel e o outro para o cálculo do estocástico. Isso pode ser feito da seguinte forma:

//+------------------------------------------------------------------+
//|                                                        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()
{
   // Definimos os parâmetros e criamos o 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");
   // Definimos os parâmetros e criamos o indicador Stoch,
   // calculados nos dados do SMA
   UnStoch.SetParameter(StochK);
   UnStoch.SetParameter(StochD);
   UnStoch.SetParameter(StochSmoth);
   UnStoch.InitByHandle(sma_h);
   double stoch_on_sma = UnStoch[1];
}
//+------------------------------------------------------------------+

A partir do código acima pode ser visto que o identificador do indicador, originado pelo método Create, é armazenado e usado para criar o indicador Stochastic. Quando se usa o indicador personalizado, não é necessário especificar a fonte de dados. No entanto, deve se especificar a fonte para indicadores de sistema. Isso pode ser feito de duas maneiras, quer dizer, através de método SetParameter:

UnMA.SetParameter(PRICE_CLOSE);

Bem como por meio da versão sobrecarregada do método Create:

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

Mais tarde, criaremos um sistema de negociação de demonstração que utiliza o acesso aos valores do indicador através da classe CUnIndicator.

Trabalho avançado com ordens pendentes

Um dos anteriores artigos, dedicado ao CStrategy, mostrava a classe orientada a objetos CPendingOrders que apresentava a ordem pendente dentro do CStrategy. CPendingOrders é uma classe-interface. Ele não tem membros internos, exceto o campo que contém o bilhete da ordem. Todos seus métodos recebem as propriedades correspondentes através da chamada das três funções de sistema principais, isto é, OrderGetInterer, OrderGetDouble e OrderGetString. Essa organização permite garantir a integridade dos dados. A cada ordem pendente no MetaTrader 5 corresponde uma instância da CPendingOrders, cujo bilhete é igual a essa ordem real. Se a ordem pendente por algum motivo é cancelada (pelo expert advisor ou usuário), o motor negociação do CStrategy exclui a instância correspondente da classe CPendingOrders da lista de ordens pendentes. A lista da classe CPendingOrders é armazenada como uma classe especial COrdersEnvironment. Cada estratégia tem sua própria instância COrdersEnvironment, chamada PendingOrders. A estratégia pode acessar diretamente esse objeto e escolher a ordem pendente necessária de acordo como o índice. 

Quando a estratégia necessitava, em vez de colocar uma ordem de mercado, posicionar uma ordem pendente, ela simplesmente enviava a ordem de negociação correspondente através do módulo CTrade. A este respeito, a colocação da ordem pendente não diferia do posicionamento de uma ordem de mercado. Mas, logo, surgiam as diferenças que o CStrategy não levava em conta. CStrategy é construído de modo que cada posição de mercado é enviada para o código-manipulador uma por uma. Assim, nas posições do tipo POSITION_TYPE_BUY, esse manipulador é o método SupportBuy, enquanto na posição do tipo POSITION_TYPE_SELL é o método SupportSell. Com as ordens pendentes tudo era diferente. Cada ordem desse genro estava disponível - para o expert advisor - na coleção PendingOrders, mas seu manipulador não tinha esse tipo de ordens. Entendia-se que as ordens pendentes deviam ser processadas em outro lugar, por exemplo, em OnEvent, BuySupport/SellSupport ou mesmo em BuyInit/SellInit. Ao acontecer isso, quando não havia ordem pendente, não havia chamada de BuySupport/SellSupport, e, portanto, só era possível processar as ordens pendentes em OnEvent de forma confiável. Mas o processamento no método afetava o fluxo geral de ações. Saia bem que parte das posições eram processadas sequencialmente, graças ao CStrategy, e outra parte (ordens pendentes) eram processadas pela maneira antiga, num único bloco do OnEvent.

Devido a isso, na nova versão do CStrategy foram introduzidos dois métodos adicionais, isto é, SupportPendingBuy e SupportPendingSell:

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

Sua assinatura de chamada é semelhante aos métodos SupportBuy e SupportSell, uma vez que o evento MarketEvent é enviado pelo primeiro parâmetro, enquanto que o ponteiro para a ordem atual - selecionado pelo CStrategy - é transferido pelo segundo parâmetro. O motor do CStrategy realiza a seleção da ordem pelo método de busca exaustiva. A busca exaustiva é realizada do final para o início da lista de ordens, no método CallSupport:

//+------------------------------------------------------------------+
//| Chama a lógica de acompanhamento das posições se o estado        |
//| de negociação não é 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);
   }
}

Assim, a chamada dos manipuladores das ordens pendentes ocorre da mesma forma como no caso de posições de mercado, ou seja, para cada ordem pendente para compra, é chamado o método SupportPendingBuy, enquanto para a cada ordem de venda, o método SupportPendingSell.

O ciclo completo da estratégia, que trabalha com ordens pendentes, surge como resultado do longo ciclo de uma estratégia baseada só em ordens de mercado. No segundo caso, usa-se uma sequência de dois manipuladores em cada sentido:

  1. abertura da posição longa em InitBuy / Abertura da posição curta em InitSell;
  2. acompanhamento da posição longa em SupportBuy / Acompanhamento da posição curta em SupportSell.

Em caso de usar ordens pendentes, é necessário empregar três manipuladores para cada direção:

  1. posicionamento da posição longa pendente em InitBuy / posicionamento da posição curta pendente em InitSell;
  2. acompanhamento da ordem longa pendente em SupportPendingBuy até a ordem ser executada ou cancelada / acompanhamento da ordem curta pendente em SupportPendingSell até a ordem ser executada ou cancelada;
  3. acompanhamento da posição longa em SupportBuy / acompanhamento da posição curta em SupportSell.

De fato, o gerenciamento de ordens pendentes é sempre uma parte independente da lógica da estratégia de negociação. Por isso, o gerenciamento separado entre as ordens pendentes e as posições de mercado permitiu reduzir a complexidade do desenvolvimento de estratégias desse tipo.

Estratégia CImpulse 2.0 

A melhor maneira de avaliar as novas alterações é reescrever a conhecida estratégia de negociação CImpulse, que foi apresentada na quinta parte do artigo. Ela consiste em posicionar uma ordem pendente de stop em cada barra e a certa distância da média móvel. A distância é expressa como uma porcentagem. Para compra, é colocada uma ordem BuyStop cujo nível de abertura é superior à média móvel em N por cento. Para venda, ao contrário, é colocada uma ordem SellStop cujo nível é inferior à média móvel em N por cento. Fecharemos a posição, quando o preço de fechamento se torne inferior (para compra) ou superior (para venda) à média.

Em geral, esta é a mesma estratégia apresentada anteriormente, mas numa nova versão, reescrita completamente. Com base no seu exemplo, podem ser avaliadas as alterações feitas no CStrategy e entender como usar os novos recursos na prática. Mas, primeiro, olharemos para o código do expert advisor da versão anterior. Ele será apresentado na íntegra, para, mais tarde, comparar ambas as variantes de sintaxe:

//+------------------------------------------------------------------+
//|                                                      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;
//+------------------------------------------------------------------+
//| Define a ação que deve ser realizada pela ordem pendente         |
//|                                                                  |
//+------------------------------------------------------------------+
enum ENUM_ORDER_TASK
{
   ORDER_TASK_DELETE,   // Excluir ordem pendente
   ORDER_TASK_MODIFY    // Modificar ordem pendente
};
//+------------------------------------------------------------------+
//| Estratégia CImpulse                                              |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // Porcentagem do nível da ordem pendente
   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;  // Trabalho com a média móvel ocorre através da classe especialmente escrita para ela
};
//+------------------------------------------------------------------+
//| Trabalho com ordens pendentes BuyStop para abertura da           |
//| posição longa                                                    |
//+------------------------------------------------------------------+
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))                    // Preço de execução da ordem deve ser superior 
à média móvel
      task = ORDER_TASK_DELETE;
   else
      task = ORDER_TASK_MODIFY;
   for(int i = PendingOrders.Total()-1; i >= 0; i--) // Ocorre a busca exaustiva de ordens pendentes na seção InitBuy, o que não é muito bom
   {
      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) // Trabalho com ordens pendentes é realizado através do 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);
}
//+------------------------------------------------------------------+
//| Trabalho com ordens pendentes SellStop para abertura de uma      |
//| posição curta                                                    |
//+------------------------------------------------------------------+
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))                    // Preço de execução da ordem deve ser superior à média móvel
      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);
}
//+-----------------------------------------------------------------------+
//| Acompanhamento da posição longa de acordo com a média móvel Moving    |
//+-----------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+--------------------------------------------------------------------------+
//| Acompanhamento da posição curta de acordo com a média móvel Moving       |
//+--------------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| filtra os eventos de entrada. Se o evento enviado                |
//| não é processado pela estratégia, retorna false, se é processado |
//| retorna true.                                                    |
//+------------------------------------------------------------------+
bool CImpulse::IsTrackEvents(const MarketEvent &event)
  {
//Processamos apenas a abertura da nova barra no instrumento e timeframe de trabalho
   if(event.type != MARKET_EVENT_BAR_OPEN)return false;
   if(event.period != Timeframe())return false;
   if(event.symbol != ExpertSymbol())return false;
   return true;
  }
//+------------------------------------------------------------------+
//| Respondemos à mudança do símbolo                                 |
//+------------------------------------------------------------------+
void CImpulse::OnSymbolChanged(string new_symbol)
  {
   Moving.Symbol(new_symbol);
  }
//+------------------------------------------------------------------+
//| Reagimos à mudança do timeframe                                  |
//+------------------------------------------------------------------+
void CImpulse::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf)
  {
   Moving.Timeframe(new_tf);
  }
//+------------------------------------------------------------------+
//| Retorna o percentual do nível de rompimento                      |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| Define a porcentagem do nível de rompimento                      |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

Os momentos mais problemáticos na implementação desta estratégia são mostrados em amarelo.

Em primeiro lugar, o trabalho com o indicador é realizado através da classe CIndMovingAverage, escrita anteriormente. Já dissemos que esta abordagem é irracional. Os indicadores são demasiados para escrever uma classe para cada um deles.

Em segundo lugar, o trabalho com ordens pendentes ocorre por meio de uma busca exaustiva de ordens pendentes nos blocos BuyInit/SellInit. Numa estratégia tão simples como CImpulse, isso não causa dificuldades, no entanto, no caso de um acompanhamento mais complexo de ordens, podem surgir dificuldades. É melhor dividir a colocação de ordens pendentes e seu acompanhamento em métodos separados, como é feito na nova versão do CStrategy.

Quando se observa atentamente o código da estratégia CImpulse, torna-se claro que ele assume parte da funcionalidade que deve fornecer o CStrategy. CStrategy deve definir um sistema de estados para gerenciar ordens pendentes, mas ele não faz isso, é a CImpulse em si que implementa o sistema.

Reescrevemos o código levando em conta as novas capacidades do CStrategy:

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

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

//+------------------------------------------------------------------+
//| Estratégia CImpulse                                              |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // Porcentagem do nível da ordem pendente
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 a média móvel                                      |
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
//| Colocação de ordens pendentes BuyStop                            |
//+------------------------------------------------------------------+
void CImpulse::InitBuy(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // Criamos ordens pendentes apenas para abertura da nova barra
   if(PositionsTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)   // Não deve haver nenhuma ordem longa aberta
      return;
   if(OrdersTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)      // Não deve haver nenhuma ordem pendente para compra
      return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // Calculamos o nível da nova ordem pendente
   if(target < UnMA[0])                                                       // Preço de execução da ordem deve ser superior à média móvel
      return;
   Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);       // Definimos a nova ordem BuyStop
}
//+------------------------------------------------------------------+
//| Trabalho com ordens pendentes BuyStop para abertura da           |
//| posição longa                                                    |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingBuy(const MarketEvent &event,CPendingOrder *order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // Calculamos o novo nível da ordem pendente
   if(UnMA[0] > target)                                                       // Se o novo nível é inferior à média atual
      order.Delete();                                                         // excluimo-lo
   else                                                                       // Caso contrário, modificamos para o novo preço
      order.Modify(target);
}
//+------------------------------------------------------------------+
//| Trabalho com ordens pendentes SellStop para abertura de uma      |
//| posição curta                                                    |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingSell(const MarketEvent &event,CPendingOrder* order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() - WS.Ask()*(m_percent/100.0);                     // Calculamos o novo nível da ordem pendente
   if(UnMA[0] < target)                                                       // Se o novo nível é superior à média atual
      order.Delete();                                                         // excluimo-lo
   else                                                                       // Caso contrário, modificamos para o novo preço
      order.Modify(target);
}
//+------------------------------------------------------------------+
//| Colocação da ordem pendente SellStop                             |
//+------------------------------------------------------------------+
void CImpulse::InitSell(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // Criamos ordens pendentes apenas para abertura da nova barra
   if(PositionsTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)  // Não deve haver nenhuma ordem curta aberta
      return;
   if(OrdersTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)     // Não deve haver nenhuma ordem pendente para venda
      return;
   double target = WS.Bid() - WS.Bid()*(m_percent/100.0);                     // Calculamos o nível da nova ordem pendente
   if(target > UnMA[0])                                                       // Preço de execução da ordem dever ser inferior à média móvel
      return;  
   Trade.SellStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);      // Definimos uma nova ordem BuyStop
}
//+--------------------------------------------------------------------------+
//| Acompanhamento da posição longa de acordo com a média móvel 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();
}
//+--------------------------------------------------------------------------+
//| Acompanhamento da posição curta de acordo com a média móvel 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();
}
//+------------------------------------------------------------------+
//| Retorna o percentual do nível de rompimento                      |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| Define a porcentagem do nível de rompimento                      |
//+------------------------------------------------------------------+  
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 OnTick()
{
   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);
}

A captura de tela abaixo mostra um trecho do teste da estratégia CIpmulse 2.0, no testador de estratégias. Nela, podem ser vistas as ordens pendentes colocadas e o trabalho com elas:


Fig. 1. Trabalho com ordens pendentes no teste da estratégia Impulse 2.0

Apesar de a lógica permanecer a mesma, os códigos antigo e novo são diferentes. Listamos as diferenças entre a nova versão e a anterior:

  • O acesso ao instrumento atual é realizado através da instância CSymbol WS (work symbol). Nesta classe são agora delegadas todas as propriedades do instrumento.
  • Os dados do expert advisor são inicializados no método OnInit. Quando estava sendo escrita a quinta parte do artigo, ele ainda não existia.
  • Em vez do indicador CIndMovingAverage, agora é usado o indicador universal CUnIndicator.
  • O posicionamento de ordens pendentes é realizado em InitBuy/InitSell, enquanto que sua modificação e remoção, em SupportPendingBuy/SupportPendingSell.
  • A pesquisa detalhada de ordens pendentes não é mais usada. Esta função é delegada no CStrategy.
  • Em vez da estrutura position, para o cálculo do número de posições atuais e ordens pendentes, usam-se os métodos PositionsTotal e OrdersTotal.

A listagem apresentada contém o código em si da estratégia e as funções básicas do expert advisor. Ou seja, este exemplo é apresentado na forma de um único arquivo mq5. Isso é devido ao fato de que a estrutura do projeto foi substancialmente rearranjada. Isto é o que vamos discutir a seguir.

Nova estrutura de projeto do CStrategy

Em versões anteriores, o motor de negociação do CStrategy estava em várias subpastas MQL5. Assim, por exemplo, ele mesmo e seus arquivos de suporte estavam localizados na pasta MQL5\Include\Strategy. Os códigos-fonte responsáveis pela implementação do painel de negociação do motor estavam em MQL5\Include\Panel. O código do expert podia estar em MQL5\Include\Strategy\Samples, enquanto o arquivo de inicialização do expert advisor mq5, na pasta MQL5\Experts\Article08. E tudo isso sem ter em conta que os vários componentes auxiliares como Dictionary.mqh ou XmlBase.mqh também estavam espalhados em muitos diretórios da pasta Include.

É óbvio que as relações formadas no projeto eram muito complexas, e a localização de arquivos e diretórios era muitas vezes duplicada. Isso dificultava a potencial aproximação do motor de negociação do CStrategy. Os usuários, especialmente os novatos, podem facilmente ficar confusos e não entender em última análise como é executado o processo de compilação. Portanto, a partir da versão atual do motor de negociação, os arquivos são colocados de forma diferente.

Agora, todo o projeto está contido no diretório MQL5\Experts\UnExpert. Ele contém a pasta Strategy e os arquivos das estratégias com extensão .mq5. Atualmente, a estratégia de negociação final consiste num único arquivo mq5, que contém as funções-manipuladores de eventos padrão (do tipo OnInit e OnTick), bem como a classe da estratégia baseada no CStrategy.

Todos os arquivos auxiliares também são colocados na pasta MQL5\Experts\UnExpert\Strategy. Isso se aplica a arquivos de trabalho com XML e arquivos de infra-estrutura, como o Dictionary.mqh. Para compilar o exemplo, basta abrir o arquivo, por exemplo, "MQL5\Experts\UnExpert\Impulse 2.0.mqh", e clicar em "Compilar".

Na compilação apresentada nesta parte do artigo, apenas são apresentadas as estratégias Impulse 1.0 e Impulse 2.0. Trata-se da mesma estratégia, mas escrita nos estilos antigo e novo do CStrategy. Isso foi feito de propósito, para poder comparar as duas abordagens em ação e entender as diferenças descritas neste artigo. No build atual, não estão disponíveis outros exemplos de estratégias que tenham sido incluídas nas versões anteriores do CStrategy. Isso é devido ao fato de estarem baseadas na sintaxe antiga, por isso não podem ser apresentadas como exemplos. Talvez em futuras versões eles vão aparecer de novo, mas com uma sintaxe modificada.

Fim do artigo

Examinamos os novos recursos do CStrategy. Eles são a classe CUnIndicator, que implementa uma interface universal POO, para trabalhar com indicadores arbitrários e de sistema em MQL5, bem como um sistema de acompanhamento de ordens pendentes baseado nos métodos SupportPendingBuy e SupportPendingSell. Todos estes elementos se combinam para proporcionar um efeito sinérgico poderoso ao escrever um Expert Advisor. O usuário não tem que pensar em operações de baixo nível. Quase todo o ambiente de negociação está disponível para ele através de umas classes intuitivas e concisas, enquanto a própria lógica comercial redefine os respectivos métodos predefinidos, de maneira simples. 

Neste momento, o projeto está localizado num só lugar por causa das restrições do diretório MQL5\Experts\UnExpert. Agora não há necessidade de colocar os arquivos em pastas diferentes do diretório MQL5. Esta inovação também deve incentivar os usuários a mudar para o CStrategy ou, pelo menos, para o estudo fascinante de suas possibilidades.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2653

Arquivos anexados |
UnExpert.zip (680.21 KB)
Redes Neurais Profundas (Parte IV). Criação, treinamento e teste de um modelo de rede neural Redes Neurais Profundas (Parte IV). Criação, treinamento e teste de um modelo de rede neural
Este artigo considera novas capacidades do pacote darch (v.0.12.0). Contém uma descrição do treinamento de redes neurais profundas com diferentes tipos de dados, diferentes estruturas e sequências de treinamento. Os resultados do treino estão incluídos.
Redes Neurais Profundas (Parte III). Seleção da amostra e redução de dimensionalidade Redes Neurais Profundas (Parte III). Seleção da amostra e redução de dimensionalidade
Este artigo é uma continuação da série de artigos sobre redes neurais profundas. Aqui, nós vamos considerar a seleção de amostras (remoção de ruído), reduzindo a dimensionalidade dos dados de entrada e dividindo o conjunto de dados nos conjuntos de train/val/test durante a preparação dos dados para treinar a rede neural.
Escrevendo um livro de ofertas de scalping com base na biblioteca gráfica CGraphic Escrevendo um livro de ofertas de scalping com base na biblioteca gráfica CGraphic
O artigo apresenta a criação de um livro de ofertas de scalping com funcionalidade básica. Desenvolve-e um gráfico de ticks com base na biblioteca gráfica CGraphic e se integra na tabela de pedidos. Pode-se criar um poderoso auxiliar para negociação no curto prazo utilizando o livro de ofertas descrito.
Usando armazenamento em nuvem para intercâmbio de dados entre os terminais Usando armazenamento em nuvem para intercâmbio de dados entre os terminais
As tecnologias baseadas em nuvem estão se tornando mais populares. À nossa disposição temos serviços de armazenamento pagos ou gratuitos. Mas será que é possível usá-los na negociação? Este artigo apresenta uma tecnologia para intercâmbio de dados entre terminais usando serviços de armazenamento em nuvem.