MQL5 Cookbook: 基于三重滤网策略开发交易系统框架·进阶篇
(2/3)· 沿用前文EA骨架,用MQL5把三重滤网塞进三个可调时间范围,少写代码多跑回测
三屏滤网在 OnInit 里的初始化落点
多周期策略最怕初始化时把周期关系搞反。这段逻辑在 OnInit 里先调 GetMinimumTimeframe,用三组(时间范围, 指标周期)算出最小时间框架,后续新柱检测都以它为准,避免小周期被大周期信号误触发。 GetIndicatorHandles 里对三个屏幕分别用 iMA 取句柄:周期参数大于 0 才建均线,MODE_SMA + PRICE_CLOSE,平滑类型写死。注意原代码有个明显笔误——三个失败分支都判的是 Screen01IndicatorHandle==INVALID_HANDLE,滤网2、滤网3 的报错文本虽不同,但判断对象没换,复制时得手动改。 GetIndicatorsData 预设 NumberOfValues=3,说明每屏至少取最近 3 个缓冲区值做信号判断。外汇与贵金属杠杆高,这类多周期 EA 在跳空时可能漏柱,开 MT5 把这三段直接贴进 EA 模板,先跑 EURUSD 的 M15+H1+H4 组合验证句柄报错是否真的发生。
class=class="str">"cmt">//--- 用于决定最小时间范围的变量 ENUM_TIMEFRAMES MinimumTimeframe=WRONG_VALUE; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 为检查新柱确定最小时间范围 MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod, Screen02TimeFrame,Screen02IndicatorPeriod, Screen03TimeFrame,Screen03IndicatorPeriod); class=class="str">"cmt">//--- 取得指标句柄 GetIndicatorHandles(); class=class="str">"cmt">//--- 初始化新柱 CheckNewBar(); class=class="str">"cmt">//--- 取得属性 GetPositionProperties(P_ALL); class=class="str">"cmt">//--- 设置信息面板 SetInfoPanel(); class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 取得指标句柄 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void GetIndicatorHandles() { class=class="str">"cmt">//--- 取得参数指定的指标的句柄 if(Screen01IndicatorPeriod>class="num">0) Screen01IndicatorHandle=iMA(_Symbol,Screen01TimeFrame,Screen01IndicatorPeriod,class="num">0,MODE_SMA,PRICE_CLOSE); if(Screen02IndicatorPeriod>class="num">0) Screen02IndicatorHandle=iMA(_Symbol,Screen02TimeFrame,Screen02IndicatorPeriod,class="num">0,MODE_SMA,PRICE_CLOSE); if(Screen03IndicatorPeriod>class="num">0) Screen03IndicatorHandle=iMA(_Symbol,Screen03TimeFrame,Screen03IndicatorPeriod,class="num">0,MODE_SMA,PRICE_CLOSE); class=class="str">"cmt">//--- 如果无法获得第一个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网1指标句柄失败!"); class=class="str">"cmt">//--- 如果无法获得第二个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网2指标句柄失败!"); class=class="str">"cmt">//--- 如果无法获得第三个时间范围的指标句柄 if(Screen01IndicatorHandle==INVALID_HANDLE) Print("获取滤网3指标句柄失败!"); } class=class="str">"cmt">//--- 用于指标值的数组 class="type">class="kw">double indicator_buffer1[]; class="type">class="kw">double indicator_buffer2[]; class="type">class="kw">double indicator_buffer3[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 取得指标值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool GetIndicatorsData() { class=class="str">"cmt">//--- 为判断交易信号的指标缓冲区数值的数量 class="type">int NumberOfValues=class="num">3; class=class="str">"cmt">//--- 如果没有获得指标句柄 if((Screen01IndicatorPeriod>class="num">0 && Screen01IndicatorHandle==INVALID_HANDLE) || (Screen02IndicatorPeriod>class="num">0 && Screen02IndicatorHandle==INVALID_HANDLE) ||
「三周期滤网的数据抓取与容错」
多时间框架滤网的核心动作,是把三个不同周期的指标缓冲拉进同一套数组里比对。上面这段逻辑依次处理第一、第二、第三滤网:只有当对应时间范围大于 0 且指标句柄有效时才动手,避免空句柄直接崩在 CopyBuffer 上。 每一段都先调 ArraySetAsSeries 把数组索引反转成 3 2 1 0 的时间倒序,再用 CopyBuffer 从句柄 0 号缓冲复制 NumberOfValues 个值。若实际复制到的数量少于请求值,就打印当前品种、周期和错误描述并 return(false),把失败暴露给上层而不是静默继续。 这种写法在 MT5 实盘里有个直接可验证的点:把 NumberOfValues 设成 100、第三滤网周期选 Weekly,冷启动首次调用时若句柄还没就绪,日志里会明确看到「复制数值失败」配 GetLastError 码,而不是指标 silently 算错。外汇与贵金属波动大、重连频繁,句柄失效是高概率事件,靠这套返回 false 的机制至少能让后续信号判定暂停而非误触。
if(Screen03IndicatorPeriod>class="num">0 && Screen03IndicatorHandle==INVALID_HANDLE)) class=class="str">"cmt">//--- 尝试重新获取 GetIndicatorHandles(); class=class="str">"cmt">//--- 如果使用了第一滤网时间范围并且获得了指标句柄 if(Screen01TimeFrame>class="num">0 && Screen01IndicatorHandle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0) ArraySetAsSeries(indicator_buffer1,true); class=class="str">"cmt">//--- 取得指标值 if(CopyBuffer(Screen01IndicatorHandle,class="num">0,class="num">0,NumberOfValues,indicator_buffer1)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至 indicator_buffer1 数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); class=class="str">"cmt">//--- class="kw">return(false); } } class=class="str">"cmt">//--- 如果使用了第二滤网的时间范围并且获得了指标句柄 if(Screen02TimeFrame>class="num">0 && Screen02IndicatorHandle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0) ArraySetAsSeries(indicator_buffer2,true); class=class="str">"cmt">//--- 取得指标值 if(CopyBuffer(Screen02IndicatorHandle,class="num">0,class="num">0,NumberOfValues,indicator_buffer2)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至indicator_buffer2数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); class=class="str">"cmt">//--- class="kw">return(false); } } class=class="str">"cmt">//--- 如果使用了第三滤网的时间范围并且获得了指标句柄 if(Screen03TimeFrame>class="num">0 && Screen03IndicatorHandle!=INVALID_HANDLE) { class=class="str">"cmt">//--- 反转索引顺序 (... class="num">3 class="num">2 class="num">1 class="num">0) ArraySetAsSeries(indicator_buffer3,true); class=class="str">"cmt">//--- 取得指标值 if(CopyBuffer(Screen03IndicatorHandle,class="num">0,class="num">0,NumberOfValues,indicator_buffer3)<NumberOfValues) { Print("复制数值失败 ("+ _Symbol+"; "+TimeframeToString(Period())+") (至indicator_buffer3数组)错误 ("+ IntegerToString(GetLastError())+"): "+ErrorDescription(GetLastError())); class=class="str">"cmt">//--- class="kw">return(false); } } class=class="str">"cmt">//--- class="kw">return(true);
◍ 信号判定与加仓阈值的代码实现
EA 的信号分发集中在 GetTradingSignal() 与 GetSignal() 两个函数里:前者管「有无持仓」下的动作决策,后者只负责把指标状态翻译成 ORDER_TYPE_BUY / ORDER_TYPE_SELL / WRONG_VALUE。无持仓时逻辑很直白——GetSignal() 给什么方向就返回什么方向,不附加过滤。 持仓后的处理才是重点。多头持仓碰到卖出信号会直接反手;而同方向加仓被设了硬门槛:以多头为例,必须 close_price[1] 高于上一笔成交价 last_deal_price 加上 CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point) 才允许再买。空头加仓则要求收盘价低于上次成交价减同样步长。这套写法把「马丁加仓」锁在了一定空间距离之外,避免贴着成本线频繁补单。 GetSignal() 本身支持三个独立周期屏幕(Screen01~03),但代码里只处理了「仅启用其中一个周期」的分支:当某 ScreenXXIndicatorPeriod>0 而其他两个 ≤0 时,比较对应 indicator_buffer 的 [1] 与 [2] 柱值,前一柱更低就出卖出信号。回测时若你开了多周期共振,这段原样逻辑会直接忽略,需要自己补判断。 外汇与贵金属杠杆高,这类基于柱值比较的信号在震荡市可能连续触发 WRONG_VALUE,实盘前务必在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 跑一遍验证步长参数。
ENUM_ORDER_TYPE GetTradingSignal() { class=class="str">"cmt">//--- 如果没有持仓 if(!pos.exists) { class=class="str">"cmt">//--- 卖出信号 if(GetSignal()==ORDER_TYPE_SELL) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- 买入信号 if(GetSignal()==ORDER_TYPE_BUY) class="kw">return(ORDER_TYPE_BUY); } class=class="str">"cmt">//--- 如果有持仓 if(pos.exists) { class=class="str">"cmt">//--- 读取仓位类型 GetPositionProperties(P_TYPE); class=class="str">"cmt">//--- 取得最后一笔交易的价格 GetPositionProperties(P_PRICE_LAST_DEAL); class=class="str">"cmt">//--- 卖出信号 if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_SELL) class="kw">return(ORDER_TYPE_SELL); if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_SELL && close_price[class="num">1]<pos.last_deal_price-CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) class="kw">return(ORDER_TYPE_SELL); class=class="str">"cmt">//--- 买入信号 if(pos.type==POSITION_TYPE_SELL && GetSignal()==ORDER_TYPE_BUY) class="kw">return(ORDER_TYPE_BUY); if(pos.type==POSITION_TYPE_BUY && GetSignal()==ORDER_TYPE_BUY && close_price[class="num">1]>pos.last_deal_price+CorrectValueBySymbolDigits(VolumeIncreaseStep*_Point)) class="kw">return(ORDER_TYPE_BUY); } class=class="str">"cmt">//--- 没有信号 class="kw">return(WRONG_VALUE); } ENUM_ORDER_TYPE GetSignal() { class=class="str">"cmt">//--- 卖出信号: 指标在已完成柱的当前值比前一柱低 class=class="str">"cmt">//--- 只有一个时间范围的条件 if(Screen01IndicatorPeriod>class="num">0 && Screen02IndicatorPeriod<=class="num">0 && Screen03IndicatorPeriod<=class="num">0) { if(indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=class="num">0 && Screen02IndicatorPeriod>class="num">0 && Screen03IndicatorPeriod<=class="num">0) { if(indicator_buffer2[class="num">1]<indicator_buffer2[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } class=class="str">"cmt">//--- }
多周期信号组合的下单判定
这段逻辑把三个时间范围的指标缓冲做了排列组合,只在满足对应周期数时才返回方向。Screen01/02/03 的 Period 参数若 ≤0 代表该周期不参与筛选,>0 才纳入信号计算。 卖信号的核心写法是:已参与的两个或三个周期中,indicator_buffer[n][1] 都小于 [2],即上一根已完成柱的值高于再前一根,倾向认为短线动量向下。例如三周期全开时,buffer1、buffer2、buffer3 的 [1]<[2] 同时成立才 return ORDER_TYPE_SELL。 买信号反过来:参与周期内 [1]>[2] 才 return ORDER_TYPE_BUY。注意代码只比对索引 1 和 2,也就是倒数第二根与倒数第三根已完成柱,实盘前应在 MT5 用 Print 打出这两个索引确认没有偏移一根。 外汇与贵金属波动受数据面影响大,这类纯动量组合只过滤方向、不带止损逻辑,触发后仍需人工复核价差与滑点风险。
if(Screen01IndicatorPeriod<=class="num">0 && Screen02IndicatorPeriod<=class="num">0 && Screen03IndicatorPeriod>class="num">0) { if(indicator_buffer3[class="num">1]<indicator_buffer3[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } class=class="str">"cmt">//--- 只有两个时间范围的条件 if(Screen01IndicatorPeriod>class="num">0 && Screen02IndicatorPeriod>class="num">0 && Screen03IndicatorPeriod<=class="num">0) { if(indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer2[class="num">1]<indicator_buffer2[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod<=class="num">0 && Screen02IndicatorPeriod>class="num">0 && Screen03IndicatorPeriod>class="num">0) { if(indicator_buffer2[class="num">1]<indicator_buffer2[class="num">2] && indicator_buffer3[class="num">1]<indicator_buffer3[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } if(Screen01IndicatorPeriod>class="num">0 && Screen02IndicatorPeriod<=class="num">0 && Screen03IndicatorPeriod>class="num">0) { if(indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer3[class="num">1]<indicator_buffer3[class="num">2]) class="kw">return(ORDER_TYPE_SELL); } class=class="str">"cmt">//--- 三个时间范围的条件 if(Screen01IndicatorPeriod>class="num">0 && Screen02IndicatorPeriod>class="num">0 && Screen03IndicatorPeriod>class="num">0) { if(indicator_buffer1[class="num">1]<indicator_buffer1[class="num">2] && indicator_buffer2[class="num">1]<indicator_buffer2[class="num">2] && indicator_buffer3[class="num">1]<indicator_buffer3[class="num">2] ) class="kw">return(ORDER_TYPE_SELL); } class=class="str">"cmt">//--- 买入信号: 在已完成柱上的指标当前值比前一柱指标值高 class=class="str">"cmt">//--- 只有一个时间范围的条件 if(Screen01IndicatorPeriod>class="num">0 && Screen02IndicatorPeriod<=class="num">0 && Screen03IndicatorPeriod<=class="num">0) { if(indicator_buffer1[class="num">1]>indicator_buffer1[class="num">2]) class="kw">return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=class="num">0 && Screen02IndicatorPeriod>class="num">0 && Screen03IndicatorPeriod<=class="num">0) { if(indicator_buffer2[class="num">1]>indicator_buffer2[class="num">2]) class="kw">return(ORDER_TYPE_BUY); } if(Screen01IndicatorPeriod<=class="num">0 && Screen02IndicatorPeriod<=class="num">0 && Screen03IndicatorPeriod>class="num">0) { if(indicator_buffer3[class="num">1]>indicator_buffer3[class="num">2]) class="kw">return(ORDER_TYPE_BUY); } class=class="str">"cmt">//--- 只有两个时间范围的条件