![MQL5 向导:如何创建交易信号模块](https://c.mql5.com/i/articles/overlay.png)
MQL5 向导:如何创建交易信号模块
简介
MetaTrader 5 提供了一款强大的工具用于快速检验交易理念。这就是 MQL5 向导的交易策略生成器。使用 MQL5 向导自动创建 EA 交易代码的做法已在《MQL5 向导:无需编程即可创建 EA 交易》一文中进行了介绍。由于代码生成系统的开放性,您能够将自己的交易信号类、资金管理系统和追踪模块添加至标准类、系统和模块。
本文介绍了交易信号模块的编写原则,以便您在通过 MQL5 向导创建 EA 交易时能用到这些原则。
通过 MQL5 向导创建的 EA 交易基于四大支柱,即四个基本类:
图 1. CExpert 基类的结构
CExpert 类(或其子类)是交易机器人的主要“引擎”。CExpert 的实例包含下列每个类的一个副本:CExpertSignal、CExpertMoney 和 CExpertTrailing(或其子类):
- CExpertSignal 构成了交易信号生成器的基础。CExpert 中包含的一个 CExpertSignal 派生类实例提供了 EA 交易以及市场准入可能性、准入水平和基于内置算法下达保护性订单方面的相关信息。交易操作执行的最终决策由 EA 交易作出。
- CExpertMoney 构成了资金和风险管理系统的基础。CExpertMoney 派生类的一个实例计算了持仓和下挂单的交易量。交易量的最终决策由 EA 交易作出。
- CExpertTrailing 构成了开仓支持模块的基础。CExpertTrailing 派生类的一个实例通知了 EA 交易修改持仓保护性订单的必要性。订单修改的最终决策由 EA 交易作出。
此外,CExpert 类的成员也是以下类的实例:
- CExpertTrade(用于交易)
- CIndicator(用于控制 EA 交易中涉及的指标和时间序列)
- CSymbolInfo(用于获取工具的信息)
- CAccountInfo(用于获取交易账户状态的相关信息)
- CPositionInfo(用于获取持仓的相关信息)
- COrderInfo (用于获取挂单的相关信息)
在下文中,EA 交易指的是 CExpert 或其子类的实例。
CExpert 及其使用的更多详情将在各个章节中进行介绍。
1. CExpertSignal 基类
CExpertSignal 构成了交易信号生成器的基础。CExpertSignal 采用了一套公共虚拟方法,用于同“外界”进行通信:
初始化 | 说明 |
虚拟 Init | 类实例的初始化可实现模块数据和 EA 交易数据的同步 |
参数设定验证 | |
创建并初始化操作交易信号生成器所需的所有指标和时间序列 | |
开仓/反向持仓/平仓信号 |
|
生成买入持仓开仓信号,定义准入水平并下达保护性订单 | |
生成卖出持仓开仓信号,定义准入水平并下达保护性订单 | |
生成买入持仓平仓信号并定义退出水平 | |
生成卖出持仓平仓信号并定义退出水平 | |
生成买入持仓反向信号,定义反向水平并下达保护性订单 | |
生成卖出持仓反向信号,定义反向水平并下达保护性订单 | |
管理挂单 |
|
生成买入挂单修改信号并定义新订单价格 | |
生成卖出挂单修改信号并定义新订单价格 |
方法说明
1.1. 初始化方法:
Init() 方法在类实例添加至 EA 交易后会立即自动调用。无需覆盖该方法。
virtual bool Init(CSymbolInfo* symbol, ENUM_TIMEFRAMES period, double adjusted_point);
ValidationSettings() 方法在所有参数设定后从 EA 交易中直接调用。如果存在任何设定参数,您都必须覆盖该方法。
virtual bool ValidationSettings();
如果所有选项都有效(可用),则覆盖方法必须返回 true。如果至少有一个参数不正确(导致无法进一步操作),则必须返回 false。
由于基类 CExpertSignal 不具备可调参数,因此基类方法会始终返回 ture 而不执行任何检查。
InitIndicators () 方法用于创建和初始化所有必要的指标和时间序列。在设置完所有参数且成功验证其正确性后,该方法会从 EA 交易中调用。 如果交易信号生成器使用了至少一个指标或时间序列,则应覆盖该方法。
virtual bool InitIndicators(CIndicators* indicators);
应通过标准库的适当类来使用指标和/或时间序列。所有指标和/或时间序列的指针均应添加至 EA 交易的指标集(指向其的指针作为参数传递)。
如果对指标和/或时间序列的所有操作均已成功(表明其适合使用),则覆盖方法必须返回 true。如果对指标和/或时间序列的操作至少有一次失败(导致无法进一步操作),则覆盖方法必须返回 false。
由于基类 CExpertSignal 不使用指标或时间序列,因此基类方法始终返回 ture 而不执行任何操作。
1.2. 检查开仓信号的方法:
CheckOpenLong() 方法会生成买入持仓开仓信号,同时定义准入水平和保护性订单下达水平。该方法通过 EA 交易调用,以确定是否有必要建立买入持仓。如果希望生成买入持仓开仓信号,则必须覆盖该方法。
virtual bool CheckOpenLong(double& price, double& sl, double& tp, datetime& expiration);
使用该方法时必须实现检查买入持仓开仓条件的算法。如果满足条件,则必须为变量 price、sl、tp 和 expiration(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成买入持仓开仓信号,因此基类方法会始终返回 false。
CheckOpenShort() 方法会生成卖出持仓开仓信号,同时定义准入水平和保护性订单下达水平。该方法通过 EA 交易调用,以确定是否有必要建立卖出持仓。如果希望生成卖出持仓开仓信号,则必须覆盖该方法。
virtual bool CheckOpenShort(double& price, double& sl, double& tp, datetime& expiration);
该方法必须实现检查卖出持仓开仓条件的算法。如果满足条件,则必须为变量 price、sl、tp 和 expiration(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成卖出持仓开仓信号,因此基类方法会始终返回 false。
1.3. 检查平仓信号的方法:
CheckCloseLong() 方法会生成买入持仓平仓信号,同时定义退出水平。它通过 EA 交易调用,以确定是否有必要平买入持仓。如果希望生成买入持仓平仓信号,则必须覆盖该方法。
virtual bool CheckCloseLong(double& price);
该方法必须实现检查买入持仓平仓条件的算法。如果满足条件,则必须为变量 price(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成买入持仓平仓信号,因此基类方法会始终返回 false。
CheckCloseShort() 方法会生成卖出持仓平仓信号,同时定义退出水平。该方法通过 EA 交易调用,以确定是否有必要平卖出持仓。如果希望生成卖出持仓平仓信号,则必须覆盖该方法。
virtual bool CheckCloseShort(double& price);
该方法必须实现检查卖出持仓平仓条件的算法。如果满足条件,则必须为变量 price(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成卖出持仓平仓信号,因此基类方法会始终返回 false。
1.4. 检查反向持仓信号的方法:
CheckReverseLong 方法会生成买入持仓反向信号,同时定义反向水平和保护性订单下达水平。该方法通过 EA 交易调用,以确定是否有必要反向买入持仓。如果希望生成买入持仓反向信号,则必须覆盖该方法。
virtual bool CheckReverseLong(double& price, double& sl, double& tp, datetime& expiration);
该方法必须实现检查买入持仓反向条件的算法。如果满足条件,则必须为变量 price、sl、tp 和 expiration(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
CExpertSignal 基类中实现了以下算法,用于生成买入持仓反向信号:
- 检查信号以平买入持仓。
- 检查信号以建立卖出持仓。
- 如果两个信号均有效(满足条件)且收盘价和开盘价相匹配,则变量 price、sl、tp 和 expiration(对其的引用作为参数传递)被赋予相应的值且该方法返回 ture。
CheckReverseShort 方法会生成卖出持仓反向信号,同时定义反向水平和保护性订单下达水平。该方法通过 EA 交易调用,以确定是否有必要反向卖出持仓。如果希望根据一定的算法(该算法不同于基类中实现的算法)生成买入持仓反向信号,则必须覆盖该方法。
virtual bool CheckReverseShort(double& price, double& sl, double& tp, datetime& expiration);
该方法必须实现检查卖出持仓反向条件的算法。如果满足条件,则必须为变量 price、sl、tp 和 expiration(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
CExpertSignal 基类中实现了以下算法,用于生成卖出持仓反向信号:
- 检查信号以平卖出持仓。
- 检查信号以建立买入持仓。
- 如果两个信号均有效(满足条件)且收盘价和开盘价相匹配,则变量 price、sl、tp 和 expiration(对其的引用作为参数传递)被赋予相应的值且该方法返回 ture。
如果不满足条件,则该方法返回 false。
1.5. 检查挂单修改信号的算法:
CheckTrailingOrderLong() 方法会生成买入挂单修改信号,同时定义新订单价格。该方法通过 EA 交易调用,以确定是否有必要买入挂单。如果希望生成买入挂单修改信号,则必须覆盖该方法。
virtual bool CheckTrailingOrderLong(COrderInfo* order, double& price)
该方法必须实现检查买入挂单修改条件的算法。如果满足条件,则必须为变量 price(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成买入挂单修改信号,因此基类方法会始终返回 false。
CheckTrailingOrderShort() 方法会生成卖出挂单修改信号,同时定义新订单价格。该方法通过 EA 交易调用,以确定是否有必要修改卖出挂单。如果希望生成卖出挂单修改信号,则必须覆盖该方法。
virtual bool CheckTrailingOrderShort(COrderInfo* order, double& price)
该方法必须实现检查卖出挂单修改条件的算法。如果满足条件,则必须为变量 price(对其的引用作为参数传递)赋予相应的值,且该方法应返回 true。如果不满足条件,则该方法必须返回 false。
基类 CExpertSignal 没有内置的算法用于生成卖出挂单修改信号,因此基类方法始终返回 false。
2. 开发自己的交易信号生成器
了解 CExpertSignal 基类的结构后,现在您就可开始创建自己的交易信号生成器了!
如上所述,CExpertSignal 类是一套公共虚拟方法,EA 交易通过其了解交易信号生成器在有关市场准入方向方面的“意见”。
因此,我们的主要目标是创建自己的交易信号生成器类,使其派生于 CExpertSignal 类并覆盖相应的虚拟方法,同时实现所需算法。
我们的第二个问题(具有同等重要性)是让我们的类对于 MQL5 向导呈“可视”状态。先说最重要的吧。
2.1. 创建交易信号生成器类
我们开始吧。
首先创建(例如,使用同一 MQL5 向导创建)一个具有 mqh 扩展名的包含文件。
在 File(文件)菜单中选择 Create(创建)(或按 Ctrl+N 组合键),并指明创建一个包含文件:
图 2. 使用 MQL5 向导创建一个包含文件
应该注意的是,为方便 MQL5 向导以后将文件识别为信号生成器,应在文件夹 Include\Expert\Signal\ 中创建该文件。
为了避免与标准库发生冲突,创建自己的文件夹 Include\Expert\Signal\MySignals,然后在其中创建文件 SampleSignal.mqh,并在 MQL5 向导中指定这些参数:
图 3. 设置包含文件的位置
MQL5 向导的运行结果显示如下情形:
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| defines | //+------------------------------------------------------------------+ // #define MacrosHello "Hello, world!" // #define MacrosYear 2010 //+------------------------------------------------------------------+ //| DLL imports | //+------------------------------------------------------------------+ // #import "user32.dll" // int SendMessageA(int hWnd,int Msg,int wParam,int lParam); // #import "my_expert.dll" // int ExpertRecalculate(int wParam,int lParam); // #import //+------------------------------------------------------------------+ //| EX5 imports | //+------------------------------------------------------------------+ // #import "stdlib.ex5" // string ErrorDescription(int error_code); // #import //+------------------------------------------------------------------+
接下来完全是“手工”操作。删除不必要部分,并加入所需部分(即标准库的包含文件 ExpertSignal.mqh 和目前为空的类说明)。
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| The CSampleSignal class. | //| Purpose: Class of trading signal generator. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { }; //+------------------------------------------------------------------+
现在有必要选择算法了。
作为交易信号生成器的基础,我们采用了广泛应用的模型“价格穿越移动平均线”。但我们增加了一个假设:“在穿越移动平均线后,价格会返回,直到那时才进入正确方向。”这反映在我们的文件中。
通常来说,在编写代码时不要省略注释。在一段时间后重读仔细注释过的代码是很惬意的事。
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| Class CSampleSignal. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { }; //+------------------------------------------------------------------+
现在,我们来详细说明确定所生成的交易信号需要用到哪些数据。在本例中,这些数据包括上一个柱的开盘价和收盘价,以及同一个柱上移动平均线的值。
为了访问这些数据,我们使用了标准库类 CiOpen、CiClose 和 CiMA。稍后我们将探讨指标和时间序列。
与此同时,让我们为生成器定义一个设置列表吧。首先,我们需要设置移动平均线。这些参数包括周期、沿时间轴偏移、均值法以及均值对象。其次,我们需要设定准入水平和保护性订单下达水平,以及挂单的生命周期,因为我们将用到挂单。
生成器的所有设置都将存储在类的受保护数据成员中。将通过相应的公共方法实现对设置的访问。
让我们将这些更改包含在文件中:
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> //+------------------------------------------------------------------+ //| The CSampleSignal class. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { protected: //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } }; //+------------------------------------------------------------------+
由于我们使用的是受保护数据成员,需要添加一个类构造函数,以便通过使用默认值来初始化这些数据。
要检查这些参数,可根据基类说明来覆盖虚拟方法 ValidationSettings。
类的说明:
class CSampleSignal : public CExpertSignal { protected: //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Methods to validate the parameters virtual bool ValidationSettings(); };
ValidationSettings() 方法的实现:
//+------------------------------------------------------------------+ //| Validation of the setup parameters. | //| INPUT: No. | //| OUTPUT: true if the settings are correct, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::ValidationSettings() { //--- Validation of parameters if(m_period_ma<=0) { printf(__FUNCTION__+": the MA period must be greater than zero"); return(false); } //--- ok return(true); } //+------------------------------------------------------------------+
至此我们已完成了大部分准备工作,我们将以更多篇幅来讨论指标和时间序列。
指标和时间序列是决策的主要信息源(当然,您也可以通过猜硬币或观月相的方式进行决策,但这些方法难以做到规范化)。
正如上文所述,进行决策时需要以下信息:上一个柱的开盘价和收盘价,以及同一个柱的移动平均线值。
为了访问这些数据,我们将使用标准库的以下类:
读者也许会问:“为什么要使用封装在类中的指标或时间序列,而这样做只是为了获得几个数字?”
这其中包含了隐含的意义,我们现在来将其揭晓。
如何使用指标或时间序列的数据?
首先,我们需要创建一个指标。
其次,我们需要将必要数量的数据复制到中间缓冲区。
再次,我们需要检查复制是否已完成。
只有经过这些步骤,您才能使用数据。
通过使用标准库的类,您无需再创建指标、关注中间缓冲区的可用性以及数据加载或句柄释放。相应类的对象将为您执行上述这些步骤。所需的一切指标都将在初始化阶段通过信号生成器生成,且所有指标都将获得必要的临时缓冲区。此外,一旦我们在集(特殊类对象)中添加了指标或时间序列对象,您便无需再关注数据的相关性(EA 交易将自动更新数据)。
让我们根据基类说明来覆盖虚拟方法 InitIndicators 吧。
类的说明:
class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values om the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } };
InitIndicators、InitMA、InitOpen、InitClose 方法的实现:
//+------------------------------------------------------------------+ //| Initialization of indicators and timeseries. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitIndicators(CIndicators* indicators) { //--- Validation of the pointer if(indicators==NULL) return(false); //--- Initialization of the moving average if(!InitMA(indicators)) return(false); //--- Initialization of the timeseries of open prices if(!InitOpen(indicators)) return(false); //--- Initialization of the timeseries of close prices if(!InitClose(indicators)) return(false); //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the moving average | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitMA(CIndicators* indicators) { //--- Initialization of the MA object if(!m_MA.Create(m_symbol.Name(),m_period,m_period_ma,m_shift_ma,m_method_ma,m_applied_ma)) { printf(__FUNCTION__+": object initialization error"); return(false); } m_MA.BufferResize(3+m_shift_ma); //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_MA))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of open prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitOpen(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_open.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_open))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of close prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitClose(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_close.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_close))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+
所有准备工作均已就绪。如您所见,我们的类有了显著增长。
我们现在已准备好生成交易信号了。
图 4. 价格穿越移动平均线的交易信号
让我们再仔细考虑下这些算法。
1. 当上一个柱满足以下条件时,会出现买入信号:
- 柱的开盘价低于移动平均线值;
- 柱的收盘价高于移动平均线值;
- 移动平均线渐增。
在这种情况下,我们提出买入挂单,其参数可通过设置进行定义。为此,我们覆盖了虚拟方法 CheckOpenLong 并赋予其相应功能。
2. 当上一个柱满足以下条件时,会出现卖出信号:
- 柱的开盘价高于移动平均线值;
- 柱的收盘价低于移动平均线值;
- 移动平均线正在逐渐下降。
在这种情况下,我们提出卖出挂单,其参数可通过设置进行定义。为此,我们覆盖了虚拟方法 CheckOpenShort 并赋予其相应功能。
3. 我们将不会生成平仓信号。平仓通过使用止损/获利订单完成。
因此,我们不会覆盖 CheckCloseLong 和 CheckCloseShort 方法。
4. 我们建议按照设置指定的“距离”沿移动平均线修改挂单。
为此,我们覆盖了虚拟方法 CheckTrailingOrderLong 和 CheckTrailingOrderShort 并赋予其相应功能。
类的说明:
class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values of the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //--- Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); //--- Methods to generate signals to enter the market virtual bool CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration); virtual bool CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration); //--- Methods to generate signals of pending order modification virtual bool CheckTrailingOrderLong(COrderInfo* order,double& price); virtual bool CheckTrailingOrderShort(COrderInfo* order,double& price); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } };
CheckOpenLong、CheckOpenShort、CheckTrailingOrderLong、CheckTrailingOrderShort 方法的实现:
//+------------------------------------------------------------------+ //| Check whether a Buy condition is fulfilled | //| INPUT: price - variable for open price | //| sl - variable for stop loss price, | //| tp - variable for take profit price | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double spread=m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); //--- Checking the condition if(Open(1)<ma && Close(1)>ma && ma>MA(2)) { price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); sl =m_symbol.NormalizePrice(price-m_stop_loss*unit); tp =m_symbol.NormalizePrice(price+m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether a Sell condition is fulfilled. | //| INPUT: price - variable for open price, | //| sl - variable for stop loss, | //| tp - variable for take profit | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); //--- Checking the condition if(Open(1)>ma && Close(1)<ma && ma<MA(2)) { price=m_symbol.NormalizePrice(ma+m_limit*unit); sl =m_symbol.NormalizePrice(price+m_stop_loss*unit); tp =m_symbol.NormalizePrice(price-m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Buy order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double spread =m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Sell order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma+m_limit*unit); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+
至此,我们已解决了第一个问题。上述代码是交易信号生成器的类的源代码,可满足我们主要任务的要求。
2.2. 为 MQL5 向导准备所创建交易信号类的说明
我们现在开始解决第二个问题。我们的信号应能被 MQL5 向导的交易策略生成器所识别。
我们已完成了第一个必要条件:我们将文件放置在 MQL5 向导能够找到的位置。但这还不够。MQL5 向导不仅要能找到文件,还要能识别它。 为此,我们必须根据 MQL5 向导的要求将类描述符添加至原始文本。
类描述符是根据一定规则构成的注释块。
我们来看一下这些规则:
1. 注释块应以下面的代码行开头:
// wizard description start //+------------------------------------------------------------------+ //| Description of the class |
2. 下一行是一个文本描述符(我们会在 MQL5 向导中选择信号时见到),格式为 "//| Title=<Text> |"。如果代码文本过多而无法显示在一行,您可以在后面再添加一行代码(但不能超过此数)。
在本例中,即下述代码行:
//| Title=Signal on the crossing of a price and the MA | //| entering on its back movement |
3. 接下来是以格式 "//| Type=<Type> |" 指定类的类型的代码行。<Type> 字段必须具有信号值(除了信号,MQL5 向导也能识别其他类的类型)。
输入:
//| Type=Signal |
4. 接下来一行呈 "//| Name=<Name> |" 格式的代码是信号的简称(MQL5 向导用其来生成 EA 交易全局变量的名称)。
我们得到下述内容:
//| Name=Sample |
5. 类的名称是说明中的一个重要要素。在呈 "//| Class=<ClassNameа> |" 格式的代码行中,<ClassName> 参数必须与类的名称相匹配:
//| Class=CSampleSignal |
6. 我们不会在此行填入任何内容,但该行必须存在(这是到语言参考部分的链接):
//| Page= |
7. 此外还有关于信号设置参数的说明。
这是一组代码行(行数等于参数的个数)。
每一行的格式均为 "//| Parameter=<NameOfMethod>,<TypeOfParameter>,<DefaultValue> |"。
下面是我们的参数集:
//| Parameter=PeriodMA,int,12 | //| Parameter=ShiftMA,int,0 | //| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA | //| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE | //| Parameter=Limit,double,0.0 | //| Parameter=StopLoss,double,50.0 | //| Parameter=TakeProfit,double,50.0 | //| Parameter=Expiration,int,10 |
8. 注释块应以下面的代码行结束:
//+------------------------------------------------------------------+ // wizard description end
让我们将描述符添加到源代码。
//+------------------------------------------------------------------+ //| SampleSignal.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| include files | //+------------------------------------------------------------------+ #include <Expert\ExpertSignal.mqh> // wizard description start //+------------------------------------------------------------------+ //| Description of the class | //| Title=Signal on crossing of the price and the MA | //| entering on the back movement | //| Type=Signal | //| Name=Sample | //| Class=CSampleSignal | //| Page= | //| Parameter=PeriodMA,int,12 | //| Parameter=ShiftMA,int,0 | //| Parameter=MethodMA,ENUM_MA_METHOD,MODE_EMA | //| Parameter=AppliedMA,ENUM_APPLIED_PRICE,PRICE_CLOSE | //| Parameter=Limit,double,0.0 | //| Parameter=StopLoss,double,50.0 | //| Parameter=TakeProfit,double,50.0 | //| Parameter=Expiration,int,10 | //+------------------------------------------------------------------+ // wizard description end //+------------------------------------------------------------------+ //| CSampleSignal class. | //| Purpose: Class of trading signal generator when price | //| crosses moving average, | //| entering on the subsequent back movement. | //| It is derived from the CExpertSignal class. | //+------------------------------------------------------------------+ class CSampleSignal : public CExpertSignal { protected: CiMA m_MA; // object to access the values of the moving average CiOpen m_open; // object to access the bar open prices CiClose m_close; // object to access the bar close prices //--- Setup parameters int m_period_ma; // averaging period of the MA int m_shift_ma; // shift of the MA along the time axis ENUM_MA_METHOD m_method_ma; // averaging method of the MA ENUM_APPLIED_PRICE m_applied_ma; // averaging object of the MA double m_limit; // level to place a pending order relative to the MA double m_stop_loss; // level to place a stop loss order relative to the open price double m_take_profit; // level to place a take profit order relative to the open price int m_expiration; // lifetime of a pending order in bars public: CSampleSignal(); //--- Methods to set the parameters void PeriodMA(int value) { m_period_ma=value; } void ShiftMA(int value) { m_shift_ma=value; } void MethodMA(ENUM_MA_METHOD value) { m_method_ma=value; } void AppliedMA(ENUM_APPLIED_PRICE value) { m_applied_ma=value; } void Limit(double value) { m_limit=value; } void StopLoss(double value) { m_stop_loss=value; } void TakeProfit(double value) { m_take_profit=value; } void Expiration(int value) { m_expiration=value; } //---Method to validate the parameters virtual bool ValidationSettings(); //--- Method to validate the parameters virtual bool InitIndicators(CIndicators* indicators); //--- Methods to generate signals to enter the market virtual bool CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration); virtual bool CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration); //--- Methods to generate signals of pending order modification virtual bool CheckTrailingOrderLong(COrderInfo* order,double& price); virtual bool CheckTrailingOrderShort(COrderInfo* order,double& price); protected: //--- Object initialization method bool InitMA(CIndicators* indicators); bool InitOpen(CIndicators* indicators); bool InitClose(CIndicators* indicators); //--- Methods to access object data double MA(int index) { return(m_MA.Main(index)); } double Open(int index) { return(m_open.GetData(index)); } double Close(int index) { return(m_close.GetData(index)); } }; //+------------------------------------------------------------------+ //| CSampleSignal Constructor. | //| INPUT: No. | //| OUTPUT: No. | //| REMARK: No. | //+------------------------------------------------------------------+ void CSampleSignal::CSampleSignal() { //--- Setting the default values m_period_ma =12; m_shift_ma =0; m_method_ma =MODE_EMA; m_applied_ma =PRICE_CLOSE; m_limit =0.0; m_stop_loss =50.0; m_take_profit=50.0; m_expiration =10; } //+------------------------------------------------------------------+ //| Validation of parameters. | //| INPUT: No. | //| OUTPUT: true if the settings are correct, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::ValidationSettings() { //--- Validation of parameters if(m_period_ma<=0) { printf(__FUNCTION__+": the MA period must be greater than zero"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of indicators and timeseries. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitIndicators(CIndicators* indicators) { //--- Validation of the pointer if(indicators==NULL) return(false); //--- Initialization of the moving average if(!InitMA(indicators)) return(false); //--- Initialization of the timeseries of open prices if(!InitOpen(indicators)) return(false); //--- Initialization of the timeseries of close prices if(!InitClose(indicators)) return(false); //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the moving average | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitMA(CIndicators* indicators) { //--- Initialization of the MA object if(!m_MA.Create(m_symbol.Name(),m_period,m_period_ma,m_shift_ma,m_method_ma,m_applied_ma)) { printf(__FUNCTION__+": object initialization error"); return(false); } m_MA.BufferResize(3+m_shift_ma); //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_MA))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of open prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitOpen(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_open.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_open))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Initialization of the timeseries of close prices. | //| INPUT: indicators - pointer to the object - collection of | //| indicators and timeseries. | //| OUTPUT: true in case of success, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::InitClose(CIndicators* indicators) { //--- Initialization of the timeseries object if(!m_close.Create(m_symbol.Name(),m_period)) { printf(__FUNCTION__+": object initialization error"); return(false); } //--- Adding an object to the collection if(!indicators.Add(GetPointer(m_close))) { printf(__FUNCTION__+": object adding error"); return(false); } //--- Successful completion return(true); } //+------------------------------------------------------------------+ //| Check whether a Buy condition is fulfilled | //| INPUT: price - variable for open price | //| sl - variable for stop loss price, | //| tp - variable for take profit price | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenLong(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double spread=m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); //--- Checking the condition if(Open(1)<ma && Close(1)>ma && ma>MA(2)) { price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); sl =m_symbol.NormalizePrice(price-m_stop_loss*unit); tp =m_symbol.NormalizePrice(price+m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether a Sell condition is fulfilled. | //| INPUT: price - variable for open price, | //| sl - variable for stop loss, | //| tp - variable for take profit | //| expiration - variable for expiration time. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckOpenShort(double& price,double& sl,double& tp,datetime& expiration) { //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); //--- Checking the condition if(Open(1)>ma && Close(1)<ma && ma<MA(2)) { price=m_symbol.NormalizePrice(ma+m_limit*unit); sl =m_symbol.NormalizePrice(price+m_stop_loss*unit); tp =m_symbol.NormalizePrice(price-m_take_profit*unit); expiration+=m_expiration*PeriodSeconds(m_period); //--- Condition is fulfilled return(true); } //--- Condition is not fulfilled return(false); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Buy order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderLong(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double spread =m_symbol.Ask()-m_symbol.Bid(); double ma =MA(1); double unit =PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma-m_limit*unit+spread); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+ //| Check whether the condition of modification | //| of a Sell order is fulfilled. | //| INPUT: order - pointer at the object-order, | //| price - a variable for the new open price. | //| OUTPUT: true if the condition is fulfilled, otherwise false. | //| REMARK: No. | //+------------------------------------------------------------------+ bool CSampleSignal::CheckTrailingOrderShort(COrderInfo* order,double& price) { //--- Checking the pointer if(order==NULL) return(false); //--- Preparing the data double ma =MA(1); double unit=PriceLevelUnit(); double new_price=m_symbol.NormalizePrice(ma+m_limit*unit); //--- Checking the condition if(order.PriceOpen()==new_price) return(false); price=new_price; //--- Condition is fulfilled return(true); } //+------------------------------------------------------------------+
以上便是全部内容。信号已经可以使用了。
为使 MQL5 向导的交易策略生成器能够使用我们的信号,我们应重启 MetaEditor(MQL5 向导仅在启动时才会扫描文件夹 Include\Expert)。
重启 MetaEditor 后,创建的交易信号模块就可以在 MQL5 向导中使用了:
图 5. MQL5 向导中创建的交易信号生成器
现在可以使用交易信号生成器参数说明部分指定的输入参数了:
图 6. MQL5 向导中创建的交易信号生成器的输入参数
可使用 MetaTrader 5 终端的策略测试程序找出所实现交易策略的输入参数的最佳值。
总结
MQL5 向导的交易策略生成器极大简化了交易理念的检验过程。所生成 EA 交易的代码基于标准库的交易策略类,用于实现某些交易信号类、资金和风险管理类以及持仓支持类。
本文介绍了如何通过在穿越价格和移动平均线时构成信号这一方法来编写自己的交易信号类,以及如何将其包含在 MQL5 向导的交易策略生成器中,还介绍了用于 MQL5 向导的生成类的说明的结构和格式。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/226
![MQL5 向导:如何创建风险和资金管理模块](https://c.mql5.com/2/0/CMoney_MQL5.png)
![使用计量经济学方法分析图表](https://c.mql5.com/2/0/econometrics.png)
![MQL5 向导:如何创建未平仓位的追踪模块](https://c.mql5.com/2/0/MQL5_Wizard_Trailing_Stop__1.png)
![在视图内/外绘制通道](https://c.mql5.com/2/0/channels_MQL5.png)