如果您想禁止一些非常糟糕的东西, 那么这是允许的。 俄文谚语
简单性与可靠性对比
回到 2005 年, 在 新发布的 MetaTrader 4 中, 简陋的 MQL-II 脚本语言被 MQL4 所替代。如今看来很有趣, 很多交易者都对所遇的新类 C 语言抱有敌意。有许多针对 MetaQuotes 软件公司的激烈辩论和指责。批评者称该语言非常复杂, 不可能掌握它。
如今, 12 年之后, 这种抱怨看来很奇怪, 但历史总会重演。就像在 2005 年一样, 一些交易者宣称与 MQL4 相比, MQL5 对于学习和开发策略而言非常复杂。这意味着开发交易机器人的总体水平在过去几年中大幅增长, 这得益于开发人员不畏惧向算法交易者提供更强力的 C++ 语言工具。新的 MQL5 允许程序员最大限度地检查所有操作的结果 (这对于处理交易尤为重要) 并按需使用内存。在提高到 MQL5 级别之前, 老旧的 MQL4 所能提供的这类机会少得多。此外, 语法本身并不严格。
我相信, 关于 MQL5 复杂性的争论也会在短时间内被遗忘。但是由于许多交易者仍然对老式 MQL4 感到怀旧, 我们尝试展现熟悉的 MQL4 函数以 MQL5 实现是什么样的。
如果您刚刚切换到 MQL5, 那么本文将会很有用处。首先, 以正常的 MQL4 风格访问指标数据和序列已经完成。其次, 以 MQL5 实现这些整体上更简单。所有函数都尽可能地清晰, 并且非常适合单步调试。
1. 可以在 MQL5 中使用 MQL4 风格的指标吗?
处理指标的主要区别在于, 在 MQL4 中, 指标数据检索字符串 实际上是指标创建命令 ( iMACD(NULL,0,12,26,9,PRICE_CLOSE ) 与指标缓存区 ( MODE_MAIN ) 及其索引值 ( 1 ) 的数据请求相结合。
//+------------------------------------------------------------------+ //| iMACd.mq4 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.00" #property strict //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); } //+------------------------------------------------------------------+
结果就是, 单条字符串只能代表单个步骤。
在 MQL5 中, 这段代码的等价物包含若干步骤:
- 声明要存储指标句柄的变量;
- 创建并检查指标句柄;
- 独立函数可提供指标值。
//+------------------------------------------------------------------+ //| iMACD.mq5 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" int handle_iMACD; // 用于存储 iMACD 指标句柄的变量 //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- 创建指标 iMACD 的句柄 handle_iMACD=iMACD(Symbol(),Period(),12,26,9,PRICE_CLOSE); //--- 如果未能创建句柄 if(handle_iMACD==INVALID_HANDLE) { //--- 告之失败并输出错误代码 PrintFormat("创建 iMACD 指标句柄失败, 品种 %s/%s, 错误代码 %d", Symbol(), EnumToString(Period()), GetLastError()); //--- 指标提前停止 return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACDGet(MAIN_LINE,1); } //+------------------------------------------------------------------+ //| 获取 iMACD 缓存区数据值 | //| 缓存区编号如下: | //| 0 - MAIN_LINE, 1 - SIGNAL_LINE | //+------------------------------------------------------------------+ double iMACDGet(const int buffer,const int index) { double MACD[1]; //--- 清除错误代码 ResetLastError(); //--- 用索引为 0 的指标缓冲区数值填充 iMACDBuffer 数组的一部分 if(CopyBuffer(handle_iMACD,buffer,index,1,MACD)<0) { //--- 如果复制失败, 告之错误代码 PrintFormat("从 iMACD 指标复制数据失败, 错误代码 %d",GetLastError()); //--- 以零为结果退出 - 意即该指标被视为未计算 return(0.0); } return(MACD[0]); } //+------------------------------------------------------------------+
我们用 MQL4 风格重新编写代码。
创建指标句柄并获取指标数据将在一个函数中实现:
//+------------------------------------------------------------------+ //| iMACD 函数的 MQL4 表述方式 | //+------------------------------------------------------------------+ double iMACD( string symbol, // 品名 ENUM_TIMEFRAMES period, // 周期 int fast_ema_period, // 快速均线计算周期 int slow_ema_period, // 慢速均线计算周期 int signal_period, // 差值均化周期 ENUM_APPLIED_PRICE applied_price, // 处理价格 int buffer, // 缓存区 int shift // 偏移 ) { double result=NULL; //--- int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period, applied_price); double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; return(result); }
注意!编写函数之后, 我们将创建指标句柄 ON EVERY 标记。您也许会说 文档不建议 这种 "创造性"。我们来看看 技术指标函数 章节:
由于指标值的计算需要一些时间, 因此您不能在创建指标数据后立即引用指标数据。所以最好在 OnInit() 中创建指标句柄.
那么这段代码为什么工作且不消耗内存?答案在同一章节:
注意。在一个 mql5 程序中重复调用具有相同参数的指标函数不会导致引用计数器的成倍增长; 该计数器只会增加 1 次。不过, 建议在函数 OnInit() 或类构造函数中获取指标句柄, 并在其它函数中深入使用这些句柄。当一个 mql5 程序逆初始化后, , 引用计数器减少。
换句话说, MQL5 采用优化设计: 它控制句柄的创建, 并且不允许使用相同的参数多次创建相同的指标。如果反复尝试创建指标副本的句柄, 只需简单地使用相应设定获取先前创建的指标句柄。无论如何, 仍然建议在 OnInit() 中接收一次句柄。原因将在稍后给出。
注意: 此处没有检查所生成的句柄有效性。
现在, 接收 iMACD 指标值的代码如下所示:
//+------------------------------------------------------------------+ //| MACD MQL4 style EA.mq5 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" #define MODE_MAIN 0 #define MODE_SIGNAL 1 //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); } //+------------------------------------------------------------------+ //| iMACD 函数的 MQL4 表述方式 | //+------------------------------------------------------------------+ double iMACD( string symbol, // 品名 ENUM_TIMEFRAMES period, // 周期 int fast_ema_period, // 快速均线计算周期 int slow_ema_period, // 慢速均线计算周期 int signal_period, // 差值均化周期 ENUM_APPLIED_PRICE applied_price, // 处理价格 int buffer, // 缓存区 int shift // 偏移 ) { double result=NULL; //--- int handle=iMACD(symbol,period,fast_ema_period,slow_ema_period,signal_period, applied_price); double val[1]; int copied=CopyBuffer(handle,buffer,shift,1,val); if(copied>0) result=val[0]; return(result); } //+------------------------------------------------------------------+
注意: 若以 MQL4 风格访问指标, 我们不能检查返回值, 因为 MQL4 风格的所有函数都只返回 'double' 值。将在 1.1 节中提供可能的解决方案。
它看起来非常麻烦, 因此我们在一个单独的 IndicatorsMQL5.mqh 头文件中实现 'define' 块和 double iMACD() 函数, 该文件位于单独的文件夹 "[数据文件夹]\MQL5\Include\SimpleCall"。在这种情况下, 代码变得很短。请注意, 我们包含 IndicatorsMQL5.mqh 文件。这意味着在访问 MACD 时, 指标行的名称应以 MQL5 的 MAIN_LINE 而非 MQL4 的 MODE_MAIN 形式传输:
//+------------------------------------------------------------------+ //| MACD MQL4 style EA short.mq5 | //| 版权所有 © 2018, Vladimir Karputov | //| http://wmua.ru/slesar/ | //+------------------------------------------------------------------+ #property copyright "版权所有 © 2018, Vladimir Karputov" #property link "http://wmua.ru/slesar/" #property version "1.000" #include <SimpleCall\IndicatorsMQL5.mqh> //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 智能系统逆初函数 | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| 智能系统逐笔报价函数 | //+------------------------------------------------------------------+ void OnTick() { //--- double macd_main_1=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1); Comment("MACD, 主缓存区, 索引 1: ",DoubleToString(macd_main_1,Digits()+1)); } //+------------------------------------------------------------------+
我已经实线了 "注释" 仅供验证。如果在测试器可视化模式下启动 "MACD MQL4 style EA short.mq5" 并将光标放在索引为 #1 的柱线上, 您可以验证它的工作:
图例 1. 在测试器中的 "MACD MQL4 style EA short.mh5"
1.1. 使用 "IndicatorsXXXX.mqh" 时的一些细微差别
在返回值中处理错误
所有指标的数据都以双精度传递。如果突然无法从指标获取数据, 则向用户发送消息报错。如果未创建指标句柄 (例如, 如果指定了不存在的品种), 或者在调用 CopyBuffer 时发生复制错误, 则可能会发生这种情况。
如果发生错误, 简单地传递 "0.0" 不是一种好的选项, 因为对于大多数指标而言, "0.0" 是一个很正常的值 (例如, 对于 MACD)。分形指标以 EMPTY_VALUE 值填充缓冲区索引, 所以返回 EMPTY_VALUE 常量 (具有 DBL_MAX 的值) 也不是一个选项, 因为这不是错误。
唯一剩下的选项是传递 "非数字" — NaN。为了达成它, 创建全局级别 NaN 变量。变量初始化为 "非数字":
double NaN=double("nan"); //+------------------------------------------------------------------+ //| iAC 函数的 MQL4 表述形式 | //+------------------------------------------------------------------+ double iAC( string symbol, // 品名 ENUM_TIMEFRAMES timeframe, // 时间帧 int shift // 偏移 ) { double result=NaN; //--- int handle=iAC(symbol,timeframe); if(handle==INVALID_HANDLE) { Print(__FUNCTION__,": INVALID_HANDLE 错误=",GetLastError()); return(result); } double val[1]; int copied=CopyBuffer(handle,0,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyBuffer 错误=",GetLastError()); return(result); }
这种方法的优点在于 NaN 在出现错误的情况下被返回, 并且与任何数字比较的结果均为 'false'。
//+------------------------------------------------------------------+ //| 脚本程序开始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- 比较 NaN 的示例 double NaN=double("nan"); double a=10.3; double b=-5; double otherNaN=double("nan"); Print("NaN>10.3=",NaN>a); Print("NaN<-5=",NaN<b); Print("(NaN==0)=",NaN==0); Print("(NaN==NaN)=",NaN==otherNaN); //--- 结果 NaN>10.3=false NaN<-5=false (NaN==0)=false (NaN==NaN)=false //--- }
因此, 如果我们想要以 MQL4 风格使用这些函数, 那么仅当比较结果为 true 时, 才有必要进行交易操作 (以及任何其它重要操作)。虽然在这种情况下, 我坚持使用 MathIsValidNumber 函数检查返回值。
MQL4 和 MQL5 中指标线的标识符
描述指标线的常量值有部分存在兼容性问题。譬如, 我们以 iAlligator 为例:
- MQL4: 1 - MODE_GATORJAW, 2 - MODE_GATORTEETH, 3 - MODE_GATORLIPS
- MQL5: 0 - GATORJAW_LINE, 1 - GATORTEETH_LINE, 2 - GATORLIPS_LINE
问题在于 "IndicatorsXXXX.mqh" 函数中的指标线以数字形式出现。例如, 如果这个数字是 1, 那么没人能说出用户的意思: 他们以 MQL4 风格工作 (并认为 1 - MODE_GATORJAW), 或是它们以 MQL5 风格工作 (且指标线认定完全不同 1 - GATORTEETH_LINE)。
在这方面, 我决定创建两个包含文件 - 几乎是双胞胎: "IndicatorsMQL4.mqh" 和 "IndicatorsMQL5.mqh"。它们的区别在于 "IndicatorsMQL4.mqh" 文件仅以 MQL4 风格理解指标线, 而文件 "IndicatorsMQL5.mqh" 仅以 MQL5 风格理解指标线。在 "IndicatorsMQL4.mqh" 当中, 输入参数中的指标线变换直接在 iADX, iAlligator ... 函数内部执行 — 您不能将这些变换替换为 #define。
我用 iBands 和 iEnvelopes 的例子来解释这个原因:
//+------------------------------------------------------------------+ //| iBands 函数的 MQL4 表述形式 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER | //| MQL5 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND | //+------------------------------------------------------------------+ double iBands( ... //+------------------------------------------------------------------+ //| iEnvelopes 函数的 MQL4 表述形式 | //| 缓存区编号如下: | //| MQL4 0 - MODE_MAIN, 1 - MODE_UPPER, 2 - MODE_LOWER | ??? //| MQL5 0 - UPPER_LINE, 1 - LOWER_LINE, -/- | //+------------------------------------------------------------------+ double iEnvelopes(
在 MQL4 中, MODE_UPPER 用于 布林带指标, 它被转换为 1, 而对于 轨道线指标, 它被转换为 0。
2. 如果我们在每次逐笔报价来临时应用 MQL4 风格的指标, 那么内存消耗是多少?
我们来比较两个 EA 的内存消耗: "iMACD.mq5" — 可正确访问指标的 EA 和 "MACD MQL4 style EA short.mq5" — 可访问 MQL4 风格指标。终端设置中窗口中的最大柱线数量设置为 "100000"。创建 14 个图表的两个设定文件:
- "iMACd" 设定 — "iMACd.mq5" EA 在13 个图表上设置, 所有图表均为 M30 时间帧;
- "MACD MQL4 style EA short" 设定 — "MACD MQL4 style EA short.mq5" EA 在 13 个图表上设置。
"Terminal memory used.mq5" 指标在第十四个图表上启动。其目标是每 10 秒打印一次 TERMINAL_MEMORY_USED 标识符。
我们将比较两个值: 终端消耗的内存量 (任务管理器数据) 和打印的 TERMINAL_MEMORY_USED 标识符。观察将进行 10 分钟 — 我们将看看是否消耗了很多内存。主要条件: 启动终端后, 不要做任何事情 - 不要打开新的页面或聊天。
设定 | 任务管理器 | TERMINAL_MEMORY_USED | 任务管理器 (10 分钟) | TERMINAL_MEMORY_USED (10 分钟) |
---|---|---|---|---|
iMACd | 279.7 MB | 745 MB | 279.7 MB | 745 MB |
MACD MQL4 style EA short | 279.9 MB | 745 MB | 280.0 MB | 745 MB |
现在, 我们修改测试: 在工作 10 分钟后, 将所有图表的时间帧切换到 H1。
设定 | 任务管理器 | TERMINAL_MEMORY_USED | 任务管理器 (10 分钟) | TERMINAL_MEMORY_USED (10 分钟) |
---|---|---|---|---|
iMACd | 398.0 MB | 869 MB | 398.3 MB | 869 MB |
MACD MQL4 style EA short | 319.2 MB | 874 MB | 330.5 MB | 874 MB |
内存使用明细汇总表:
设定 | 任务管理器 (M30), MB |
TERMINAL_MEMORY_USED (M30), MB |
任务管理器 (H1), MB |
TERMINAL_MEMORY_USED (H1), MB |
||||
---|---|---|---|---|---|---|---|---|
开始 | 10 分钟 | 开始 | 10 分钟 | 开始 | 10 分钟 | 开始 | 10 分钟 | |
iMACd | 279.7 | 279.7 | 745 | 745 | 398.0 | 869 | 398.3 | 869 |
MACD MQL4 style EA short | 279.9 | 280.0 | 745 | 745 | 319.2 | 874 | 330.5 | 874 |
3. MACD Sample.mq4 EA 的新生
我们来检查执行速度, 内存消耗和 [数据文件夹]\MQL4\Experts\MACD Sample.mq4 EA (以 MQL5 开发, 不过是 MQL4 风格, 就像 "MACD MQL4 style EA short.mq5") 符合 [数据文件夹]\MQL5\Experts\Examples\MACD\MACD Sample.mq5 EA。
3.1. 我们来修改 "MACD Sample.mq5" EA, 令其一次接收一个值
标准发行版中 "MACD Sample.mq5" 一次接收两个指标的数值:
//+------------------------------------------------------------------+ //| 如果处理过任何仓位, 主函数返回 true | //+------------------------------------------------------------------+ bool CSampleExpert::Processing(void) { //--- 更新汇率 if(!m_symbol.RefreshRates()) return(false); //--- 更新指标 if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2) return(false); if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main) !=2 || CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 || CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA) !=2) return(false); // m_indicators.Refresh(); //--- 简化编码并加速访问 //--- 数据被放入内部变量 m_macd_current =m_buff_MACD_main[0]; m_macd_previous =m_buff_MACD_main[1]; m_signal_current =m_buff_MACD_signal[0]; m_signal_previous=m_buff_MACD_signal[1]; m_ema_current =m_buff_EMA[0]; m_ema_previous =m_buff_EMA[1];
之后, 来自数组维度 "2" 的数据被分配给变量。为什么这样做?无论我们每次复制一个亦或两个数值, 我们仍然使用 CopyBuffer。但是, 一次复制两个数值时, 我们节省一次写入数组的操作。
但是 "MACD Sample.mq4" EA 每次收到一个指标值:
//--- 简化编码并加快访问放入内部变量的数据 MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,0); MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_MAIN,1); SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,0); SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MODE_SIGNAL,1); MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
MACD 主线, MACD 信号线和移动平均线分别被检索两次。所以, "MACD Sample.mq5" 应该采用相同的形式。我们将这个 EA 版本称为 "MACD Sample One value at time.mq5"。以下是它如何变化, 以便我们一次接收一个数值:
//--- 更新指标 if(BarsCalculated(m_handle_macd)<2 || BarsCalculated(m_handle_ema)<2) return(false); // if(CopyBuffer(m_handle_macd,0,0,2,m_buff_MACD_main) !=2 || // CopyBuffer(m_handle_macd,1,0,2,m_buff_MACD_signal)!=2 || // CopyBuffer(m_handle_ema,0,0,2,m_buff_EMA) !=2) // return(false); // m_indicators.Refresh(); //--- 简化编码并加速访问 //--- 数据被放入内部变量 CopyBuffer(m_handle_macd,0,0,1,m_buff_MACD_main); m_macd_current=m_buff_MACD_main[0]; CopyBuffer(m_handle_macd,0,1,1,m_buff_MACD_main); m_macd_previous=m_buff_MACD_main[0]; CopyBuffer(m_handle_macd,1,0,1,m_buff_MACD_signal); m_signal_current=m_buff_MACD_signal[0]; CopyBuffer(m_handle_macd,1,1,1,m_buff_MACD_signal); m_signal_previous=m_buff_MACD_signal[0]; CopyBuffer(m_handle_ema,0,0,1,m_buff_EMA); m_ema_current=m_buff_EMA[0]; CopyBuffer(m_handle_ema,0,1,1,m_buff_EMA); m_ema_previous=m_buff_EMA[0];
该代码保存在文章末尾的 "MACD Sample time value at time.mq5" 之中。
3.2. 将 "MACD Sample.mq4" 转换为 MQL5 代码
为了能够以 MQL4 风格访问指标, 以及处理仓位和交易, 我们应该包含 "IndicatorsMQL4.mqh" 文件 (您记得, 该文件仅理解指标线的 MQL4 名称) 和 CPositionInfo, CTrade, CSymbolInfo 和 CAccountInfo 交易类。还有, 'defines' 模块 — 指标线名称 — 应添加到 EA 以便正确访问 "IndicatorsMQL4.mqh" 中的指标:
#property description "以 MQL4 的风格访问指标" #define MODE_MAIN 0 #define MODE_SIGNAL 1 #include <SimpleCall\IndicatorsMQL4.mqh> //--- #include <Trade\PositionInfo.mqh> #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\AccountInfo.mqh> CPositionInfo m_position; // 交易仓位对象 CTrade m_trade; // 交易对象 CSymbolInfo m_symbol; // 品名信息对象 CAccountInfo m_account; // 账户信息包装 //--- input double TakeProfit =50;
另外, 特殊乘数 是必需的, 用于调整报价小数位数为三位或五位:
input double MACDCloseLevel=2; input int MATrendPeriod =26; //--- double m_adjusted_point; // 用于小数位为 3 或 5 的调整值 //+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+
若要接收当前价格, 我使用 CSymbolInfo 交易类的 m_symbol 对象:
//+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- if(!m_symbol.Name(Symbol())) // 设置品名 return(INIT_FAILED); RefreshRates();
RefreshRates() 方法更新价格并确认价格不会等于 "0.0":
//+------------------------------------------------------------------+ //| 更新品种报价数据 | //+------------------------------------------------------------------+ bool RefreshRates(void) { //--- 更新汇率 if(!m_symbol.RefreshRates()) { Print("RefreshRates 错误"); return(false); } //--- 防止返回 "零" 值 if(m_symbol.Ask()==0 || m_symbol.Bid()==0) return(false); //--- return(true); }
在 OnInit() 中初始化 m_symbol 对象后, 初始化 m_adjusted_point 乘数:
//+------------------------------------------------------------------+ //| 智能系统初始化函数 | //+------------------------------------------------------------------+ int OnInit() { //--- if(!m_symbol.Name(Symbol())) // 设置品名 return(INIT_FAILED); RefreshRates(); //--- 调整 3 或 5 位小数 int digits_adjust=1; if(m_symbol.Digits()==3 || m_symbol.Digits()==5) digits_adjust=10; m_adjusted_point=m_symbol.Point()*digits_adjust; //--- return(INIT_SUCCEEDED); }
在 OnTick() 中, 我们以 MQL4 风格访问指标, 感谢 "IndicatorsMQL4Style.mqh" :
if(!RefreshRates()) return; //--- 简化编码并加快访问放入内部变量的数据 MacdCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,0); MacdPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,MAIN_LINE,1); SignalCurrent=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,0); SignalPrevious=iMACD(NULL,0,12,26,9,PRICE_CLOSE,SIGNAL_LINE,1); MaCurrent=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,0); MaPrevious=iMA(NULL,0,MATrendPeriod,0,MODE_EMA,PRICE_CLOSE,1);
3.2.1. 处理仓位
为了最大限度地符合要求, 确定缺少仓位
total=PositionsTotal(); if(total<1) {
尽管这种方法并不完全正确, 因为它没有考虑其它品种 和/或 其它标识符 (魔幻数字) 对应的仓位。
3.2.2. 买入仓位 使用 CTrade 类的 Buy 方法开仓, 同时由相同类中的 ResultDeal 方法验证执行的正确性。如果执行无误, ResultDeal 会返回一个成交单号。
//--- 检查多头 (买入) 的可能性 if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDOpenLevel*m_adjusted_point) && MaCurrent>MaPrevious) { m_trade.Buy(Lots,m_symbol.Name(),m_symbol.Ask(), 0.0, m_symbol.NormalizePrice(m_symbol.Ask()+TakeProfit*m_adjusted_point), "macd sample"); if(m_trade.ResultDeal()!=0) Print("买入仓位开仓 : ",m_trade.ResultPrice()); else Print("开买入仓位错误 : ",m_trade.ResultRetcodeDescription()); return; }
注意, 交易请求中的价格使用 CSymbolInfo 交易类的 NormalizePrice 方法进行常规化。该方法允许参考量化: 最小价格变化和小数位数。
使用相同的方法为卖出仓位开仓。
3.2.3. 仓位旁路模块: 平仓或修改。
循环本身通过 从公共仓位数量减一直至零 (含零)。为了能够处理一笔仓位, 首先, 我们需要通过一般列表中的索引来选择它:
for(int i=PositionsTotal()-1;i>=0;i--) if(m_position.SelectByIndex(i)) // 通过索引选择仓位以便进一步访问其属性
使用 PositionClose 方法平仓, 而通过 PositionModify 完成仓位修改。注意, 修改允许使用 CSymbolInfo 交易类的 NormalizePrice 方法。
整体仓位旁路模块:
//--- 正确入场非常重要, 但正确离场更重要... for(int i=PositionsTotal()-1;i>=0;i--) if(m_position.SelectByIndex(i)) // 通过索引选择仓位以便进一步访问其属性 if(m_position.Symbol()==m_symbol.Name()) { //--- 开多头仓位 if(m_position.PositionType()==POSITION_TYPE_BUY) { //--- 应当平仓吗? if(MacdCurrent>0 && MacdCurrent<SignalCurrent && MacdPrevious>SignalPrevious && MacdCurrent>(MACDCloseLevel*m_adjusted_point)) { //--- 平仓并退出 if(!m_trade.PositionClose(m_position.Ticket())) Print("PositionClose 错误 ",m_trade.ResultRetcodeDescription()); return; } //--- 检查尾随停止 if(TrailingStop>0) { if(m_position.PriceCurrent()-m_position.PriceOpen()>m_adjusted_point*TrailingStop) { if(m_position.StopLoss()<m_symbol.Bid()-m_adjusted_point*TrailingStop) { //--- 修改仓位并退出 if(!m_trade.PositionModify(m_position.Ticket(), m_symbol.NormalizePrice(m_position.PriceCurrent()-m_adjusted_point*TrailingStop), m_position.TakeProfit())) Print("PositionModify 错误 ",m_trade.ResultRetcodeDescription()); return; } } } } if(m_position.PositionType()==POSITION_TYPE_SELL) { //--- 应当平仓吗? if(MacdCurrent<0 && MacdCurrent>SignalCurrent && MacdPrevious<SignalPrevious && MathAbs(MacdCurrent)>(MACDCloseLevel*m_adjusted_point)) { //--- 平仓并退出 if(!m_trade.PositionClose(m_position.Ticket())) Print("PositionClose 错误 ",m_trade.ResultRetcodeDescription()); return; } //--- 检查尾随停止 if(TrailingStop>0) { if((m_position.PriceOpen()-m_position.PriceCurrent())>(m_adjusted_point*TrailingStop)) { if((m_position.StopLoss()>(m_symbol.Ask()+m_adjusted_point*TrailingStop)) || (m_position.StopLoss()==0.0)) { //--- 修改仓位并退出 if(!m_trade.PositionModify(m_position.Ticket(), m_symbol.NormalizePrice(m_symbol.Ask()+m_adjusted_point*TrailingStop), m_position.TakeProfit())) Print("PositionModify 错误 ",m_trade.ResultRetcodeDescription()); return; } } } } }
我们所有变化均已完成。最后的文件 "MACD Sample 4 to 5 MQL4 style.mq5" 附在下面。
3.3. 我们来比较基于 MACD 的 EA 的执行速度
以下 EA 将用于比较:
- "MACD Sample.mq5" — 来自标准发行包中的 EA 正确访问指标
- "MACD Sample One value at a time.mq5" — "MACD Sample.mq5" 的等价物, 每次从指标中获取一个数值
- "MACD Sample 4 to 5 MQL4 style.mq5" — 将 MQL4 EA 以最小修改转换到 MQL5, 并访问 MQL4 风格的指标
在 MetaQuotes-Demo 服务器上, 从 2017.02.01 到 2018.01.16 针对 USDJPY M30 进行测试。终端在每次测试后重置 (无论是切换 EA 还是切换逐笔报价生成模式)。PC 配置:
Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M @ 2.50GHz, 内存: 4217 / 8077 Mb, 硬盘: 335 / 464 Gb, GMT+2
# | 智能交易系统 | 每次逐笔报价基于真实的逐笔报价 | 每次逐笔报价 | OHLC (开盘价/最高价/最低价/收盘价) | ||||||
---|---|---|---|---|---|---|---|---|---|---|
测试时间 | 交易 | 成交 | 测试时间 | 交易 | 成交 | 测试时间 | 交易 | 成交 | ||
1 | MACD Sample.mq5 | 0:01:19.485 | 122 | 244 | 0:00:53.750 | 122 | 244 | 0:00:03.735 | 119 | 238 |
2 | MACD Sample One value at a time.mq5 | 0:01:20.344 | 122 | 244 | 0:00:56.297 | 122 | 244 | 0:00:03.687 | 119 | 238 |
3 | MACD Sample 4 to 5 MQL4 style.mq5 | 0:02:37.422 | 122 | 244 | 0:01:52.171 | 122 | 244 | 0:00:06.312 | 119 | 238 |
所有三个 EA 在 "每次逐笔报价" 中都显示出类似的图表:
图例 2. 策略测试器中的 MACD Sample XXXX
结论: "MACD Sample 4 to 5 MQL4 style.mq5" EA 访问 MQL4 风格的指标时比之可以正确访问指标的 类似 EA 慢两倍。
3.4. 我们来比较基于 MACD 的 EA 的内存消耗
如同第 2 点那样, 使用了相同的 14 个图表。如果我们在每次交易时使用 MQL4 风格的指标, 会发生什么呢?"Terminal memory used.mq5" 指标始终保留在第一个图表上。它每 10 秒打印一次 TERMINAL_MEMORY_USED ID, 而 EA 则在剩下的 13 个图表中逐一打开。 在每次测量之前, 终端都会复位。
# | 智能交易系统 | 任务管理器, MB | TERMINAL_MEMORY_USED, Мб |
---|---|---|---|
1 | MACD Sample.mq5 | 334.6 | 813 |
2 | MACD Sample One value at a time.mq5 | 335.8 | 813 |
3 | MACD Sample 4 to 5 MQL4 style.mq5 | 342.2 | 818 |
结论: 基于 MACD 的 EA 正确访问指标, 与基于 MACD 的 EA 访问 MQL4 风格的指标在内存消耗方面具有可比性。它们消耗大约相同数量的内存。
4. [数据文件夹]\MQL4\Experts\Moving Average.mq4 EA 的新生
在前一章节中, 我们将 MQL4 转换为 MQL5。至于 Movinge Average.mq4, 我建议只需简单地修改 Moving Average.mq5, 包含 "IndicatorsMQL5.mqh" 文件
#property version "1.00" #include <SimpleCall\IndicatorsMQL5.mqh> #include <Trade\Trade.mqh>
替代 CopyBuffer
//--- 获得当前移动平均线 double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer 自 iMA 失败, 无数据"); return; }
访问 MQL4 风格的指标:
//--- 得到移动均线 ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
这给我们留下了一个检查操作结果的选项 — 将获得的数据与零进行比较。考虑到这一点, "CheckForOpen" 和 "CheckForClose" 模块中的最终代码如下所示:
//--- 获得当前移动平均线 double ma[1]; if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) { Print("CopyBuffer 自 iMA 失败, 无数据"); return; }
并且看起来像这样:
//--- 获得当前移动平均线 double ma[1]; ma[0]=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0); //if(CopyBuffer(ExtHandle,0,0,1,ma)!=1) if(ma[0]==0.0) { //Print("CopyBuffer 自 iMA 失败, 无数据"); Print("获取 MQL4 风格的 iMA 失败, 无数据"); return; }
这些是我们将在 "Moving Average MQL4 style.mq5" EA 中保存的变化。EA 附于文后。我们来测量标准的 "Moving Average.mq5" 和 "Moving Average MQL4 style.mq5" 之间的性能和内存消耗。
如您所记, 测试 在以下系统上 执行
Windows 10 (build 16299) x64, IE 11, UAC, Intel Core i3-3120M @ 2.50GHz, 内存: 4217 / 8077 Mb, 硬盘: 335 / 464 Gb, GMT+2
终端在每次测试后重置。测试针对 EURUSD M15 进行, 数据区间 2017.02.01 至 2018.01.16, MetaQuotes-Demo 服务器。
# | 智能交易系统 | 每次逐笔报价基于真实的逐笔报价 | 每次逐笔报价 | OHLC (开盘价/最高价/最低价/收盘价) | ||||||
---|---|---|---|---|---|---|---|---|---|---|
测试时间 | 交易 | 成交 | 测试时间 | 交易 | 成交 | 测试时间 | 交易 | 成交 | ||
1 | Moving Average.mq5 | 0:00:33.359 | 1135 | 2270 | 0:00:22.562 | 1114 | 2228 | 0:00:02.531 | 1114 | 2228 |
2 | Moving Average MQL4 style.mq5 | 0:00:34.984 | 1135 | 2270 | 0:00:23.750 | 1114 | 2228 | 0:00:02.578 | 1114 | 2228 |
结论: 在使用 MQL4 风格访问指标时, MQL5 内核可能不得不在 MACD Sample 中的两个句柄之间进行搜索。这种搜索花费最多时间。
在 Moving Average EA 的情况下, 当以 MQL4 风格访问指标时, MQL5 内核搜索必要的蜡烛时不花时间, 因为它是唯一的一个。
我们来比较基于移动平均值 EA 的内存消耗
如同第 2 点那样, 使用了相同的 14 个图表。"Terminal memory used.mq5" 指标始终保留在第一个图表上。它每 10 秒打印一次 TERMINAL_MEMORY_USED ID, 而 EA 则在剩下的 13 个图表中逐一打开。 在每次测量之前, 终端都会复位。
# | 智能交易系统 | 任务管理器, MB | TERMINAL_MEMORY_USED, Мб |
---|---|---|---|
1 | Moving Average.mq5 | 295.6 | 771 |
2 | Moving Average MQL4 style.mq5 | 283.6 | 760 |
结论: 内存消耗几乎相同。很小的差异可归因于终端的 "内部生活": 新闻更新等。
5. iXXXX 序列的等价物
由于我们以 MQL4 风格执行获取指标值, 因此我们来编写 "访问时间序列和指标数据" 章节的功能。该实现在 [数据文件夹]\MQL5\Include\SimpleCall\Series.mqh 中完成。
在 Series.mqh 中提供对 MQL4 时间序列值的访问的函数列表:
- Bars
- iBarShift
- iClose
- iHigh
- iHighest
- iLow
- iLowest
- iOpen
- iTime
- iVolume
MODE_OPEN, MODE_LOW, MODE_HIGH, MODE_CLOSE, MODE_VOLUME 和 MODE_TIME 系列的预定义 ID 可用于 iHighest 和 iLowest 函数。
iClose 函数实现的示例:
//+------------------------------------------------------------------+ //| iClose 函数的 MQL4 表述形式 | //+------------------------------------------------------------------+ double iClose( string symbol, // 品种 ENUM_TIMEFRAMES timeframe, // 时间帧 int shift // 偏移 ) { double result=0.0; //--- double val[1]; ResetLastError(); int copied=CopyClose(symbol,timeframe,shift,1,val); if(copied>0) result=val[0]; else Print(__FUNCTION__,": CopyClose 错误=",GetLastError()); //--- return(result); }
使用 CopyClose 获取 shift 柱线的收盘价 — 第一种调用形式 (通过初始位置和所需元素的数量访问):
int CopyClose( string symbol_name, // 品名 ENUM_TIMEFRAMES timeframe, // 周期 int start_pos, // 初始位置 int count, // 复制数量 double close_array[] // 复制收盘价的数组 );
结束语
正如我们所见, MQL5 允许 MQL4 粉丝以自己喜欢的风格获取指标和时间系列的值。他们说这段代码更短, 更易于阅读。平台开发人员在调用函数时需要更仔细的编写代码和最大程度的检查 (我完全同意他们)。我们简要列举文章中描述的函数的优缺点。
缺点:
- 访问指标时所要处理的返回错误有限;
- 在同时访问多个指标时降低测试速度;
- 依据连接 IndicatorsMQL5.mqh 或 IndicatorsMQL4.mqh, 需要正确高亮指标线。
- 代码编写简单 — 一条字符串替代了多条字符串;
- 直观性和简洁性 — 代码量越少, 理解起来就越容易。