下载MetaTrader 5

通用EA交易: CUnIndicator 和挂单的使用(第9部分)

23 十月 2017, 10:25
Vasiliy Sokolov
1
5 828

目录

简介

通用EA交易的基础源代码开发仍然继续,整合到 CStrategy 交易引擎的大部分方法都证明了它们在实际使用中的高效、方便和简单,但是,因为实际使用的原因,我们需要回顾 EA 运行都某些方面,

其中一个方面就是对指标的操作。在系列的第三篇文章中,我们提出了一个用于操作指标的经典面向对象方法,背后的思路是把每个指标都当成一个面向对象的类,使用它们自己的方法来设置和获得某些属性。然而,在实际使用中,很难为每个指标实现一个单独的封装类。本文探讨了使用OOP(面向对象编程)风格操作指标的新方法,不再需要实现单独模块类了。

本文中探讨的第二个变化是关于引入了一个全面管理挂单的过程,如果需要在之前策略代码中直接管理挂单,一部分这样的函数现在指派到 CStrategy 交易引擎了。最终的策略类现在可以重载 SupportBuyPending 和 SupportSellPending 方法并开始管理挂单,和管理活动仓位类似。

在早前版本的 CStrategy 中访问指标

为了理解当前的任务,让我们参考在系列文章第三篇中谈到的操作指标的方案,它提出的方法是通过一个封装类来操作任何指标。例如,提供的例子中使用了 CIndMovingAverage 封装类用于操作 iMA 指标。CIndMovingAverage 类本身包含了设置或者返回指标特定属性的方法,以及调用 iMA 系统函数的 Init 方法。尽管指标类的结构比较简单,它的代码非常长,用户需要自己实现它们。参见这个类的源代码以评估它所需的工作量:

//+------------------------------------------------------------------+
//|                                                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>
//+------------------------------------------------------------------+
//| 定义                                                              |
//+------------------------------------------------------------------+
class CIndMovingAverage
  {
private:
   int               m_ma_handle;         // 指标句柄
   ENUM_TIMEFRAMES   m_timeframe;         // 时段
   int               m_ma_period;         // 周期数
   int               m_ma_shift;          // 偏移
   string            m_symbol;            // 交易品种
   ENUM_MA_METHOD    m_ma_method;         // 移动平均方法
   uint              m_applied_price;     // 指标句柄,您会用它计算移动平均数值,
                                          // 或者是 ENUM_APPLIED_PRICE 类型的价格值
   CLog*             m_log;               // Logging
   void              Init(void);
public:
                     CIndMovingAverage(void);

/*参数*/
   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);

/*输出值*/
   double            OutValue(int index);
  };
//+------------------------------------------------------------------+
//| 默认构造函数                                                       |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+
//| 初始化                                                            |
//+------------------------------------------------------------------+
CIndMovingAverage::Init(void)
  {
   if(m_ma_handle!=INVALID_HANDLE)
     {
      bool res=IndicatorRelease(m_ma_handle);
      if(!res)
        {
         string text="初始化 iMA 指标失败,错误 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="(周期数:"+(string)m_ma_period+", 偏移: "+(string)m_ma_shift+
                    ", MA 方法:"+EnumToString(m_ma_method)+")";
      string text="创建 iMA 指标失败"+params+". Error ID: "+(string)GetLastError();
      CMessage *msg=new CMessage(MESSAGE_ERROR,__FUNCTION__,text);
      m_log.AddMessage(msg);
     }
  }
//+------------------------------------------------------------------+
//| 设置时段                                                          |
//+------------------------------------------------------------------+
void CIndMovingAverage::Timeframe(ENUM_TIMEFRAMES tf)
  {
   m_timeframe=tf;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 返回当前时段                                                       |
//+------------------------------------------------------------------+
ENUM_TIMEFRAMES CIndMovingAverage::Timeframe(void)
  {
   return m_timeframe;
  }
//+------------------------------------------------------------------+
//| 设置移动平均的平均周期数                                             |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaPeriod(int ma_period)
  {
   m_ma_period=ma_period;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 返回移动平均的当前平均周期数                                          |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaPeriod(void)
  {
   return m_ma_period;
  }
//+------------------------------------------------------------------+
//| 设置移动平均类型                                                    |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaMethod(ENUM_MA_METHOD method)
  {
   m_ma_method=method;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 返回移动平均类型                                                    |
//+------------------------------------------------------------------+
ENUM_MA_METHOD CIndMovingAverage::MaMethod(void)
  {
   return m_ma_method;
  }
//+------------------------------------------------------------------+
//| 返回移动平均偏移                                                    |
//+------------------------------------------------------------------+
int CIndMovingAverage::MaShift(void)
  {
   return m_ma_shift;
  }
//+------------------------------------------------------------------+
//| 设置移动平局你偏移                                                   |
//+------------------------------------------------------------------+
void CIndMovingAverage::MaShift(int ma_shift)
  {
   m_ma_shift=ma_shift;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 设置用于计算 MA 的价格类型                                           |
//+------------------------------------------------------------------+
void CIndMovingAverage::AppliedPrice(int price)
  {
   m_applied_price = price;
   if(m_ma_handle != INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 返回用于 MA 计算的价格类型                                           |
//+------------------------------------------------------------------+
uint CIndMovingAverage::AppliedPrice(void)
  {
   return m_applied_price;
  }
//+------------------------------------------------------------------+
//| 设置用于计算指标的交易品种                                            |
//+------------------------------------------------------------------+
void CIndMovingAverage::Symbol(string symbol)
  {
   m_symbol=symbol;
   if(m_ma_handle!=INVALID_HANDLE)
      Init();
  }
//+------------------------------------------------------------------+
//| 返回用于计算指标的交易品种                                            |
//+------------------------------------------------------------------+
string CIndMovingAverage::Symbol(void)
  {
   return m_symbol;
  }
//+------------------------------------------------------------------+
//| 返回索引为 'index' 的 MA 数值                                       |
//+------------------------------------------------------------------+
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;
  }

很明显,代码数量相当惊人,并且这只是最简单的指标之一的例子。这种方法很复杂,因为有几百个为 MetaTrader 开发的各种指标,包括标准的和自定义的。它们中的每一个都有自己相对独特的属性和参数集合,根据所提出的方法,为每个这样的指标都需要创建独立的封装类。 

CStrategy 允许直接访问指标,不需要包含任何其他类,所以,在实际使用中我经常在交易策略的代码中直接调用特定的系统函数,所以,使用标准方法来调用指标,而不是花时间来写这样的类,要简单多了。

IndicatorCreate 函数 — 通用指标的基础

经常会出现,在实际使用 CStrategy 一些时间后就找到了方案,很明显,指标访问机制应该有下面的属性:

  • 灵活. 访问任何指标必须基于一种通用的访问过程,而不是从各方面访问封装类。
  • 方便和简单. 访问指标值应该方便和简单,它必须依赖于指标类型。

实际的应用程序显示,最简单、并且更重要的通用访问指标的方法通常是通过 iCustomIndicatorCreate 函数实现的。

这两个函数允许创建标准的和自定义的指标。iCustom 函数需要在程序编译的时候就指定指标的实际参数,而 IndicatorCreate 函数的处理是不同的。它使用 MqlParams 结构的数组,第二个函数的格式使得可以为一个任意的指标创建一个通用的访问过程。在这种情况下,不需要预先知道指标的参数,这使访问过程真正通用。

让我们探讨一个使用 IndicatorCreate 的实际例子,我们将使用它来创建一个移动平均指标的句柄,它将是与使用 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"

//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // 使用 iMA 取得指标句柄
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // 使用 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("指标句柄相同");
   else
      printf("指标句柄不同");
}

上面所示的脚本通过两个不同的接口访问相同的指标: iMA 函数和 IndicatorCreate 函数。这两个函数都返回相同的句柄,这是很容易验证的: 在运行之后,脚本会输出 "指标句柄相同"。然而,通过 IndicatorCreate 访问指标需要对 MqlParams 数组进行复杂的配置,MqlParam 数组的每个元素必须设置两个属性: 变量的类型和变量的值。IndicatorCreate 函数很少使用主要就是因为这个问题。但是,这种调用接口绝对可以访问任何 MQL 指标。所以,我们将会使用它。

CUnIndicator — CStrategy 中的通用指标

面向对象的编程帮助用户隐藏了大部分 MqlParams 数组配置的工作,提供了方便的接口来设置任意参数。让我们创建 CUnIndicator — 一个使用 IndicatorCreate 函数的封装类。它可以用于为指标按照顺序设置任何数量的参数,它不需要设置特定参数的类型,这要感谢模板,传入的类型将会被自动侦测。这个类还将有方便的索引,以方括号'[]'的形式使用,它将可以同时设置指标值的索引和取得指标值。

CUnIndicator 的操作会缩减到下面的步骤:

  • 使用 SetParameter 方法设置所需的参数
  • 使用 Create 方法直接创建指标
  • 通过 SetBuffer 设置所需的缓冲区 (可选)
  • 使用 [] 索引来访问位于任意索引 i 的指标值
  • 使用 IndicatorRelease 方法删除指标 (可选).

这里是使用 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;
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // 使用 iMA 取得指标句柄
   int h_ima = iMA(Symbol(), Period(), 13, 0, MODE_SMA, PRICE_CLOSE); 
   // 使用 CUnIndicator 取得指标句柄
   UnMA.SetParameter(13);
   UnMA.SetParameter(0);
   UnMA.SetParameter(MODE_SMA);
   int handle = UnMA.Create(Symbol(), Period(), "Examples\\Custom Moving Average");
}

'handle' 变量现在包含了所创建指标的句柄, UnMA 对象可以操作指标值。例如,为了取得前面柱的指标值,只需要下面的代码行:

double value = UnMA[1];

让我们探讨一个更加复杂的例子,创建一个包含多个缓冲区的指标 — 例如,标准的 MACD,每一行代码都包括详细的注释:

//+------------------------------------------------------------------+
//|                                                        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;
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   UnMACD.SetParameter(FastEMA);                            // 设置快速移动平均值
   UnMACD.SetParameter(SlowEMA);                            // 设置慢速移动平均值
   UnMACD.SetParameter(SignalSMA);                          // 设置信号线周期数
   int handle = UnMACD.Create(Symbol(), Period(),           // 创建指标,指定交易品种和时段
                "Examples\\MACD", PRICE_CLOSE);
   UnMACD.SetBuffer(MAIN_LINE);                             // 设置默认缓冲区 - MACD 数值
   double macd = UnMACD[1];                                 // 取得前一个柱上的 MACD 值
   UnMACD.SetBuffer(SIGNAL_LINE);                           // 设置信号线为默认缓冲区
   double signal = UnMACD[1];                               // 取得前一个柱上信号线的值
   datetime time_span = TimeCurrent() - PeriodSeconds();    // 计算前一个柱的开启时间
   double signal_by_time = UnMACD[time_span];               // 取得该时间的信号线数值
   printf("MACD: " + DoubleToString(macd, Digits()) +       // 输出前一个柱上 MACD 和信号线的值
         "; Signal: " + DoubleToString(signal, Digits()));
   if(signal == signal_by_time)                             // 根据时间和索引的访问应当能匹配上
      printf("从索引和时间上取得的数值相等 ");
}
//+------------------------------------------------------------------+


在这个例子中,最有趣的部分就是使用 SetBuffer 方法切换内部指标缓冲区,这样,由 UnMACD[1] 返回的数值将依赖于当前设置的是哪个缓冲区。第一次,UnMACD[1] 返回前一个柱的 MACD 值,但是,如果设置 SIGNAL_LINE 为默认缓冲区,UnMACD[1] 就返回信号线的值。

指标值可以根据索引或者根据时间来访问,在这个例子中,计算了 'time_span' , 它等于前一个柱的开启时间。如果在 UnMACD 中指定了这个时间而不是索引,返回的数值将和 UnMACD[1] 相同。

CUnIndicator 的内部结构

是时候分析 CUnIndicator 的内部结构了,它的完整源代码如下:

//+------------------------------------------------------------------+
//|                                                   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"
//+------------------------------------------------------------------+
//| 指标基类                                                           |
//+------------------------------------------------------------------+
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;
//+------------------------------------------------------------------+
//| 不指定名称的初始化                                                   |
//+------------------------------------------------------------------+
CUnIndicator::CUnIndicator(void) : m_params_count(0),
                                   m_handle(INVALID_HANDLE),
                                   m_current_buffer(0),
                                   m_invalid_handle(false)
{
   Log = CLog::GetLog(); 
}

//+------------------------------------------------------------------+
//| 指标终止化                                                         |
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
//| 返回指标句柄                                                       |
//+------------------------------------------------------------------+
int CUnIndicator::GetHandle(void)
{
   return m_handle;
}
//+------------------------------------------------------------------+
//| 设置当前指标缓冲区                                                   |
//+------------------------------------------------------------------+
void CUnIndicator::SetBuffer(int index)
{
   m_current_buffer = index;
}
//+------------------------------------------------------------------+
//| 初始化指标 (创建它的句柄). 返回句柄                                    |
//| 如果成功的话,否则返回 INVALID_HANDLE                                |
//| 加入指标创建失败                                                    |
//+------------------------------------------------------------------+
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 + "' 没有被创建. 检查它的参数. 最后的错误:" + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      m_invalid_handle = true;
   }
   return m_handle;
}
//+------------------------------------------------------------------+
//| 初始化指标 (创建它的句柄). 返回句柄                                    |
//| 如果成功的话,否则返回 INVALID_HANDLE                                |
//| 加入指标创建失败                                                    |
//+------------------------------------------------------------------+
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 + "' 没有被创建. 指标类型不能是 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 + "' 没有被创建. 检查它的参数. 最后的错误:" + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      m_invalid_handle = true;
   }
   return m_handle;
}
//+------------------------------------------------------------------+
//| 初始化指标 (创建它的句柄). 返回句柄                                    |
//| 如果成功的话,否则返回 INVALID_HANDLE                                 |
//| 加入指标创建失败                                                     |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol,ENUM_TIMEFRAMES period,ENUM_INDICATOR ind_type,int app_price)
{
   SetParameter(app_price);
   return Create(symbol, period, ind_type);
}
//+------------------------------------------------------------------+
//| 把指标名称放到 m_params[] 数组的索引0处                               |
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
//| 初始化指标 (创建它的句柄). 返回句柄                                    |
//| 如果成功的话,否则返回 INVALID_HANDLE                                |
//| 加入指标创建失败                                                    |
//+------------------------------------------------------------------+
int CUnIndicator::Create(string symbol, ENUM_TIMEFRAMES period, string name, int app_price)
{
   SetParameter(app_price);
   return Create(symbol, period, name);
}
//+------------------------------------------------------------------+
//| 根据已有的指标句柄                                                   |
//| 初始化指标                                                          |
//+------------------------------------------------------------------+
void CUnIndicator::InitByHandle(int handle)
{
   this.IndicatorRelease();
   m_handle = handle;
}
//+------------------------------------------------------------------+
//| 根据 '索引' 返回指标值                                              |
//+------------------------------------------------------------------+
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 = "复制指标缓冲区失败. 最后的错误: " + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      return EMPTY_VALUE;
   }
   return values[0];
}
//+------------------------------------------------------------------+
//| 根据 '时间' 返回指标值                                              |
//+------------------------------------------------------------------+
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 = "复制指标缓冲区失败. 最后的错误: " + (string)GetLastError();
      CMessage* msg = new CMessage(MESSAGE_ERROR, __FUNCTION__, text);
      Log.AddMessage(msg);
      return EMPTY_VALUE;
   }
   return values[0];
}

从上面列出的代码中可以看出,SetParameter 是一个模板方法,它使用了通用的 T 参数作为输入参数,依赖 ENUM_DATATYPE 参数所选择的对应类型。这个参数是为 MqlParam 结构设置的。从速度的角度考虑,多次检查字符串不是最优的,但是这也不会影响到效率,因为 SetParameter 函数只会被调用一次 — 在 EA 交易初始化的时候。

这个类还提供了 Create 方法的多个变体,这就可以同时创建自定义指标 (指定指标的字符串名称) 和标准指标, 类型可以通过 INDICATOR_TYPE 设置。例如,这就是如何以自定义指标创建移动平均指标:

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

在此,UnMA 是 CUnIndicator 的一个实例。以标准指标创建相同指标有一点不同:

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

CUnIndicator 类还包含了 InitByHandle 方法,让我们详细研究一下。我们知道,很多指标可以不仅根据交易品种的价格进行计算,还可以根据其他指标的数据进行计算,感谢这一点,这就可以创建一个指标链,根据前面指标的输出值来计算它们的价格了。假设我们需要根据移动平均计算随机震荡指标的值,要做到这一点,我们需要初始化两个指标: 一个用于计算移动平均,而另一个是用于随机震荡指标。可以像下面这样做:

//+------------------------------------------------------------------+
//|                                                        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;
//+------------------------------------------------------------------+
//| 脚本程序起始函数                                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   // 设置参数并创建 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");
   // 设置参数并创建随机震荡指标,
   // 根据来自 SMA 的数据
   UnStoch.SetParameter(StochK);
   UnStoch.SetParameter(StochD);
   UnStoch.SetParameter(StochSmoth);
   UnStoch.InitByHandle(sma_h);
   double stoch_on_sma = UnStoch[1];
}
//+------------------------------------------------------------------+

上面的代码显示,由 Create 方法创建的指标句柄可以保存并用于创建随机震荡指标。如果是使用自定义指标,数据源就不需要指定,但是,这样的数据源对于系统指标必须指定。这可以通过两种方法实现: 通过 SetParameter 方法:

UnMA.SetParameter(PRICE_CLOSE);

以及使用 Create 方法的重载版本:

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

我们晚些时候将会创建一个演示交易系统,通过 CUnIndicator 类访问指标值。

使用挂单来提高工作水平

之前有一篇关于 CStrategy 的文章介绍了 CPendingOrders 面向对象类,表示 CStrategy 中的一个挂单。CPendingOrders 是一个接口类,它除了用于保存订单单号的栏位之外没有内部成员变量。它的所有用于取得对应属性的方法都是通过调用三个主系统函数 — OrderGetInterer, OrderGetDouble 和 OrderGetString。这样的组织确保了数据表示的完整性。CPendingOrders 的一个实例对应着 MetaTrader 5 中的每个挂单, 单号等于真实订单号。如果因为某些原因挂单被取消 (通过EA交易或者通过用户), CStrategy 交易引擎就会从挂单列表中删除对应的 CPendingOrders 实例。CPendingOrders 列表保存为一个特定的 COrdersEnvironment 类,每个策略都有它自己的 COrdersEnvironment 实例, 叫作 PendingOrders。策略可以直接访问这个对象,并从对象中根据索引选择所需的订单。

如果策略需要建立一个挂单而不是市场订单,只要通过 CTrade 模块简单发送对应的交易请求。通过这种方式,设置挂单和设置市场订单没有区别。但是随后就会有所差别,这在 CStrategy 中没有考虑到。CStrategy 是这样处理的,每个市场仓位按顺序传入到处理函数中,这样,POSITION_TYPE_BUY 类型的仓位是由 SupportBuy 方法处理的, 而 POSITION_TYPE_SELL 类型的仓位是由 SupportSell 方法处理。而对于挂单,一切都不同了。每个这样的订单都放在 PendingOrders 集合中,由 EA 交易访问,但是这样的订单没有它们自己的处理函数。我们假定,挂单将会在其他地方进行处理,例如,在 OnEvent 中, BuySupport/SellSupport 或者甚至在 BuyInit/SellInit 中。但是,在这种情况下,如果没有开启的仓位,随后就没有对 BuySupport/SellSupport 的调用,结果是,可靠的对挂单的处理只能放在 OnEvent 中。但是在这个方法中处理又与一般操作顺序有冲突。会出现一部分仓位按顺序在队列中由 CStrategy 处理,而另一部分仓位(挂单)是以旧的方式处理的,在单个的 OnEvent 模块中。

正因为这一点,新版本的 CStrategy 引入了两个另外的方法, SupportPendingBuySupportPendingSell:

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

它们的格式和 SupportBuy 与 SupportSell 方法类似: MarketEvent 事件作为第一个参数传入,第二个参数是由 CStrategy 选择的当前订单的指针。订单本身是由 CStrategy 引擎选择的, 使用的是穷举方法。搜索是由 CallSupport 方法进行的, 从订单列表的最后搜索到开始:

//+------------------------------------------------------------------+
//| 调用仓位管理逻辑,交易状态                                            |
//| 不等于 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);
   }
}

这样,挂单的处理函数就和市场仓位处理函数以同样方法调用: 为每个买入挂单调用 SupportPendingBuy 方法, 而为每个卖出挂单调用 SupportPendingSell 方法。

策略的完整循环,包括了挂单处理部分,就变得比只处理市场订单的策略循环要长了。在第二种情况下,每个方向上顺序有两个处理函数:

  1. 在 InitBuy 中建立买入仓位 / 在 InitSell 中建立卖出仓位;
  2. 在 SupportBuy 中管理买入仓位 / 在 SupportSell 中管理卖出仓位.

而当使用有挂单的策略时,就需要在每个方向上执行三个处理函数了:

  1. 在 InitBuy 中设置买入挂单/ 在 InitSell 中设置卖出挂单;
  2. 在 SupportPendingBuy 中管理买入挂单直到其触发或者取消 / 在 SupportPendingSell 管理卖出挂单直到其触发或者取消;
  3. 在 SupportBuy 中管理买入仓位 / 在 SupportSell 中管理卖出仓位.

实际上,管理挂单总是交易策略逻辑中独立的部分,所以,单独管理挂单和市场仓位使得可以减少开发这种策略时的复杂性。

CImpulse 2.0 策略 

处理新变化的最好方法是重写在 CImpulse 交易策略中熟悉的例子,它在文章的第五部分中。它的本质就是在每个柱上距离移动平均一定的距离设置止损挂单,距离以百分比表示。对于买入交易,设置止损买入挂单,而建仓水平高于移动平均百分之N,对于卖出交易,就是相反的: 设置止损卖出挂单,建仓水平低于移动平均百分之N。仓位将会在收盘价低于 (对于买入交易) 或者高于 (对于卖出交易) 移动平均的时候关闭。

总体说来,这与之前介绍的策略是相同的, 只是属于修改版。它可以用作例子,来评估 CStrategy 中所做的修改,并且看看新功能是如何在实际中使用的。但是,首先我们需要回到之前版本的EA代码中,这里会提供完整代码,所以晚些时候我们可以比较版本之间的语句:

//+------------------------------------------------------------------+
//|                                                      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;
//+------------------------------------------------------------------+
//| 定义需要在挂单中进行的                                               |
//| 操作。                                                            |
//+------------------------------------------------------------------+
enum ENUM_ORDER_TASK
{
   ORDER_TASK_DELETE,   // 删除挂单
   ORDER_TASK_MODIFY    // 修改挂单
};
//+------------------------------------------------------------------+
//| CImpulse 策略                                                     |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // 用于挂单水平的百分比数值
   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;  // 操作移动平均是在特别写的一个类中进行的
};
//+------------------------------------------------------------------+
//| 操作止损买入挂单用于建立买入                                          |
//| 仓位                                                              |
//+------------------------------------------------------------------+
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))                    // 订单触发价格必须高于移动平均
      task = ORDER_TASK_DELETE;
   else
      task = ORDER_TASK_MODIFY;
   for(int i = PendingOrders.Total()-1; i >= 0; i--) // 在 InitBuy 部分中迭代所有挂单,这不是很好
   {
      CPendingOrder* Order = PendingOrders.GetOrder(i);
      if(Order == NULL || !Order.IsMain(ExpertSymbol(), ExpertMagic()))
         continue;
      if(Order.Type() == ORDER_TYPE_BUY_STOP)
      {
         if(task == ORDER_TASK_MODIFY) // 通过系统状态操作挂单
         {
            buy_stop_total++;
            Order.Modify(target);
         }
         else
            Order.Delete();
      }
   }
   if(buy_stop_total == 0 && task == ORDER_TASK_MODIFY)
      Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);
}
//+------------------------------------------------------------------+
//|  操作止损卖出挂单来建立卖出                                           |
//| 仓位                                                              |
//+------------------------------------------------------------------+
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))                    // 订单的触发价格必须低于移动平均
      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);
}
//+------------------------------------------------------------------+
//| 根据移动平均管理买入仓位                                              |
//+------------------------------------------------------------------+
void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Bid() < Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| 根据移动平均管理卖出仓位                                              |
//+------------------------------------------------------------------+
void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos)
{
   if(!IsTrackEvents(event))return;
   if(Ask() > Moving.OutValue(0))
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| 过滤进入的事件. 如果传入的事件不是                                     |
//| 由策略处理,就返回 false; 如果是由策略处理                             |
//| 就返回 true.                                                      |
//+------------------------------------------------------------------+
bool CImpulse::IsTrackEvents(const MarketEvent &event)
  {
//我们只在当前交易品种和时段的新柱开启时进行处理
   if(event.type != MARKET_EVENT_BAR_OPEN)return false;
   if(event.period != Timeframe())return false;
   if(event.symbol != ExpertSymbol())return false;
   return true;
  }
//+------------------------------------------------------------------+
//| 回应交易品种的变化                                                  |
//+------------------------------------------------------------------+
void CImpulse::OnSymbolChanged(string new_symbol)
  {
   Moving.Symbol(new_symbol);
  }
//+------------------------------------------------------------------+
//| 回应时段的变化                                                      |
//+------------------------------------------------------------------+
void CImpulse::OnTimeframeChanged(ENUM_TIMEFRAMES new_tf)
  {
   Moving.Timeframe(new_tf);
  }
//+------------------------------------------------------------------+
//| 返回突破水平的百分比                                                 |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| 设置突破水平百分比                                                   |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

这个策略中主要有问题的部分用黄色突出显示出来了,

首先,对指标的操作是通过之前创建的 CIndMovingAverage 类进行的,这种方法已经被证明不合理了,有太多的指标了,没法为它们中的每一个都写一个类。

第二,操作挂单的部分是在 BuyInit/SellInit 代码块中通过穷举挂单来做的,这在简单的策略,例如 CImpulse 中不会太复杂,但是如果有更加复杂的订单管理,就会出现问题。最好把设置挂单和管理它们的部分独立出来,放到单独方法中,就像新版本的 CStrategy 中所做的那样。

仔细观察 CImpulse 的代码会发现这部分功能,它是要在 CStrategy 中提供的, 现在是由 CImpulse 来做的。CStrategy 应该定义一个状态系统来管理挂单,但是它没有: 系统是在 CImpulse 本身实现的。

让我们重写代码,加上 CStrategy 的新功能:

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

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

//+------------------------------------------------------------------+
//| CImpulse 策略                                                     |
//+------------------------------------------------------------------+
class CImpulse : public CStrategy
{
private:
   double            m_percent;        // 用于挂单水平的百分比数值
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;
};
//+------------------------------------------------------------------+
//| 初始化移动平均                                                      |
//+------------------------------------------------------------------+
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;
}
//+------------------------------------------------------------------+
//| 设置止损买入挂单                                                    |
//+------------------------------------------------------------------+
void CImpulse::InitBuy(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // 只在新柱开启时创建挂单
   if(PositionsTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)   // 必须现在没有开启的买入仓位
      return;
   if(OrdersTotal(POSITION_TYPE_BUY, ExpertSymbol(), ExpertMagic()) > 0)      // 必须现在没有买入挂单
      return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // 计算新挂单的水平
   if(target < UnMA[0])                                                       // 订单触发价格必须高于移动平均
      return;
   Trade.BuyStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);       // 设置新的止损买入挂单
}
//+------------------------------------------------------------------+
//| 操作止损买入挂单用于建立买入                                          |
//| 仓位                                                              |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingBuy(const MarketEvent &event,CPendingOrder *order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() + WS.Ask()*(m_percent/100.0);                     // 计算新挂单的水平
   if(UnMA[0] > target)                                                       // 如果新的水平低于当前移动平均
      order.Delete();                                                         // - 把它删除
   else                                                                       // 否则,以新的价格来修改
      order.Modify(target);
}
//+------------------------------------------------------------------+
//|  操作止损卖出挂单来建立卖出                                           |
//| 仓位                                                              |
//+------------------------------------------------------------------+
void CImpulse::SupportPendingSell(const MarketEvent &event,CPendingOrder* order)
{
   if(!IsTrackEvents(event))return;
   double target = WS.Ask() - WS.Ask()*(m_percent/100.0);                     // 计算新挂单的水平
   if(UnMA[0] < target)                                                       // 如果新水平高于当前移动平均
      order.Delete();                                                         // - 把它删除
   else                                                                       // 否则,以新的价格来修改
      order.Modify(target);
}
//+------------------------------------------------------------------+
//| 设置止损卖出挂单                                                    |
//+------------------------------------------------------------------+
void CImpulse::InitSell(const MarketEvent &event)
{
   if(!IsTrackEvents(event))return;                                           // 只在新柱开启时创建挂单
   if(PositionsTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)  // 必须没有开启的卖出仓位
      return;
   if(OrdersTotal(POSITION_TYPE_SELL, ExpertSymbol(), ExpertMagic()) > 0)     // 必须没有开启的止损卖出挂单
      return;
   double target = WS.Bid() - WS.Bid()*(m_percent/100.0);                     // 计算新的挂单水平
   if(target > UnMA[0])                                                       // 订单触发价格必须低于移动平均
      return;  
   Trade.SellStop(MM.GetLotFixed(), target, ExpertSymbol(), 0, 0, NULL);      // 设置新的止损卖出挂单
}
//+------------------------------------------------------------------+
//| 根据移动平均管理买入仓位                                              |
//+------------------------------------------------------------------+
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();
}
//+------------------------------------------------------------------+
//| 根据移动平均管理卖出仓位                                              |
//+------------------------------------------------------------------+
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();
}
//+------------------------------------------------------------------+
//| 返回突破水平的百分比                                                 |
//+------------------------------------------------------------------+  
double CImpulse::GetPercent(void)
{
   return m_percent;
}
//+------------------------------------------------------------------+
//| 设置突破水平百分比                                                  |
//+------------------------------------------------------------------+  
void CImpulse::SetPercent(double percent)
{
   m_percent = percent;
}

//+------------------------------------------------------------------+
//| EA交易初始化函数                                                    |
//+------------------------------------------------------------------+
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);
   
}
//+------------------------------------------------------------------+
//| EA交易订单分时函数                                                  |
//+------------------------------------------------------------------+
void OnTick()
{
   Manager.OnTick();
}
//+------------------------------------------------------------------+
//| ChartEvent 函数                                                   |
//+------------------------------------------------------------------+
void OnChartEvent(const int    id,
                  const long   &lparam,
                  const double &dparam,
                  const string &sparam)
{
   Manager.OnChartEvent(id, lparam, dparam, sparam);
   ChartRedraw(0);
}

下面的屏幕截图显示了在策略测试器中 CIpmulse 2.0 的测试片段,它显示了设置的挂单以及对它们的处理:


图 1. 在 Impulse 2.0 策略测试中对挂单的处理

尽管逻辑上是相同的,新旧代码还是不同的,新旧版本之间的差别有:

  • 当前交易品种是通过 CSymbol WS (work symbol)的实例来访问的,所有对交易品种属性的访问都指派到这个类中了。
  • EA交易的数据是在 OnInit 方法中初始化的,在写文章的第五部分时, 它还没有写好。
  • 没有使用 CIndMovingAverage 指标, 现在使用的是通用的 CUnIndicator 指标。
  • 挂单的设置是在 InitBuy/InitSell 中进行的, 而它们的修改和删除是在 SupportPendingBuy/SupportPendingSell 中进行的。
  • 不再使用挂单的迭代,这个函数现在放到 CStrategy 中了。
  • 现在使用 PositionsTotal 和 OrdersTotal 方法,而不是使用 'position' 结构来计算当前开启仓位和挂单的数量了。

所提供的代码包含了策略的代码,以及EA交易基本功能的代码。换句话说,这个例子显示了单独的 .mq5 文件。这是因为项目的结构在本质上有了重新组织,我们会在下面讨论。

CStrategy 项目的新结构

在前面的版本中,CStrategy 交易引擎位于几个 MQL5 子目录中,例如,引擎本身和它的辅助文件位于 MQL5\Include\Strategy;用于实现引擎面板的源代码在 MQL5\Include\Panel 中;EA交易的代码可能位于 MQL5\Include\Strategy\Samples, 而用于运行EA的 mq5 文件 — 在 MQL5\Experts\Article08 中。另外还有各种辅助组件,例如 Dictionary.mqh 或者 XmlBase.mqh, 也都分散在 Include 文件夹的各个子目录中。

很明显,这样形成的项目间的相互关联变得非常复杂,而文件的位置和目录经常会有重复。这使得 CStrategy 交易引擎难以理解,用户,特别是初学者,会很容易混淆,而无法理解一切是如何来源以及编译过程是怎样的。所以,从当前版本的交易引擎开始,它的文件会以不同方式放置。

整个项目现在都位于 MQL5\Experts\UnExpert 目录中,它包含了 Strategy 文件夹,以及 .mq5 为扩展名的策略文件。最终的交易策略现在就是一个单独的 mq5 文件,它包含了用于处理事件的标准函数 (例如 OnInit 和 OnTick), 也包含了基于 CStrategy 的策略类本身。

所有的辅助文件现在也都放在 MQL5\Experts\UnExpert\Strategy 目录下,这也包括用于操作 XML 的文件, 和操作内部结构的文件(例如 Dictionary.mqh)。如需编译例子,只要简单打开文件 (例如, "MQL5\Experts\UnExpert\Impulse 2.0.mqh") 然后按下 "编译" 按钮。

在这篇文章提供的实例部分中,只使用了两个策略 — Impulse 1.0 和 Impulse 2.0. 它是相同的策略,只是用了 CStrategy 不同的新旧风格来写。这是有意这样做的,您可以比较这两种方法来看看文章中所描述的不同点。其他的在前面文章中用到的旧版本 CStrategy 的例子在这个版本中就没有了,这是因为它们依赖于旧的语法,所以不能作为例子在这里展示了,也许它们会出现在未来的版本中,但是使用的是修改过的语法。

结论

我们已经讨论了 CStrategy 的新组件,这包含了 CUnIndicator 类, 它实现了通用的 OOP 风格的接口,来操作任何系统或者自定义 MQL5 指标,还有用于基于 SupportPendingBuy 和 SupportPendingSell 方法的用于管理挂单的系统。所有这些元件组在一起,在开发EA交易的时候提供了强大的同步效果,用户不再需要考虑低水平的操作了。几乎所有的交易环境都可以通过类来进行直接或者间接的访问,而交易逻辑可以通过简单重载对应的预先提供的方法来实现。

现在项目都位于一个地方了,而它的关联只在 MQL5\Experts\UnExpert 目录之内,现在不再需要把文件放到 MQL5 目录下的各个文件夹下了。这种创新应该也会鼓舞用户继续使用它,或者至少会有更多兴趣来学习它的功能。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/2653

附加的文件 |
UnExpert.zip (680.21 KB)
最近评论 | 前往讨论 (1)
Ziheng Zhuang
Ziheng Zhuang | 29 10月 2017 在 18:02

不错的文章。

CUnIndicator类中Create方法用于客户自定义指标时,那个代码有中有调用PushName(),这个函数有bug,

创建用户自己定义指标时,MqlParam  parameters_array[] 第一个元素必须放置自定义的指标名称。

所有的元素都必须向右移动一个位置,把0号元素位置腾出来放置自定义的指标名称

修改如下:

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];  // 这么写是错的
   for(int i=old_size-1;i>=0;i--) m_params[i+1] = m_params[i]; //这样才对
   m_params[0].type = TYPE_STRING;
   m_params[0].string_value = name; //自定义的指标名称
   m_params_count++;// 参数个数 加1 
}
图形界面 XI: 集成标准图形库 (统合构建 16) 图形界面 XI: 集成标准图形库 (统合构建 16)

能够创建科学图表 (CGraphic 类) 的新版本图形库已于最近发布。创建图形界面的开发中函数库在本次更新中将引入创建图表的新版本控件。不同类型数据的可视化现在更加容易了。

单一资产交易顺序中的风险评估 单一资产交易顺序中的风险评估

本文介绍在交易系统分析中使用概率论方法和数学统计。

本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。 本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。

本文讨论如何在跨平台智能交易系统中设置自定义停止价位。它还讨论了一种紧密相关的方法, 即随着时间的推移, 定义停止位的演化。

自动搜索背离和趋合 自动搜索背离和趋合

本文研究各种类型背离: 简单, 隐藏, 扩展, 三重, 四重, 收敛, 以及 A, B 和 C 种类的背离。还开发了在图表上搜索并显示的通用指标。