下载MetaTrader 5

6 步创建您自己的交易机器人!

6 十一月 2013, 13:28
MetaQuotes Software Corp.
0
1 556

又说到 MQL5 向导了

身边的世界正在飞速变化,而我们则试图紧跟其脚步。我们没有时间学习新东西,正常人往往都会这么想。交易员也和每个人一样,他们追求的是用最小的工作量取得最大的成效。MetaEditor 5 专为交易员准备了一套奇妙的 MQL5 向导。已有多篇文章讲述过如何利用此向导创建一个自动化交易系统,其中包括“轻量级版本” MQL5 傻瓜向导 和一个“开发人员版本” - MQL5 向导:新版本

看起来都不错 - 点击 5 次鼠标即可创建一个交易机器人,您还可以在“策略测试仪”中对其进行检测,实现某交易系统中的参数优化,您可以让创建的机器人根据您的账户进行交易,且无需再加任何人工干预。但是,当交易员/MQL5 开发人员想要创建某些自己的东西、某些从未于任何地方描述过的独特的东西、以及要编写其自己的交易信号模块时,问题就出现了。交易员会打开 MQL5 文档,找到“标准库”,然后吃惊地发现……


5 个令人生畏的类

的确,MQL5 向导极大地简化了“EA 交易”的创建,但是您首先需要学习什么能用作其输入。欲利用 MQL5 向导自动创建“EA 交易”,则要确保其组件从属于“EA 交易”的基类一节所述的 5 个基类:

这就是“又伟大又可怕”、被称为面向对象编程 (OOP) 方法的全部阵容。但不用害怕,现在几乎每个人都有一部拥有大量功能的手机,而且几乎没有人知道它是怎么运行的。我们无需全盘学习,只研究 CExpertSignal 类的某些功能即可。


本文中,我们会从头到尾看一看创建交易信号模块的几个阶段,您也会明白如何在不学习 OOP 或类的情况下完成创建。如果您愿意,则还可以再深入一些。


1. 从头创建一个类

我们不会根据自己的需求更改任何现有的交易信号模块,因为那样会造成混淆。所以,我们只是简单地编写自己的类,但是首先,我们使用 Navigator 创建一个新文件,以将我们的信号存储于 MQL5/Include/Expert/



右键点击我们创建的文件名,选择 "New File" (新建文件),再为我们的交易信号模块创建一个新的类。


填写字段:

  • Class Name - 类的名称。这个会是两条移动平均线交叉处生成信号的一个模块,所以我们将其命名为 MA_Cross。
  • “基类”即指我们的类派生的由来。我们的派生由来基类应为 CExpertSignal

点击 "Finish" (完成),我们的模块即建模完成。到目前为止,还都很简单。我们只需将 #include 声明添加到结果文件,以使编译器知道基类 CExpertSignal 在哪里

#include "..\ExpertSignal.mqh"   // CExpertSignal 是在包含文件 ExpertSignal 中

结果是:

//+------------------------------------------------------------------
//|                                                     MA_Cross.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"

#include "..\ExpertSignal.mqh"   // CExpertSignal 是在包含文件 ExpertSignal 中
//+------------------------------------------------------------------
//|                                                                  |
//+------------------------------------------------------------------
class MA_Cross : public CExpertSignal
  {
private:

public:
                     MA_Cross();
                    ~MA_Cross();
  };
//+------------------------------------------------------------------
//|                                                                  |
//+------------------------------------------------------------------
MA_Cross::MA_Cross()
  {
  }
//+------------------------------------------------------------------
//|                                                                  |
//+------------------------------------------------------------------
MA_Cross::~MA_Cross()
  {
  }
//+------------------------------------------------------------------

检查结果类(必须没有编译错误)并点击 F7。没有任何错误,所以我们可以继续了。


2. 模块的处理函数

我们的类还完全是空的,没有任何错误,所以我们可以试试它 - 尝试以其为基础、利用 MQL5 向导创建一个新的“EA 交易”。我们来到选择一个交易信号模块这一步再看……模块怎么没在这?


它又怎么能在那呢?我们并未添加任何指示,让 MQL5 向导明白我们的类是有用的东西。我们来修复一下。如果您观察标准包的模块,就会发现每个模块文件的开头都包含一个数据头。这就是根据特定规则编译的模块处理函数。规则倒是非常简单。

比如说,打开基于 AMA 的交易信号模块的源代码(参见自适应移动平均线的信号中的逻辑描述。)然后运行 MQL5 向导,选择此模块。对比:

处理函数中的最后一个区块是指模块参数,而第一行则包含将于 MQL5 向导中显示的模块名称。看到了吧,没什么复杂的。因此,每个模块的处理函数都包含下述条目:

  • 标题  - 将于 MQL5 向导中显示的模块名称。
  • 类型 - 信号模块的版本。始终都必须是 SignalAdvanced。
  • 名称 - 于 MQL5 向导中选定之后的模块名称,用于描述生成的“EA 交易”内部参数的注释中(最好指定)。
  • 短名称 - 生成的“EA 交易”中外部参数自动命名的一个前缀(采用 Signal_<ShortName>_<ParameterName> 的形式)。
  • 类 - 的名称,包含于模块中。
  • 页 - 获取此模块帮助的一个参数(仅限标配模块)。

接下来是以 Parameter=list_of_values 形式出现的参数描述,以此指定下述内容(逗号分隔):

  1. 启动“EA 交易”时,设定参数值的函数的名称。
  2. 参数可以是枚举类型。
  3. 参数的默认值,即,如果您不利用 MQL5 向导更改它,参数就会被设置为该值。
  4. 参数的描述,您会在启动利用 MQL5 向导生成的“EA 交易”时看到。

现在,明白所有这些之后,我们为自己的交易信号创建一个处理函数。我们正在编写一个于两条移动平均线交叉处获取交易信号的模块。我们至少需要设置 4 项外部参数:

  • FastPeriod - 快速移动平均线时段
  • FastMethod - 快速移动平均线的平滑类型
  • SlowPeriod - 慢速移动平均线时段
  • SlowMethod - 慢速移动平均线的平滑类型

您也可以添加一个 shift (位移)和价格类型以计算每条移动平均线,但不会从根本上改变任何东西。所以,当前的版本如下:

// 向导描述开始
//+------------------------------------------------------------------
//| 类描述                                                |
//| 标题=两条移动平均线交叉信号                           |
//| 类型=SignalAdvanced                                 |
//| 名称=My_MA_Cross                                      |
//| 短名=MaCross                                          |
//| 类=MA_Cross                                           |
//| 页=不需要                                             |
//| 参数=FastPeriod,int,13,fast MA周期                    |
//| 参数=FastMethod,ENUM_MA_METHOD,MODE_SMA,快速 MA 方法  |
//| 参数=SlowPeriod,int,21,slow MA 周期                   |
//| 参数=SlowMethod,ENUM_MA_METHOD,MODE_SMA,慢速 MA 方法  |
//+------------------------------------------------------------------
// 向导描述结束

模块处理函数已就绪,而且我们已于其中描述了下述内容:

  1. 于 MQL5 向导中显示的名称 - "Signals at the intersection of two moving averages" (两条移动平均线交叉处的信号)。
  2. 4 项配置交易信号的外部参数。
    • FastPeriod - 快速移动平均线的时段,默认值为 13。
    • FastMethod - 快速移动平均线的平滑类型,默认简单平滑。
    • SlowPeriod - 慢速移动平均线的时段,默认值为 21。
    • SlowMethod - 慢速移动平均线的平滑类型,默认简单平滑。

保存更改并编译。不得有任何错误。运行 MQL5 向导检查。您看,我们的模块现在已经可供选择,而且显示出了我们的所有参数!

庆祝一下,我们的交易信号模块现在看起来非常不错了!


3. 设置参数的方法

现在,该使用外部参数了。因为我们的交易模块由 MA_Cross 类表示,所以,必须将其参数作为 private 成员存储于同一类中。我们添加 4 行(等于参数数量)到类声明。我们已在处理函数内完成了参数描述,并知道下述信息:

class MA_Cross : public CExpertSignal
  {
private:
   //--- 配置模块参数
   int               m_period_fast;    // 快速 MA 周期
   int               m_period_slow;    // 慢速 MA 周期
   ENUM_MA_METHOD    m_method_fast;    // 快速 MA 平滑类型
   ENUM_MA_METHOD    m_method_slow;    // 慢速 MA 平滑类型

但是,模块外部参数的值怎样才能显示于我们 MA_Cross 类的相应成员中呢?全都非常简单,您只需在此类中声明同名的公共方法,也就是,向 public 区段添加 4 行:

class MA_Cross : public CExpertSignal
  {
private:
   //--- 配置模块参数
   int               m_period_fast;    // 快速 MA 周期
   int               m_period_slow;    // 慢速 MA 周期
   ENUM_MA_METHOD    m_method_fast;    // 快速 MA 平滑类型
   ENUM_MA_METHOD    m_method_slow;    // 慢速 MA 平滑类型

public:
   //--- 类构造函数
                     MA_Cross();
   //--- 类析构函数
                    ~MA_Cross();
   //--- 设置方法
   void              FastPeriod(int value)               { m_period_fast=value;        }
   void              FastMethod(ENUM_MA_METHOD value)    { m_method_fast=value;        }
   void              SlowPeriod(int value)               { m_period_slow=value;        }
   void              SlowMethod(ENUM_MA_METHOD value)    { m_method_slow=value;        }
   };

如您利用 MQL5 向导根据此模块生成一个“EA 交易”并于图表上运行,这 4 个方法就会在初始化该“EA 交易”时自动调用。因此,有一个简单法则:

模块中的参数创建法则 - 我们应在类中为已于处理函数中声明的每一个参数创建一个私有成员以存储其值,创建一个公共成员以为其设定一个值。方法名称必须匹配参数名称。

而且,最后一步是为我们的参数设置默认值,以供值设置方法未被调用的情况使用。每一个声明的变量或类成员都必须初始化。该技巧可避免许多难以发现的错误。

就自动初始化而言,最适合的就是类构造函数;创建对象时,它总是第一个被调用。至于默认值,我们会使用模块处理函数中编写的那些。

class MA_Cross : public CExpertSignal
  {
private:
   //--- 配置模块参数
   int               m_period_fast;    // 快速 MA 周期
   ENUM_MA_METHOD    m_method_fast;     // 快速 MA 平滑类型
   int               m_period_slow;    // 慢速 MA 周期
   ENUM_MA_METHOD    m_method_slow;     // 慢速 MA 平滑类型

public:
   //--- 类构造函数
                     MA_Cross(void);
   //--- 类析构函数
                    ~MA_Cross(void);
//+------------------------------------------------------------------
//| 构造函数                                                      |
//+------------------------------------------------------------------
MA_Cross::MA_Cross(void) : m_period_fast(13),          // 默认快速MA周期是 3
                           m_method_fast(MODE_SMA),    // 默认快速 MA 平滑类型
                           m_period_slow(21),          // 默认慢速 MA周期是21
                           m_method_slow(MODE_SMA)     // 默认慢速 MA 平滑类型
  {
  }

这里的类成员是利用初始化列表完成了初始化。

您可能发现了,我们并未使用移动平均线指标。我们找到了一条简单法则 - 模块处理函数中设定的参数有多少,类中实现此模块的方法和成员就要有多少。没什么复杂的!但是,不要忘了设定构造函数上的默认参数值。


4. 检查输入参数的正确性

我们已经创建了交易模块参数,编写了为其设定相应值的方法,现在则到了下一个重要的阶段 - 务必检查参数的正确性。本例中,我们必须检查移动平均段的各个时段,及其计算的平滑类型。为此目的,您要在类中编写您自己的 ValidationSettings() 方法。此方法于父类 CExpertBase 中定义,而且在其所有子类中,均强制性重新定义。

但是如果您根本不懂得面向对象编程,那就靠记吧 - 我们应于类中编写 ValidationSettings() 函数,该函数无需参数即返回 true 或 false。

class MA_Cross : public CExpertSignal
  {
...
   //--- 类构造函数
                     MA_Cross(void);
   //--- 类析构函数
                    ~MA_Cross(void);
   //--- 检查输入数据正确与否
   bool              ValidationSettings();
...
   };
//+------------------------------------------------------------------
//| 检查输入参数和返回true是否一切OK      |
//+------------------------------------------------------------------
bool MA_Cross:: ValidationSettings()
  {
   //--- 调用基类函数
   if(!CExpertSignal::ValidationSettings())  return(false);
   //--- 检查周期,柱线数量计算MA >=1
   if(m_period_fast<1 || m_period_slow<1)
     {
      PrintFormat("不正确的周期值!FastPeriod=%d, SlowPeriod=%d",
                  m_period_fast,m_period_slow);
      return false;
     }
//--- 慢速 MA 周期必须大于快速MA周期
   if(m_period_fast>m_period_slow)
     {
      PrintFormat("SlowPeriod=%d 必须大于 FastPeriod=%d!",
                  m_period_slow,m_period_fast);
      return false;
     }
//--- 快速 MA 平滑类型必须是四个枚举类型值之一
   if(m_method_fast!=MODE_SMA && m_method_fast!=MODE_EMA && m_method_fast!=MODE_SMMA && m_method_fast!=MODE_LWMA)
     {
      PrintFormat("无效平滑类型: 快速 MA!");
      return false;
     }
//--- 慢速 MA 平滑类型必须是四个枚举类型值之一
   if(m_method_slow!=MODE_SMA && m_method_slow!=MODE_EMA && m_method_slow!=MODE_SMMA && m_method_slow!=MODE_LWMA) 
     {
      PrintFormat("无效平滑类型: 慢速 MA!");
      return false;
     }
//--- 所有检查完成, 全部通过
   return true;
  }
您已经看到了,我们已于 MA_Cross 类的公共部分添加了 ValidationSettings() 方法声明,并以下述形式添加了方法主体
bool MA_Cross:: ValidationSettings()

首先是返回类型,然后是类名称,再然后是范围解析操作符 ::,所有这些之后,是上一次声明方法的名称。千万不要忘记:参数的名称和类型,必须与类方法的声明和描述相匹配。但是,如有此类错误,编译器会向您发生警告。

注意先调用基类方法,然后再检查输入参数。

//--- 调用基类函数
   if(!CExpertSignal::ValidationSettings())  return(false);
//--- 我们的代码检查参数

如您未添加此行,则生成的“EA 交易”将不能初始化我们的交易信号模块。


5. 我们的指标呢?

使用其参数的所有准备工作都完成了,到了使用指标的时候了。交易信号的每个模块都包含 InitIndicators() 方法,该方法会在您运行生成的“EA 交易”时自动调用。在此方法中,必须提供我们模块移动平均线的指标。

首先,于类中声明 InitIndicators() 方法并粘贴其草稿:

public:
   //--- 类构造函数
                     MA_Cross(void);
   //--- 类析构函数
                    ~MA_Cross(void);
   //--- 设置方法
   void              FastPeriod(int value)               { m_period_fast=value;        }
   void              FastMethod(ENUM_MA_METHOD value)    { m_method_fast=value;        }
   void              SlowPeriod(int value)               { m_period_slow=value;        }
   void              SlowMethod(ENUM_MA_METHOD value)    { m_method_slow=value;        }
   //--- 检查输入数据正确与否
   bool              ValidationSettings();
   //--- 创建信号模型的指标和时间帧
   bool              InitIndicators(CIndicators *indicators);
  };
...
//+------------------------------------------------------------------
//| 创建指标                                               |
//| 输入:  一组指标指针                  |
//| 输出:若成功true,否则false                       |
//+------------------------------------------------------------------
bool MA_Сross::InitIndicators(CIndicators* indicators)
  {
//--- 标准检查一组 NULL指标
   if(indicators==NULL)                           return(false);
//--- 初始化指标和时间帧及附加过滤器
   if(!CExpertSignal::InitIndicators(indicators)) return(false);
//--- 创建我们的 MA 指标
   ... 一些代码
//--- 抵达此处, 函数成功, 返回 true
   return(true);
  }

没什么复杂的了,我们要声明此方法,然后再像我们在 ValidationSettings() 方法中的做法一样简单创建方法主体。首先,不要忘记将类名称和操作符 ::插入函数定义中。我们有一个草稿,可将一个代码插入其中以创建移动平均线。我们来认真完成这一操作 - 我们为每个指标创建一个单独函数,如果成功,则会返回 true。此函数名称不限,但要体现其用途,那么,我们来调用 CreateFastMA() 和 CreateSlowMA() 函数吧。

protected:
   //--- 创建 MA 指标
   bool              CreateFastMA(CIndicators *indicators);
   bool              CreateSlowMA(CIndicators *indicators);
  };
//+------------------------------------------------------------------
//| 创建指标                                               |
//| 输入:  一组指标指针                  |
//| 输出:若成功true,否则false                       |
//+------------------------------------------------------------------
bool MA_Cross::InitIndicators(CIndicators *indicators)
  {
//--- 标准检查一组NULL指标
   if(indicators==NULL) return(false);
//--- 初始化指标和时间帧及附加过滤器
   if(!CExpertSignal::InitIndicators(indicators)) return(false);
//--- 创建我们的 MA 指标
   if(!CreateFastMA(indicators))                  return(false);
   if(!CreateSlowMA(indicators))                  return(false);
//--- 抵达此处, 函数成功, 返回 true
   return(true);
  }
//+------------------------------------------------------------------
//| 创建"快速MA"指标                                  |
//+------------------------------------------------------------------
bool MA_Cross::CreateFastMA(CIndicators *indicators)
  {
... 一些代码
//--- 抵达此处, 函数成功, 返回 true
   return(true);
  }
//+------------------------------------------------------------------
//| 创建"慢速MA"指标                                  |
//+------------------------------------------------------------------
bool MA_Cross::CreateSlowMA(CIndicators *indicators)
  {
... 一些代码
//--- 抵达此处, 函数成功, 返回 true
   return(true);
  }

就这些了。我们只需编写生成 MA 指标的代码,并通过某种方式将这些指标的处理函数集成到交易模块中,这样模块就能使用这些指标的值了。这也是将指向 CIndicators 型变量的指针作为一个参数传递的原因所在。下述内容载于相关文档中:

CIndicators 是一个收集时间序列与技术指标类实例的类。CIndicators 类可实现技术指标类实例的创建,提供它们的存储和管理(数据同步、处理及内存管理)。

也就是说,我们必须创建自己的指标,并将其放入这个集中。因为只有 CIndicator 形式的指标及其子类可存储于此集中,所以我们要利用这一点。我们会使用 CiCustom,即上面所述的子类。针对每一条移动平均线,我们都会在此类的私有部分中声明一个 CiCustom 类型对象:

class MA_Cross : public CExpertSignal
  {
private:
   CiCustom          m_fast_ma;            // 指标作为一个对象
   CiCustom          m_slow_ma;            // 指标作为一个对象
   //--- 配置模块参数
   int              m_period_fast;   // 快速 MA 周期
   ENUM_MA_METHOD    m_method_fast;    // 快速 MA 平滑类型
   int              m_period_slow;   // 慢速 MA 周期
   ENUM_MA_METHOD    m_method_slow;    // 慢速 MA 平滑类型

当然,您也可以创建将由 CIndicator 派生的自己的指标类,并实现搭配 MQL5 向导使用的所有必要方法。但在本例中我们想要展示的是,采用 CiCustom 交易信号的模块中,您要怎样使用其中的任何自定义指标。

代码呈现如下:

//+------------------------------------------------------------------
//| 创建"快速MA"指标                                  |
//+------------------------------------------------------------------
bool MA_Cross::CreateFastMA(CIndicators *indicators)
  {
//--- 检查指针
   if(indicators==NULL) return(false);
//--- 添加对象至集合
   if(!indicators.Add(GetPointer(m_fast_ma)))
     {
      printf(__FUNCTION__+": 添加快速 MA 对象错误");
      return(false);
     }
//--- 设置快速 MA 参数
   MqlParam parameters[4];
//---
   parameters[0].type=TYPE_STRING;
   parameters[0].string_value="Examples\\Custom Moving Average.ex5";
   parameters[1].type=TYPE_INT;
   parameters[1].integer_value=m_period_fast;      // 周期
   parameters[2].type=TYPE_INT;
   parameters[2].integer_value=0;                  // 平移
   parameters[3].type=TYPE_INT;
   parameters[3].integer_value=m_method_fast;      // 平均方法
//--- 对象初始化  
   if(!m_fast_ma.Create(m_symbol.Name(),m_period,IND_CUSTOM,4,parameters))
     {
      printf(__FUNCTION__+": 初始化快速 MA 对象错误");
      return(false);
     }
//--- 缓存区数量
   if(!m_fast_ma.NumBuffers(1)) return(false);
//--- 抵达此处, 函数成功, 返回 true
   return(true);
  }

在 CreateFastMA() 方法中,首先检查指标集的指针,然后再向此集添加一个快速 MA m_fast_ma 的指针。之后,声明 MqlParam 结构(专为存储自定义指标参数而设计),并填入相应值。

我们将源自标准终端交付包的自定义移动平均线用作自定义 MA 指标。指标的名称必须对应 data_folder/MQL5/Indicators/ 文件夹指明。因为源自标准包的 Custom Moving Average.mq5 位于 data_folder/MQL5/Indicators/Examples/,所以我们指定其包含 Examples (示例)文件夹的路径:

parameters[0].string_value="Examples\\Custom Moving Average.ex5";

只要查看此指标的代码,您就能知道所有的必要数据:

//--- 输入参数
input int            InpMAPeriod=13;       // 周期
input int            InpMAShift=0;         // 平移
input ENUM_MA_METHOD InpMAMethod=MODE_SMMA;  // 方法

此结构的值中包含类型-值对:

  1. 参数类型 - 字符串(传递指标的名称)
  2. 自定义指标可执行文件的名称 - "Custom Moving Averages.exe"
  3. 参数类型 - int (时段值)
  4. 移动平均线的时段
  5. 参数类型 - int (位移值)
  6. 柱中平均线的水平位移
  7. 参数类型 - int (枚举值为整数)
  8. 平均法

结构填写完毕后,通过所有必需参数的 Create() 方法初始化此指标:其计算所基的交易品种和时间表、源于 ENUM_INDICATOR 枚举的指标类型、指标参数的数量以及带参数值的 MqlParam 结构。最后一个是利用 NumBuffers() 方法指定指标缓冲区的数目。

创建慢速移动平均线的 CreateSlowMA() 方法很简单。使用模块中的自定义指标时,千万不要忘记由 MQL5 向导生成的“EA 交易”也会在测试仪中运行。所以,我们在文件开头加上 #property tester_indicator 属性 - 向测试仪传达所需指标的位置:

#include "..\ExpertSignal.mqh"   // CExpertSignal 类包含于文件 ExpertSignal
#property tester_indicator "Examples\\Custom Moving Average.ex5"

如果我们使用多个不同的指标,则应为每个指标添加此行。如此一来,我们已经完成了指标的添加。为更加方便,我们提供两种接收 MA 值的方法:

   //--- 检查输入数据正确与否
   bool              ValidationSettings(void);
   //--- 创建信号模型的指标和时间帧
   bool              InitIndicators(CIndicators *indicators);
   //--- 存取指标数据
   double            FastMA(const int index)             const { return(m_fast_ma.GetData(0,index)); }
   double            SlowMA(const int index)             const { return(m_slow_ma.GetData(0,index)); }

看到了吧,这些方法都非常简单,它们采用的是 SIndicator 父类的 GetData() 方法,此方法会返回指定位置指定指标缓冲区的值。

如果您需要使用标准包经典指标的类,请参考使用指标的类一节。我们已经做好准备,前往最后阶段。


6. 定义 LongCondition 与 ShortCondition 方法

让我们模块运行并生成交易信号的一切准备均已就绪。此功能由必须在 CExpertSignal 每个子类中都有描述的两种方法提供:

  • LongCondition() 会检查买入条件并返回从 0 到 100 的 Long (做多)信号强度。
  • ShortCondition() - 则会检查卖出条件并返回从 0 到 100 的 Short (做空信号)

如果此函数返回一个空值,则表明没有交易信号。如果有信号条件,则您可以估计信号的强度并返回任何一个不超过 100 的值。对信号强度的评估允许您根据多个模块和市场模块构建交易系统。更多详情请参见 MQL5 向导:新版本

因为我们要编写一个简单的交易信号模块,我们可商定买入和卖出信号等值 (100)。我们向类声明中添加必要的方法。

   ...
   bool              InitIndicators(CIndicators *indicators);
   //--- 访问指标数据
   double            FastMA(const int index)             const { return(m_fast_ma.GetData(0,index)); }
   double            SlowMA(const int index)             const { return(m_slow_ma.GetData(0,index)); }
   //--- 检查买、卖条件
   virtual int       LongCondition();
   virtual int       ShortCondition();

我们还要创建函数描述。也就是检查买入信号的方式(与卖出信号完全相同):

//+------------------------------------------------------------------
//| 返回买入信号强度                           |
//+------------------------------------------------------------------
int MA_Cross::LongCondition()
  {
   int signal=0;
//--- 操作条件 idx=0, 及已经建立的 idx=1
   int idx=StartIndex();
//--- 最后已建立柱线的 MAs 值
   double last_fast_value=FastMA(idx);
   double last_slow_value=SlowMA(idx);
//--- 最后已建立柱线的前一根柱线 MAs 值
   double prev_fast_value=FastMA(idx+1);
   double prev_slow_value=SlowMA(idx+1);
//---如果两根靠拢的柱线的快速 MA 从下往上与慢速 MA 交叉
   if((last_fast_value>last_slow_value) && (prev_fast_value<prev_slow_value))
     {
      signal=100; // 这是买信号
     }
//--- 返回信号值
   return(signal);
  }

注意我们已声明 idx 变量,通过父类 CExpertBase StartIndex() 函数返回的值就指派给它。如果“EA 交易”的设计针对所有订单号,则 StartIndex() 函数会返回 0,在这种情况下,分析开始于当前柱。如果“EA 交易”的设计针对开盘价,则 StartIndex() 会返回 1,且分析始于最后形成的柱。

默认情况下,StartIndex() 会返回 1,也就是说,利用 MQL5 向导生成的这个“EA 交易”只会在新柱开盘时运行,并会忽略当前柱形成期间进入的订单号。

如何激活此模式及如何使用,稍后将于点睛之笔中讲述。

模块随时可用,所以我们根据此模块利用 MQL5 向导创建一个交易机器人。


利用测试仪检查“EA 交易”

为测试我们模块的效能,我们根据它利用 MQL5 向导生成一个“EA 交易”并于图表上运行。出现的起始窗口中的 "Inputs" (输入)选项卡会包含 MA_Cross 模块的参数。

所有其它参数也都利用 MQL5 向导完成添加,同时基于选定的货币管理模块和头寸维护模块(跟踪止损)生成“EA 交易”。因此,我们只需缩写一个交易信号模块并接收一个即用解决方案。此即 MQL5 向导的主要使用优势所在!

现在,我们利用 MetaTrader 5 策略测试仪来测试该交易机器人。我们试着运行一次关键参数的快速优化。

在输入参数的这些设置中,完整优化需要 50 万次以上的计算次数。因此,我们选择快速优化(遗传算法),且加用 MQL5 云网络来加速优化。优化已于 10 分钟内完成,我们亦已获得结果。


正如您所看到的,与编写头寸管理服务逻辑、调试和寻找最佳算法所需的时间相比,利用 MQL5 创建一个交易机器人并优化输入参数所耗用的时间要少得多。


点睛之笔

您可以跳过此项,或是在您完全熟悉编写交易信号模块技巧的时候再回到这里。

如果您打开利用 MQL5 向导生成的“EA 交易”的源代码,您就会发现带有假值的全局变量 Expert_EveryTick。StartIndex() 函数会根据此变量返回其值。它会将“EA 交易”应运行的模式传达给“EA 交易”。

//+------------------------------------------------------------------
//|                                                 TestMA_Cross.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
//+------------------------------------------------------------------
//| 包含文件                                                          |
//+------------------------------------------------------------------
#include <Expert\Expert.mqh>
//--- 可用信号
#include <Expert\MySignals\MA_Cross.mqh>
//--- 可用移动止损
#include <Expert\Trailing\TrailingNone.mqh>
//--- 可用资金管理
#include <Expert\Money\MoneyFixedLot.mqh>
//+------------------------------------------------------------------
//| 输入                                                           |
//+------------------------------------------------------------------
//--- 交易程序输入参数
input string         Expert_Title             ="TestMA_Cross";  // 文档名
ulong               Expert_MagicNumber       =22655;          // 交易程序标识符
bool                  Expert_EveryTick             =false;          // EA是否工作于每个即时价格
//--- 主信号输入参数
input int            Signal_ThresholdOpen     =10;             // 信号开仓的阀值 [0...100]
input int            Signal_ThresholdClose    =10;             // 信号平仓的阀值 [0...100]

如果您将 Expert_EveryTick 设置为 true 并编译代码,则交易机器人会对每个进入的订单号进行分析,并根据当前不完整柱的值做出决定。只有当您了解其作用方式时才能如此。并非所有的交易系统都为于柱内工作而设计。

您还可以为 Expert_EveryTick 参数添加一个关键词 input ,然后您就有了一个新的“EA 交易”输入参数,而您可以在图表或测试仪中启动“EA 交易”时对其进行设置:

input bool          Expert_EveryTick         =false;          // EA是否工作于每个即时价格

现在,到了总结我们工作的时候了。


6 步创建一个交易信号模块

如果您已经掌握了 MQL5,则不再需要从头编写“EA 交易”。只需创建一个交易信号模块,并基于此模块,自动生成一个带有已启用跟踪和交易手数管理模块的交易机器人。而且,就算您不熟悉 OOP 且不想过多深究交易类的结构,您也可以简单 6 步通关:

  1. 创建一个新类,利用单独文件夹 MQL5/Include/MySignals/ 中的 MQL5 向导。我们的交易信号模块也将存储于此。
  2. 创建一个描述参数、其类型及默认值的模块处理函数
  3. 于类中声明模块参数,并添加构造函数中的初始化方法。
  4. 检查输入参数,且不要忘记调用 CExpertSignal 基类的 ValidationSettings()。
  5. 创建指标-对象,并添加一个预定义的初始化方法 InitIndicators()。
  6. 识别 LongCondition() 与 ShortCondition() 方法中交易信号的条件

每一步都很简单,所需的 MQL5 编程技能也很少。您只需编写自己的模块一次,接下来的指令及对任何交易理念的进一步验证用时都不会超过一小时,再也不需要夜以继日的编码和调试了。


从简单到复杂

记住:通过您利用 MQL5 向导创建的交易机器人实现的交易策略,与其使用的交易信号模块同样复杂。但是在您开始构建一个基于一套进入与退出规则的复杂交易系统之前,要将其分成几个简单的系统,并分别予以检查。

您可以基于简单模块,利用交易信号的现成模块创建复杂的交易策略,但这又需要另一篇文章来讲述了!

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

附加的文件 |
ma_cross.mqh (19.68 KB)
testma_cross.mq5 (12.91 KB)
面向对象法建立多时间表及多货币面板 面向对象法建立多时间表及多货币面板

本文讲述如何利用面向对象编程创建 MetaTrader 5 多时间表与多货币面板。主要目标在于建立一个可用于显示多种不同类型数据(比如价格、价格变动、指标值或自定义买/卖条件)、且无需修改面板本身代码的通用面板。

面向对象编程基础 面向对象编程基础

您无需了解什么是多态性、什么是封装性,以及使用面向对象编程(OOP)相关的一切内容……您可能只需要使用这些功能就好了。本文中涵盖了 OOP 的基础知识,且带有亲身实践示例。

MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易 MQL5 Cookbook: 使用不限数量的参数开发多币种EA交易

在本文中,我们将创建一种模式,它会使用一系列参数为交易系统作优化,而且允许不加数量限制的参数。交易品种的列表将在标准文本文件(*.txt)中创建,每个交易品种的输入参数也将存储于文件中。使用这种方法,我们将能够免除终端中对EA输入参数个数的限制。

MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表 MQL5 Cookbook: 把交易历史写入文件以及为每个交易品种在Excel中创建余额图表

当在各种论坛做沟通时,我经常使用我自己的测试结果作为例子,这些结果显示为Microsoft Excel中的图表截图。很多时候都有人问我这些图表是怎样创建的,最终,我现在有时间在本文中解释其中的全部了。