通用EA交易:与MetaTrader的标准信号模块集成 (第7部分)

Vasiliy Sokolov | 28 七月, 2016

目录


简介

在前面的文章中,我们讨论了使得交易算法的创建过程简单而高效的方法 - 特别是我们已经创建了 CStrategy 算法,这个项目在超过六个月的时间中不断发展,在这段时间中,CStrategy 中已经加入了新的模块,使得交易过程使用了技术性执行的交易操作,而变得更加高效和安全,但是,该引擎仍然缺乏一个重要特性。尽管 CStrategy 实际上是一个严格的面向对象的应用程序,它仍然还是只能 "做自己份内之事"。面向对象的方法在代码角度提供了开放性和模块性,实际上,其代码应该是基于通用的、普通的类,特别是这关系到交易的模式和信号生成的模式,CStrategy 交易策略是基于标准的 CTrade 交易模块,但是对于 CStrategy 信号的数据库部分做得就没有那么好了,简单地说,CStrategy 并没有包含任何负责生成交易信号的模块,使用之前的版本,任何用户都要从头开始重写EA交易的逻辑,就算所需的信号在标准 MetaTrader 5包中也不行。所以,我们决定在CStrategy新版本中加入操作MetaTrader5 信号数据库的机制。在本文中,我将解释如何把 CStrategy 与一个标准的信号模块相集成,并会向您展示如何使用已经制作好的算法来创建您自己的策略。

 

策略生成器使用的类的概览

MetaTrader 5 包中标准的各种类可以在MQL向导中用于生成自动化的策略,这些类包含在mqh文件中,位于相应的 MQL5\Include 目录的子文件夹下,这些类 (或者模块) 可以被方便地分为几类。它们是:

  • 用于操作数据的基类(CObject, CArrayDouble, CArrayObj 以及其它),所有其它的交易模块都是基于这些类构建的。
  • 用于访问指标缓冲区的类(CDoubleBuffer, CIndicatorBuffer),它们是用于操作指标的。
  • 指标类和基于通用CSeries类的时间序列类。
  • 基本的EA交易类 CBaseExpert 和继承于它的 CExpert 类。所有的辅助模块都是基于 CBaseExpert 的 — 例如,用于计算资产的模块和控制跟踪止损的模块。CExpert 是自定义 EA 交易的基础。
  • 信号模块是基于 CExpertSignal 的, 而它则是基于 CEpertBase 的。信号模块会产生买入和卖出的交易信号,它们使用指标类,信号就是根据它们产生的。
  • CExpertTrade 交易模块,它是基于 CTrade 类的,并且它提供了对交易操作执行的访问。

以下的图表显示了在策略的自动生成过程中,类的垂直继承一般架构。

 

图 1. 策略生成器中标准类的继承

此图只显示了基本类和一些派生类,概要图中并没有包含所有的从 CIndicators 类中继承的指标。独立的跟踪止损,资金管理和信号模块也都没有包含在图中,而只是描绘了基本的相互关系。其中有一组是我们感兴趣的: CExpertSignal 信号类以及它的子类。在图1中,这个组是使用绿色点形线来突出显示的,

除了垂直关联之外,一个复杂系统还由类的包含构成 (水平关联),例如,信号模块使用了指标类,也就是使用了指标缓冲区。各种集合是相互间的组成部分。例如,资金管理模块同时也是交易的专家(至少是基于 CExpertBase 的),尽管这些模块与EA交易之间看起来不知有没有关系。

作为一项原则,为了创建这些类的对象,需要复杂的初始化链,例如,为了创建一个信号对象,比如 CSignalMacd, 我们应该初始化信号,初始化它所依赖的对应指标,并且为了信号的运行也要初始化所需的时间序列(CPriceSeries的子类),因为对象可能由复杂对象初始化,所以它们也需要初始化(就像时间序列),因而,初始化的问题是所述开发库最复杂的部分之一。

让我们分析以下示例,假定我们需要初始化 CMacdSignal 模块,在创建这个类的对象时在类中初始化,信号模块的初始化代码将如下所示:

//+------------------------------------------------------------------+
//| CSignalMacd 信号模块的初始化                      |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   CSymbolInfo* info = new CSymbolInfo();                // 创建一个代表此策略交易品种的对象
   info.Name(Symbol());                                  // 初始化代表策略中交易品种的对象
   m_signal_ma.Init(info, Period(), 10);                 // 根据交易品种和时段初始化信号模块
   m_signal_ma.InitIndicators(GetPointer(m_indicators)); // 在空白的指标列表 m_indicators 基础上创建信号模块中所需的指标
   m_signal_ma.EveryTick(true);                          // 测试模式
   m_signal_ma.Magic(ExpertMagic());                     // 幻数
   m_signal_ma.PatternsUsage(8);                         // 模式的掩码
   m_open.Create(Symbol(), Period());                    // 初始化开盘价的时间序列
   m_high.Create(Symbol(), Period());                    // 初始化最高价的时间序列
   m_low.Create(Symbol(), Period());                     // 初始化最低价的时间序列
   m_close.Create(Symbol(), Period());                   // 初始化收盘价的时间序列
   m_signal_ma.SetPriceSeries(GetPointer(m_open),        // 初始化信号模块的时间序列对象
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}

应该注意的是,初始化的问题与自动策略生成器用户无关,整个初始化链条是由策略生成器自动创建的,用户所需要做的就是开始使用EA交易。对于想用这些类来创建他们自己解决方案的人来说,情况就有所不同了,如果是这样,就需要执行整个初始化链条。

 

信号模块概览,模式的概念

我们已经说过,信号模块式基于通用的 CExpertSignal 类的,而它又是基于 CExpertBase 类的,实际上,每个标准的信号模块都是一个类,会搜索一个或者多个模式 — 决定买入或者卖出时刻的特定逻辑条件。例如,如果快速移动平均从下往上穿过慢速移动平均,它就构成了一个买入模式。每个指标都可以构成多个买入和卖出的条件,一个简单的例子是 MACD 指标,指标的背离和信号线与指标柱形图的交叉都可以被解释成为一个信号,这些交易条件代表了不同的模式,可以根据一个或者多个这样的事件来进入市场。很重要的是要注意,买入与卖出的模式是不同的,并且通常由相反的条件表示。从自动化策略生成器的角度看,信号就是含有一个通用指标的一个或多个模式的处理器,例如,一个基于 MACD 的信号可以侦测市场上的多个模式,标准的基于MACD模块的信号包含了5个买入模式和5个卖出模式:

  • 反转 — 震荡指标转向上(买入)或者向下(卖出)
  • 主线与信号线的交叉
  • 与零点线的交叉
  • 背离
  • 双重背离

模式的详细描述在终端的帮助文件中, 所以我们就不在这里详细讨论了,其他的信号模块包含了不同的模式,并且它们的数量也不尽相同。典型情况下,每个信号平均含有三个买入模式和三个卖出模式,一个信号最多可以在一个方向上包含32个模式,在相反方向上也包含32个模式(这是整数变量位的长度,它保存了使用模式的掩码)。

信号可以识别模式,也可以以整数的形式提供一些建议,我们期望的是这个数字应该显示信号的强度: 数字越大, 信号就越强。也可以使用特别的方法组 Pattern_x(int value) 来为每个模式设置信号的强度, 其中 x 是模式的索引。例如,以下就是信号配置的代码:

//+------------------------------------------------------------------+
//| CSignalMacd 信号模块的初始化                      |
//+------------------------------------------------------------------+
CStrategyMACD::CStrategyMACD(void)
{
   m_signal_ma.Pattern_0(0);
   m_signal_ma.Pattern_1(0);
   m_signal_ma.Pattern_2(0);
   m_signal_ma.Pattern_3(100);
   m_signal_ma.Pattern_4(0);
   m_signal_ma.Pattern_5(0);
}

在这种情况下,CSignalMacd 信号将只在形成背离时返回一个数值,也就是说,MACD指标的模式中, 第一个震荡指标的波谷比前一个波谷要浅,而对应的价格波谷比前一个要深(这就是一个买入信号)。所有其他的模式都将跳过,

交易的推荐通过两个独立方法返回,即 LongCondition 和 ShortCondition,第一个返回买入的推荐,第二个 - 卖出。让我们深入了解它们,以明白它们是如何工作的,这里是 LongCondition:

//+------------------------------------------------------------------+
//| 价格将要增长的指示                          |
//+------------------------------------------------------------------+
int CSignalMACD::LongCondition(void)
  {
   int result=0;
   int idx   =StartIndex();
//--- 检查主线的方向
   double diff = DiffMain(idx);
   if(diff>0.0)
     {
      //--- 主线方向向上,这是价格可能上升的确认
      if(IS_PATTERN_USAGE(0))
         result=m_pattern_0;      // 信号确认编号 0
      //--- 如果使用了模式 1, 就搜索主线的反转
      if(IS_PATTERN_USAGE(1) && DiffMain(idx+1)<0.0)
         result=m_pattern_1;      // 信号编号 1
      //--- 如果使用了模式2,就搜索主线与信号线的交叉
      if(IS_PATTERN_USAGE(2) && State(idx)>0.0 && State(idx+1)<0.0)
         result=m_pattern_2;      // 信号编号 2
      //--- 如果使用了模式3,就搜索主线与零点线的交叉 
      if(IS_PATTERN_USAGE(3) && Main(idx)>0.0 && Main(idx+1)<0.0)
         result=m_pattern_3;      // 信号编号3
      //--- 如果使用了模式4和5,并且主线低于零点线水平,并且在上升,就搜索背离 
      if((IS_PATTERN_USAGE(4) || IS_PATTERN_USAGE(5)) && Main(idx)<0.0)
        {
         //--- 进一步分析震荡指标状态 
         ExtState(idx);
         //--- 如果使用了模式4,就等待背离信号 
         if(IS_PATTERN_USAGE(4) && CompareMaps(1,1)) // 0000 0001b
            result=m_pattern_4;   // 信号编号 4
         //--- 如果使用了模式5,就等待双重背离信号 
         if(IS_PATTERN_USAGE(5) && CompareMaps(0x11,2)) // 0001 0001b
            return(m_pattern_5);  //信号编号5
        }
     }
//--- 返回结果
   return(result);
  }

 该方法使用了模式侦测的宏定义 IS_PATTERN_USAGE, 它是基于位的掩码的:

//--- 检查是否使用了市场模式
#define IS_PATTERN_USAGE(p)          ((m_patterns_usage&(((int)1)<<p))!=0)

如果使用了模式,并且达到了相应的条件,推荐的结果就将等于对应的模式权重,它是用户通过 Pattern_x 组方法来定义的。但是,如果您使用了多个模式,就无法找到它们中的哪一个被触发了,因为检查中间没有中断。在这种情况下,推荐的强度就将等于之前确定的模式的权重。

您想使用的模式的编号应当以特定位掩码的形式给出,例如,如果您想使用编号3的模式,32位变量的第4位应该等于1(注意,模式的索引从零开始,所以第四位就是第三个模式,第一位用于编号为0的模式)。如果我们把二进制数字1000转为十进制,我们会得到数字8,这就是我们需要传给 PatternsUsage 方法的数字。模式也可以组合,例如,为了把编号为3的模式和编号为2的模式一起使用,您必须创建一个位变量,第4位和第3位等于1,即等于: 1100,童谣数值的十进制格式是12。

 

信号模块。第一次使用

现在我们已经足够了解信号模块的使用了,让我们在 CStrategy 的基础上试一试,为此,我们创建一个特定的实验类,CSignalSamples 策略。

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/en/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Expert\Signal\SignalMACD.mqh>
//+------------------------------------------------------------------+
//| 该策略接收事件并在终端中显示它们。|
//+------------------------------------------------------------------+
class CSignalSamples : public CStrategy
{
private:
   CSignalMACD       m_signal_macd;
   CSymbolInfo       m_info;
   CiOpen            m_open;
   CiHigh            m_high;
   CiLow             m_low;
   CiClose           m_close;
   CIndicators       m_indicators;
public:
                     CSignalSamples(void);
   virtual void      OnEvent(const MarketEvent& event);                     
};
//+------------------------------------------------------------------+
//| CSignalMacd 信号模块的初始化                      |
//+------------------------------------------------------------------+
CSignalSamples::CSignalSamples(void)
{
   m_signal_macd.Pattern_0(0);
   m_signal_macd.Pattern_1(0);
   m_signal_macd.Pattern_2(0);
   m_signal_macd.Pattern_3(100);
   m_signal_macd.Pattern_4(0);
   m_signal_macd.Pattern_5(0);
   m_info.Name(Symbol());                                  // 初始化代表策略中交易品种的对象
   m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // 根据交易品种和时段初始化信号模块
   m_signal_macd.InitIndicators(GetPointer(m_indicators)); // 在空白指标列表 m_indicators 基础上创建信号模块所需的指标
   m_signal_macd.EveryTick(true);                          // 测试模式
   m_signal_macd.Magic(ExpertMagic());                     // 幻数
   m_signal_macd.PatternsUsage(8);                         // 模式掩码
   m_open.Create(Symbol(), Period());                      // 初始化开盘价的时间序列
   m_high.Create(Symbol(), Period());                      // 初始化最高价的时间序列
   m_low.Create(Symbol(), Period());                       // 初始化最低价的时间序列
   m_close.Create(Symbol(), Period());                     // 初始化收盘价的时间序列
   m_signal_macd.SetPriceSeries(GetPointer(m_open),        // 根据时间序列对象初始化信号模块
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
                              
}
//+------------------------------------------------------------------+
//| 买入                                |
//+------------------------------------------------------------------+
void CSignalSamples::OnEvent(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0 || power_sell != 0)
      printf("PowerSell: " + (string)power_sell + " PowerBuy: " + (string)power_buy);
}
//+------------------------------------------------------------------+

CStrategy 的最新版本包含了一个新的事件OnEvent, 以 OnEvent 方法表示, 每当有事件发生时就会调用,与我们更熟悉的方法例如 BuyInit, SellInit, BuySupport 和 SellSupport 不同, OnEvent 不论任何交易策略模式和交易计划下都会被调用,. 所以,OnEvent 使得可以从策略中访问事件流,而保持了严格的事件模型。OnEvent 对通用计算或者操作的使用很方便,它们与特定的买入或者卖出方向不相关,

大部分代码都是用于交易信号模块的初始化的。作为实例,我们使用了基于 MACD 指标的: CSignalMACD,模块中只使用了编号3的模式。模块中使用了权重100,并在对应的 PatternsUsage 方法中使用了数值8。为了运行这个策略,需要准备一个策略载入程序或者一个可执行的 mq5 模块。它的内容:

//+------------------------------------------------------------------+
//|                                                       Agent.mq5  |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/en/users/c-4"
#property version   "1.00"
#include <Strategy\StrategiesList.mqh>
#include <Strategy\Samples\SignalSamples.mqh>
CStrategyList Manager;
//+------------------------------------------------------------------+
//| EA 初始化函数                              |
//+------------------------------------------------------------------+
int OnInit() 
{
   CSignalSamples* signal = new CSignalSamples();
   signal.ExpertMagic(2918);
   signal.ExpertName("MQL 信号实例");
   signal.Timeframe(Period());
   if(!Manager.AddStrategy(signal))
      delete signal;
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| EA 订单处理函数                         |
//+------------------------------------------------------------------+
void OnTick()
{
  Manager.OnTick();
}
//+------------------------------------------------------------------+
//| OnChartEvent 函数                          |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
{
   Manager.OnChartEvent(id, lparam, dparam, sparam);
}

它在OnInit函数中包含了策略的初始化,并在 OnTick 函数中处理新订单,在策略测试器中以可视化模式运行结果代码,您将可以看到接收到信号的通知:

2016.06.20 16:34:31.697 测试器代理关闭完成
2016.06.20 16:34:31.642 关闭测试器引擎
2016.06.20 16:34:31.599 开始关闭测试器代理
2016.06.20 16:34:31.381 记录 文件为 "Z:\MetaTrader 5\Tester\Agent-127.0.0.1-3000\logs\20160620.log" 
2016.06.20 16:34:31.381 使用了325 Mb 内存,包括 28 Mb 历史数据, 64 Mb 订单数据
2016.06.20 16:34:31.381 EURUSD,M1: 51350 订单数据 (12935 bars) 已生成, 时间 0:00:00.780 (历史总柱数 476937, 总时间 0:00:00.843)
2016.06.20 16:34:31.376 最终余额 100000.00 USD
2016.06.20 16:34:31.373 2016.04.14 22:12:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 22:01:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 21:24:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:54:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:50:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:18:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.373 2016.04.14 20:14:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:13:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.373 2016.04.14 20:07:00   PowerSell: 0 PowerBuy: 100
2016.06.20 16:34:31.372 2016.04.14 19:48:00   PowerSell: 100 PowerBuy: 0
2016.06.20 16:34:31.372 2016.04.14 18:48:00   PowerSell: 0 PowerBuy: 100
...
...

这个通知指出了,所要求的信号已经成功接收到,我们可以继续在我们的策略中集成信号模块了。

 

基于 CSignalMACD 开发第一个策略

现在是时候在 CSignalMACD 信号模块的基础上开发一个完整功能的策略了,我们将要使用的第一个模式是信号线与震荡指标交叉的模式,让我们使用文档,打开 MQL5 参考 -> 标准库 -> 交易策略类 -> 交易信号模块 -> MACD 震荡指标信号,找到第二个模式,"主线与信号线的交叉",这里是它的描述:

  • 买入: "主线与信号线的交叉" — 在所分析的柱中,主线在信号线上方,而在前一个柱中,主线在信号线下方。

  • 图 2. 震荡指标从下往上穿过信号线

  • 卖出: "主线与信号线的交叉" — 在所分析的柱中,主线在信号线下方,而在前一个柱中,主线在信号线上方。

图 3. 震荡指标从上往下穿过信号线

我们需要定义对应着这个描述的模式的编号,CSignalMACD 类的头文件将会帮助我们:

//+------------------------------------------------------------------+
//| Class CSignalMACD.                                               |
//| 目的: 生成交易信号的类                                |
//|   基于MACD震荡指标。|
//| 继承于 CExpertSignal 类。                     |
//+------------------------------------------------------------------+
class CSignalMACD : public CExpertSignal
  {
protected:
   CiMACD            m_MACD;           // 震荡指标对象
   //--- 调整的参数
   int               m_period_fast;    // 震荡指标参数 "快速 EMA 周期数" 
   int               m_period_slow;    // 震荡指标参数 "慢速 EMA 周期数" 
   int               m_period_signal;  // 震荡指标参数 "差距的平均周期数" 
   ENUM_APPLIED_PRICE m_applied;       // 震荡指标参数 "价格序列" 
   //--- 市场模式的"权重" (0-100)
   int               m_pattern_0;      // 模式 0 "震荡指标向所需方向移动"
   int               m_pattern_1;      // 模式 1 "震荡指标向所需方向反转"
   int               m_pattern_2;      // 模式 2 "主线与信号线交叉"
   int               m_pattern_3;      // 模式 3 "主线与零线水平交叉"
   int               m_pattern_4;      // 模式 4 "震荡指标与价格背离"
   int               m_pattern_5;      // 模式 5 "震荡指标与信号双重背离"
   //--- 变量
   double            m_extr_osc[10];   // 震荡指标极值的数组 
   double            m_extr_pr[10];    // 对应价格极值的数组 
   int               m_extr_pos[10];   // 极值偏移的数组 (单位是柱数) 
   uint              m_extr_map;       // 震荡指标极值与价格极值的结果位图 
...
  }

从代码的注释中,我们可以看到模式类型的编号是2,

现在我们知道了模式的编号,我们就需要正确配置信号了。首先,为了避免混淆,我们将不使用其他模式,为此我们把模式掩码设为等于4(二进制形式为100),因为我们只打算使用一个模式,我们不需要知道信号的强度(不用配置模式的强度) - 即有信号,或者没有信号。我们将在新柱开启时检查信号,所以我们应该调用对应的信号方法 EveryTick,标志为false,配置应该在策略的构造函数中实现,然后继续交易逻辑的编程。让我们重载这些方法: InitBuy, SupportBuy, InitSell, SupportSell,让我们把策略命名为 COnSignalMACD: On前缀指出,该策略是基于信号标准模块的。策略的代码如下所示:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/en/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Expert\Signal\SignalMACD.mqh>
//+------------------------------------------------------------------+
//| 该策略接收事件并在终端中显示它们。|
//+------------------------------------------------------------------+
class COnSignalMACD : public CStrategy
{
private:
   CSignalMACD       m_signal_macd;
   CSymbolInfo       m_info;
   CiOpen            m_open;
   CiHigh            m_high;
   CiLow             m_low;
   CiClose           m_close;
   CIndicators       m_indicators;
public:
                     COnSignalMACD(void);
   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);
};
//+------------------------------------------------------------------+
//| CSignalMacd 信号模块的初始化                      |
//+------------------------------------------------------------------+
COnSignalMACD::COnSignalMACD(void)
{
   m_info.Name(Symbol());                                  // 初始化代表策略中交易品种的对象
   m_signal_macd.Init(GetPointer(m_info), Period(), 10);   // 根据交易品种和时段初始化信号模块
   m_signal_macd.InitIndicators(GetPointer(m_indicators)); // 在空白指标列表 m_indicators 基础上创建信号模块所需的指标
   m_signal_macd.EveryTick(false);                         // 测试模式
   m_signal_macd.Magic(ExpertMagic());                     // 幻数
   m_signal_macd.PatternsUsage(4);                         // 模式掩码
   m_open.Create(Symbol(), Period());                      // 初始化开盘价的时间序列
   m_high.Create(Symbol(), Period());                      // 初始化最高价的时间序列
   m_low.Create(Symbol(), Period());                       // 初始化最低价的时间序列
   m_close.Create(Symbol(), Period());                     // 初始化收盘价的时间序列
   m_signal_macd.SetPriceSeries(GetPointer(m_open),        // 根据时间序列对象初始化信号模块
                              GetPointer(m_high),
                              GetPointer(m_low),
                              GetPointer(m_close));
}
//+------------------------------------------------------------------+
//| 买入                                |
//+------------------------------------------------------------------+
void COnSignalMACD::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0)
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void COnSignalMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   //printf("Power sell: " + (string)power_sell);
   if(power_sell != 0)
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| 卖出。                                |
//+------------------------------------------------------------------+
void COnSignalMACD::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_sell = m_signal_macd.ShortCondition();
   if(power_sell != 0)
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void COnSignalMACD::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   m_indicators.Refresh();
   m_signal_macd.SetDirection();
   int power_buy = m_signal_macd.LongCondition();
   if(power_buy != 0)
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+

根据参考中的信号描述,建立了买入和卖出仓位,已有仓位是通过相反信号平仓的,也就是说,如果有一个建立买入仓位的条件,那么之前开启的卖出仓位将被关闭,反之亦然。

交易结果可以在策略测试器中看到,在下图中标出了部分测试历史:

 

图 4. 在MACD柱形图与信号线交叉时,建立交易仓位,

根据测试模式,交易都是因为信号在前一个柱接收到时建立仓位,图中显示,如果前一个柱有MACD柱形图和信号线的交叉,图中显示了,柱形图在买入或者卖出The figure shows that on the bar following the crossing of the MACD histogram and the signal line, a long or a short position is opened, and the previous position is closed.

 

信号适配器

我们已经知道,在您开始使用信号之前,您需要配置它。信号是使用复杂对象配置的,而它们在传入到信号之前也是需要配置的,不同的信号需要不同的对象以支持其运行。例如,一些信号只是需要基本的时段设定,而对于另外一些信号,您还需要指定指标容器以及另外的价格数据,例如订单价格或者实际交易量。所有这些都会使用户使用信号更加复杂,因为用户需要了解信号的内部系统,以及为了它的正常工作,需要哪些数据。

为了避免这些困难,我们引入了特别的适配器类(adapter class)。该类被命名为CSignalAdapter, 它位于 CStrategy 项目的通用目录下。适配器有一个简单的接口,它可以创建一个信号,并接收买入或者卖出模式构造的标志。要创建一个信号,我们需要把信号的参数传给一个特殊的方法 CSignalAdapter::CreateSignal,信号的参数包含在特定的MqlSignalParams结构中,这里是此结构的定义:

//+--------------------------------------------------------------------+
//| 信号参数                                |
//+--------------------------------------------------------------------+
struct MqlSignalParams
{
public:
   string            symbol;           // 交易品种
   ENUM_TIMEFRAMES   period;           // 图表时段
   ENUM_SIGNAL_TYPE  signal_type;      // 信号类型
   int               index_pattern;    // 模式索引
   int               magic;            // EA 的幻数
   double            point;            // 点数
   bool              every_tick;       // "每一订单"测试模式
   void operator=(MqlSignalParams& params);
};
//+--------------------------------------------------------------------+
//| 使用复制操作符,因为策略使用字符串                                  |
//+--------------------------------------------------------------------+
void MqlSignalParams::operator=(MqlSignalParams& params)
{
   symbol = params.symbol;
   period = params.period;
   signal_type = params.signal_type;
   usage_pattern = params.usage_pattern;
   magic = params.magic;
   point = params.point;
   every_tick = params.every_tick;
}

这个结构包含了定义以下信号特征的基本类型:

  • 交易品种;
  • 时段;
  • 信号类型;
  • EA的幻数;
  • 一个用于指示"每一订单"测试模式的标志;
  • 价格过滤器;
  • 使用的信号模式。

这里是一些关于index_pattern的详细内容,与信号模块不同,它不接收模式的掩码,而只是接收它们的索引。所以,每个信号适配器只能使用所选信号的一个模式index_pattern 的值应该在1到31之间, 并且应该等于所使用信号的真正编号

除了基本的参数,此结构还包含了一个复制操作符,因为它使用了字符串类型,这就是为什么不能自动把一个结构复制到另外一个结构的原因。在确定了所需的参数并填充了相应的结构之后,用户就可以调用 CSignalAdapter::CreateSignal 方法并在返回中得到来自此方法的所创建信号的实例,所得到的实例可以进一步配置,以设定对应信号的特定功能,

以下代码显示了使用 CSignalAdapter 适配器配置 CSignalMACD 信号的方法:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/en/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Strategy\SignalAdapter.mqh>

//+------------------------------------------------------------------+
//| 该策略接收事件并在终端中显示它们。|
//+------------------------------------------------------------------+
class CAdapterMACD : public CStrategy
{
private:
   CSignalAdapter    m_signal;
   MqlSignalParams   m_params;
public:
                     CAdapterMACD(void);
   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);
};
//+------------------------------------------------------------------+
//| 配置适配器                             |
//+------------------------------------------------------------------+
CAdapterMACD::CAdapterMACD(void)
{
   m_params.symbol = Symbol();
   m_params.period = Period();
   m_params.every_tick = false;
   m_params.signal_type = SIGNAL_MACD;
   m_params.magic = 1234;
   m_params.point = 1.0;
   m_params.usage_pattern = 2;
   CSignalMACD* macd = m_signal.CreateSignal(m_params);
   macd.PeriodFast(15);
   macd.PeriodSlow(32);
   macd.PeriodSignal(6);
}

应该为此适配器配置参数,然而,与EA交易的第一个版本不同,所有的参数都是简单的,也就是基本类型。另外,也不需要创建或者监控其它复杂的对象,例如时间序列和指标,这些都是由适配器完成的。这也是使用它使得信号的操作更为简单的原因。  

请注意,在创建信号之后,我们要继续通过设置我们自己的 MACD 指标的周期数 (15, 32, 6),这是很容易做到的,因为 CreateSignal 方法返回了对应的对象,

在信号正确配置之后,您就可以开始使用它了,BuySignal 和 ShortSignal 这些简单方法就是用于此目的,这里是策略类的继续:

//+------------------------------------------------------------------+
//| 买入                                |
//+------------------------------------------------------------------+
void CAdapterMACD::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.LongSignal())
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void CAdapterMACD::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.ShortSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
 //| 卖出                                 |
//+------------------------------------------------------------------+
void CAdapterMACD::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.ShortSignal())
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void CAdapterMACD::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_signal.LongSignal())
      pos.CloseAtMarket();
}

以上逻辑与之前的例子相同: 它在MACD柱形图与信号线交叉时建立买入和卖出仓位,然而您可以看到,代码现在更加简短了。与信号代码的第一个版本不同,现在不需要每次都定义信号的方向或者更新指标值了,您现在所需要的就是把辅助对象加到策略的代码中,所有这些操作都由适配器完成。

 

在交易策略代码中组合多个信号

我们能够在进入或者退出市场时使用不同的模式甚至不同的信号吗?答案是"可以"。我们拥有信号系统的完全访问权,所以我们可以使用多个模式。为了避免模式的混淆,信号适配器只允许使用一个模式,但是适配器的数量没有限制,在这种情况下,每个模式都由一个独立的适配器和信号表示,即使所有的模式都基于一个信号。当然,从使用资源的角度看,这比标准库中的方法效率要低,但是它是有好处的。

让我们写一个策略的实例,它可以接收不同的进入市场和退出市场的信号,此策略将使用RSI指标的模式,基于其超买和超卖区域来进入市场,第二个模式 - 就是由比尔.威廉姆提出的加速震荡指标(AC),它用于退出市场。这里是此策略更加详细的内容。

买入: 从超卖区域反转 — 震荡指标转向上方,并且它的数值在所分析的柱中低于超卖水平(默认值为 30)。

 

图 5. 买入仓位的建仓条件 

 

卖出: 从超买区域反转 — 震荡指标转向下方,并且它的数值在所分析的柱中高于超买水平 (默认值为 70)。

 

图 6. 卖出仓位的建仓条件

买入平仓: AC 指标值大于 0, 并且在所分析的柱和之前两个柱上都在下降:


图 7. 买入仓位的平仓条件 

卖出平仓: AC 指标值小于 0, 并且在所分析的柱和之前两个柱上都在上升:

 

图 8. 卖出仓位的平仓条件

买入仓位的平仓是基于AC信号中用于建立卖出仓位的模式,相反,使用AC信号中用于建立买入仓位的模式来进行卖出仓位的平仓,

实现这个逻辑的EA交易如下所示:

//+------------------------------------------------------------------+
//|                                                EventListener.mqh |
//|           Copyright 2016, Vasiliy Sokolov, St-Petersburg, Russia |
//|                                https://www.mql5.com/en/users/c-4 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2016, Vasiliy Sokolov."
#property link      "https://www.mql5.com/ru/users/c-4"
#include <Strategy\Strategy.mqh>
#include <Strategy\SignalAdapter.mqh>
input int RSI_Period = 14; // RSI 周期数
//+------------------------------------------------------------------+
//| 该策略接收事件并在终端中显示它们。|
//+------------------------------------------------------------------+
class COnSignal_RSI_AC : public CStrategy
{
private:
   CSignalAdapter    m_adapter_rsi;
   CSignalAdapter    m_adapter_ac;
public:
                     COnSignal_RSI_AC(void);
   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);
};
//+------------------------------------------------------------------+
//| CSignalMacd 信号模块的初始化                      |
//+------------------------------------------------------------------+
COnSignal_RSI_AC::COnSignal_RSI_AC(void)
{
   MqlSignalParams params;
   params.every_tick = false;
   params.magic = 32910;
   params.point = 10.0;
   params.symbol = Symbol();
   params.period = Period();
   params.usage_pattern = 2;
   params.signal_type = SIGNAL_AC;
   CSignalAC* ac = m_adapter_ac.CreateSignal(params);
   params.usage_pattern = 1;
   params.magic = 32911;
   params.signal_type = SIGNAL_RSI;
   CSignalRSI* rsi = m_adapter_rsi.CreateSignal(params);
   rsi.PeriodRSI(RSI_Period);
}
//+------------------------------------------------------------------+
//| 买入                                |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::InitBuy(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(positions.open_buy > 0)
      return;
   if(m_adapter_rsi.LongSignal())
      Trade.Buy(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::SupportBuy(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_adapter_ac.ShortSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+
//| 卖出。                                |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::InitSell(const MarketEvent &event)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(positions.open_sell > 0)
      return;
   if(m_adapter_rsi.ShortSignal())
      Trade.Sell(1.0);
}
//+------------------------------------------------------------------+
//| 买入平仓                               |
//+------------------------------------------------------------------+
void COnSignal_RSI_AC::SupportSell(const MarketEvent &event, CPosition* pos)
{
   if(event.type != MARKET_EVENT_BAR_OPEN)
      return;
   if(m_adapter_ac.LongSignal())
      pos.CloseAtMarket();
}
//+------------------------------------------------------------------+

请注意,这个EA交易有一个外部变量,使得您可以设置RSI指标的周期数,这是在策略构造器中通过直接访问信号进行的,

策略的运行结果在以下图表中显示:

 

图 8. 策略的结果 

您可以在图表中看到,此 EA 交易使用了两个指标 — RSI 和 AC。EA 交易在RSI指标开始在超买和超卖区域中上升或者下降时进入市场,这些区域使用红色标识,EA交易在AC指标形成三条同样颜色的线时平仓退出市场,对于买入仓位的平仓,线的颜色必须是红色的而且必须高于零点线水平,对于卖出仓位的平仓,线的颜色必须是绿色的并且必须低于零点线水平。这样的时刻在蓝色框中显示。

图表显示了EA的逻辑是正确处理的。此EA交易的交易规则并不简单,但是策略本身并不长,这就是代码重用的好处。

 

结论

我们已经探讨了在 CStrategy 交易引擎中集成标准库的信号的方法,通过这种集成, CStrategy 就是一个基于标准信号的模式来创建自定义策略的有力工具。另外,任何为自动的 MetaTrader 策略生成器开发的信号都可以在 CStrategy 交易引擎中使用,

使用标准的交易信号可以大幅节约策略开发的时间。现在您不需要开发自己的指标或者模式侦测算法,如果它们已经在信号的标准模块中具备的话。另外,这个新的功能大幅减少了策略开发过程的复杂性,那些复杂模式的定义,例如背离或者双重背离,现在是由已经完成的解决方案来做的。