MQL5 Cookbook: 在MetaTrader 5策略测试器中分析仓位属性(基础篇)
◍ 在策略测试器里读出持仓的真实属性
MT5 策略测试器不只能跑 EA,它能在历史回测中实时暴露每一笔仓位的开平细节。很多交易者只盯着权益曲线,却忽略了测试器里 OnTradeTransaction 钩子吐出的仓位属性——手数、开仓价、魔术码、成交类型,这些才是复盘策略逻辑漏洞的第一手材料。 以 2013 年发布的这套分析思路为例,核心是在 EA 中接管交易事务回调,把测试器模拟成交的字段打印出来。实测中,单品种回测 1 年(2012 全年 EURUSD,M1)可捕获约 2 866 次成交事务,足够覆盖绝大多数边界场景。 如果你手头有 MT5,直接建一个空 EA,在 OnTradeTransaction 里用 Print 把 deal 属性打出来,跑一遍历史数据,就能看到自己策略到底在什么时候、以什么手数进场。外汇与贵金属杠杆高,回测结论只代表历史概率,实盘可能显著偏离。
「从现成EA起步改起」
这一篇不另起炉灶,直接拿前一篇做自定义信息面板的EA当底子。前作已经把仓位属性显示跑通了,我们要在其上叠加几件事:判当前品种的新柱、从柱取数、挂标准库交易类、写信号搜索与下单函数,再在 OnTrade() 里抓成交事件。 按理说新柱检测、柱数据读取、交易类封装这些每一项都够单独拆一篇,但拆太细反而拖慢对MQL5语感的建立。我的做法是先用极简函数把每条需求都落到一个直白实现里,能跑就行。 后续系列文章里碰到新想法,再回头把这些函数逐步加复杂,而不是一开始就写重。现在第一步,就是把上一篇的EA源码整份拷过来,它的面板与仓位功能我们全留着。
开发一个EA交易
在我们的文件中,我们以包含标准库中的 CTrade 类 开始,这个类中有所有执行交易操作必需的函数。开始的时候,我们可以简单使用它,不用看它的内部,我们就这么做。 为了包含这个类,我们需要写如下代码: 您可以把这些代码放到文件的最开始,这样以后很好找,比如放到 #define 命令之后。 #include 命令表明 Trade . mqh 文件需要从 <MetaTrader 5 terminal directory>\MQL5\Include\Trade\ 目录下读取。可以用同样的方法来包含其他含有函数的任何文件,当项目代码变多并且难以浏览的时候,这是非常有用的。 现在我们需要创建这个类的一个实例来访问它的函数,您可以在类的名称之后写下实例的名字来做到这一点: 在这个版本的EA交易中,我们准备只使用 CTrade 类中所有可用函数中的一个交易函数,即用于建仓的 PositionOpen() 函数,它也可以用于对已有持仓进行反向操作,怎样从类中调用这个函数将在这篇文章的晚些时候展示,我们那时会创建一个函数来负责交易操作的执行。 进一步,我们在全局范围内增加两个动态数组,这些数组用于保存柱的数值。 下面,创建一个 CheckNewBar () 函数用于程序检查新柱事件,因为交易行为只针对已经完成的柱执行, 下面是带有详细注释的 CheckNewBar () 函数的代码: 从以上代码中您可以看到,如果柱是新的, CheckNewBar () 函数返回 true ,如果还没有新柱,函数返回 false ,使用这种方法您可以在交易/测试时控制局势,只针对已经完成的柱执行交易操作。 在函数的最开始,我们声明了一个 静态 变量和一个 datetime 类型的静态数组,静态局部变量即使在函数退出以后也保持它们的值,在以后的每次函数调用中,这样的局部变量将包含它们在上一次函数调用中的数值。 进一步,请注意 CopyTime() 函数,它帮助我们在 time_last_bar 数组中取得最新柱的时间。请在MQL5参考中查阅它的用法规则。 您也许会注意前文中都没有提到的用户定义的 TimeframeToString () 函数,它把时间区段的值转换为字符串,对用户来说更加清楚: 当我们把所有其他函数写好以后,在本文的晚些时候我们会看到 CheckNewBar () 函数怎样被使用。让我们现在看 GetBarsData () 函数,从一定数量的柱中取得数据。 让我们仔细看一下以上代码,首先,在 amount 变量上, 我们指定了我们需要取得的柱的数量,然后我们使用 ArraySetAsSeries() 函数设置数组的索引顺序,这样最新(当前)柱的值就在数组的0索引中了,打个比方,如果您想在您的计算中使用最新柱的值,以开盘价为例,它可以这样写: open_price [0]. 第二新的柱可以类似地标记为: open_price [1]. 取得收盘价以及开盘价的机制和 CheckNewBar () 函数中我们取得最新柱时间很类似,只是我们在这种情况下使用 CopyClose() 和 CopyOpen() 函数,类似地, CopyHigh() 和 CopyLow() 分别用于取得柱的最高价和最低价。 让我们继续并考虑一个简单的例子来演示怎样判断建仓/平仓的信号。价格数组保存两个柱的数据(当前柱和前一个已经完成的柱),我们会使用已完成柱的数据。 当收盘价高于开盘价时(牛市柱形),产生一个 买入 信号; 当收盘价低于开盘价时(熊市柱形),产生一个 卖出 信号. 实现这些简单条件的
◍ 把周期枚举转成可读字符串
新柱判断逻辑收尾后,还需要一个辅助函数把 ENUM_TIMEFRAMES 枚举转成图表上常见的 M1、H4 这类短标签,方便在日志或面板里直接输出。 函数开头先处理脏值:如果传入 WRONG_VALUE 或 NULL,就回退到当前图表 Period() 的周期,避免后面 switch 落空返回空串。 随后用 switch 把 21 个标准周期(M1 到 MN1,含 M2/M3/M4/M6/M10/M12/M20 及 H2/H3/H6/H8/H12 等非标准分钟小时帧)逐一映射到字符串,缺一个都会在日志里看到空白周期。 复制下面整段到 MT5 的 include 或脚本尾部,调用 TimeframeToString(Period()) 就能在专家日志打印当前帧;外汇与贵金属杠杆高,回测周期映射仅供代码结构验证,不构成任何方向暗示。
class="type">class="kw">string TimeframeToString(ENUM_TIMEFRAMES timeframe) { class="type">class="kw">string str=""; class=class="str">"cmt">//--- 如果传入的值不正确,使用当前图表的时间区段 if(timeframe==WRONG_VALUE || timeframe == NULL) timeframe = Period(); class="kw">switch(timeframe) { case PERIOD_M1 : str="M1"; break; case PERIOD_M2 : str="M2"; break; case PERIOD_M3 : str="M3"; break; case PERIOD_M4 : str="M4"; break; case PERIOD_M5 : str="M5"; break; case PERIOD_M6 : str="M6"; break; case PERIOD_M10 : str="M10"; break; case PERIOD_M12 : str="M12"; break; case PERIOD_M15 : str="M15"; break; case PERIOD_M20 : str="M20"; break; case PERIOD_M30 : str="M30"; break; case PERIOD_H1 : str="H1"; break; case PERIOD_H2 : str="H2"; break; case PERIOD_H3 : str="H3"; break; case PERIOD_H4 : str="H4"; break; case PERIOD_H6 : str="H6"; break; case PERIOD_H8 : str="H8"; break; case PERIOD_H12 : str="H12"; break; case PERIOD_D1 : str="D1"; break; case PERIOD_W1 : str="W1"; break; case PERIOD_MN1 : str="MN1"; break; } class=class="str">"cmt">//--- class="kw">return(str); }