MQL5三重滤网EA框架开发实战
🛠️

MQL5三重滤网EA框架开发实战

基于移动平均改造可扩展多周期交易系统

MQL5 编程 难度 · 进阶 2026-06-27 6 分钟阅读
#MQL5#三重滤网#EA框架#多周期

三重滤网策略与本文目标

三重滤网(Triple Screen)策略由Alexander Elder博士提出,核心思想是在不同时间周期上分别确认趋势与入场时机,就像潮汐、波浪和涟漪的关系:大周期定方向,小周期找切入点。网络上对该策略评价两极分化,但真正检验它的方式是用代码实现并做历史回测,而不是听信任何一方的观点。

本文不从头开发EA,而是基于前作《在EA交易中使用指标设置交易条件》的成品修改。原EA已具备止损止盈、跟踪止损、仓位递增、反向反转等完整功能,我们只需调整外部参数、增加时间框架选项并改写信号模块,即可得到一个通用三重滤网框架。后续若想换用其他指标,改动量也极小。

✦ 学习建议
动手前请把前文EA文件夹复制并重命名,避免直接覆盖原工程,方便对比修改差异。

外部参数与指标句柄设计

为支持三个独立滤网,我们声明了三组参数:Screen01/02/03TimeFrame(ENUM_TIMEFRAMES类型,可在下拉框选周期)与对应的IndicatorPeriod。若某周期Period设为0,表示该滤网停用,系统可降级为单屏或双屏。同时删除了原工程的IndicatorSegments等冗余条件,并在Enums.mqh中移除多余指标枚举,因为本框架只用一种指标(示例为均线)。

每个时间范围需要独立的指标句柄与数据数组。句柄在GetIndicatorHandles()中通过iMA()按各自Symbol与周期获取;数组indicator_buffer1~3分别接收对应滤网数值。注意原文代码中存在一处笔误:三个失败提示都判断了Screen01IndicatorHandle,实际应分别对应01/02/03,否则排查句柄错误时会产生误导。

input    ENUM_TIMEFRAMES   Screen01TimeFrame=PERIOD_W1;  // 第一个滤网的时间范围
input    int               Screen01IndicatorPeriod=14;   // 第一个滤网的指标周期数
input    ENUM_TIMEFRAMES   Screen02TimeFrame=PERIOD_D1;  // 第二滤网的时间范围
input    int               Screen02IndicatorPeriod=24;   // 第二滤网的指标周期数
input    ENUM_TIMEFRAMES   Screen03TimeFrame=PERIOD_H4;  // 第三滤网的时间范围
input    int               Screen03IndicatorPeriod=44;   // 第三滤网的指标周期数
int  Screen01IndicatorHandle=INVALID_HANDLE;
int  Screen02IndicatorHandle=INVALID_HANDLE;
int  Screen03IndicatorHandle=INVALID_HANDLE;
double indicator_buffer1[]; double indicator_buffer2[]; double indicator_buffer3[];

最小时间范围的自动判定

EA以最小时间范围作为新柱检查基准(CheckNewBar与GetBarsData均依赖它),但用户在外部参数中可按任意顺序填写周期。因此需在初始化时调用GetMinimumTimeframe(),遍历所有启用的Period,用PeriodSeconds换算为秒并取最小值。该函数覆盖0~3个滤网全场景:全0返回当前周期;仅一个>0则返回它;两个或三个>0则用MathMin比较秒数。

此设计让框架不强制周期大小顺序,提升易用性。MinimumTimeframe变量存结果,在OnInit里赋值,供后续逻辑使用。若日后扩展为四屏,只需重载此函数增加参数分支。

ENUM_TIMEFRAMES GetMinimumTimeframe(ENUM_TIMEFRAMES tf1,int p1, ENUM_TIMEFRAMES tf2,int p2, ENUM_TIMEFRAMES tf3,int p3)
  {
   int t1=PeriodSeconds(tf1); int t2=PeriodSeconds(tf2); int t3=PeriodSeconds(tf3);
   if(p1<=0 && p2<=0 && p3<=0) return(PERIOD_CURRENT);
   if(p1>0 && p2<=0 && p3<=0) return(tf1);
   if(p2>0 && p1<=0 && p3<=0) return(tf2);
   if(p3>0 && p1<=0 && p2<=0) return(tf3);
   if(p1>0 && p2>0 && p3<=0) return((MathMin(t1,t2)==t1)?tf1:tf2);
   if(p1>0 && p3>0 && p2<=0) return((MathMin(t1,t3)==t1)?tf1:tf3);
   if(p2>0 && p3>0 && p1<=0) return((MathMin(t2,t3)==t2)?tf2:tf3);
   if(p1>0 && p2>0 && p3>0)
     {
      ENUM_TIMEFRAMES tm=(int)MathMin(t1,t2)==t1?tf1:tf2;
      int tmin=PeriodSeconds(tm);
      return((int)MathMin(tmin,t3)==tmin?tm:tf3);
     }
   return(WRONG_VALUE);
  }

指标数据获取与缓冲区管理

GetIndicatorsData()负责把各滤网均线值填入数组。关键点:使用ArraySetAsSeries(...,true)将索引倒序,使[0]为最新未完成柱、[1]为上一完结柱、[2]为再前一柱,符合顺势比较习惯。CopyBuffer若返回条数不足则打印错误并返回false,触发重连句柄逻辑。

NumberOfValues设为3,即只取最近三根柱,足够判断“当前值比前一柱高/低”。若某滤网Period<=0,则跳过对应拷贝,自然不参与信号计算。这种按开关懒加载的方式降低了资源占用。

bool GetIndicatorsData()
  {
   int NumberOfValues=3;
   if((Screen01IndicatorPeriod>0 && Screen01IndicatorHandle==INVALID_HANDLE) ||
      (Screen02IndicatorPeriod>0 && Screen02IndicatorHandle==INVALID_HANDLE) ||
      (Screen03IndicatorPeriod>0 && Screen03IndicatorHandle==INVALID_HANDLE))
      GetIndicatorHandles();
   if(Screen01TimeFrame>0 && Screen01IndicatorHandle!=INVALID_HANDLE)
     {
      ArraySetAsSeries(indicator_buffer1,true);
      if(CopyBuffer(Screen01IndicatorHandle,0,0,NumberOfValues,indicator_buffer1)<NumberOfValues)
        { Print("复制数值失败1"); return(false); }
     }
   // 同理处理 buffer2, buffer3 ...
   return(true);
  }

信号合成与交易触发逻辑

GetSignal()按启用滤网数量组合条件:单屏时仅看该屏均线方向;多屏时要求所有启用屏同向(如三屏均buffer[1]<buffer[2]才发SELL)。GetTradingSignal()再叠加仓位状态:无仓时直接跟信号;有仓时若反向信号且价格偏离足够(VolumeIncreaseStep*_Point)则加仓或反转。原文评论指出该实现用三根MA比较并非Elder原版“潮汐波浪涟漪”精神,但作为框架演示足够清晰。

⚠ 常见坑
原文GetIndicatorHandles中三处失败打印都判断Screen01,复制代码务必改对变量名;另外GetSignal未贴完的买入三屏分支需自行补全闭合括号,否则编译失败。
ENUM_ORDER_TYPE GetSignal()
  {
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]<indicator_buffer1[2] &&
         indicator_buffer2[1]<indicator_buffer2[2] &&
         indicator_buffer3[1]<indicator_buffer3[2])
         return(ORDER_TYPE_SELL);
     }
   if(Screen01IndicatorPeriod>0 && Screen02IndicatorPeriod>0 && Screen03IndicatorPeriod>0)
     {
      if(indicator_buffer1[1]>indicator_buffer1[2] &&
         indicator_buffer2[1]>indicator_buffer2[2] &&
         indicator_buffer3[1]>indicator_buffer3[2])
         return(ORDER_TYPE_BUY);
     }
   return(WRONG_VALUE);
  }

初始化与资源释放

OnInit中依次:算最小周期→取句柄→初始化新柱→读仓位属性→画面板。OnDeinit在REASON_REMOVE时删除面板对象并IndicatorRelease三个句柄,防止内存泄漏与指标实例残留。这种成对管理是MQL5 EA规范的基本要求。

int OnInit()
  {
   MinimumTimeframe=GetMinimumTimeframe(Screen01TimeFrame,Screen01IndicatorPeriod,
                                        Screen02TimeFrame,Screen02IndicatorPeriod,
                                        Screen03TimeFrame,Screen03IndicatorPeriod);
   GetIndicatorHandles(); CheckNewBar(); GetPositionProperties(P_ALL); SetInfoPanel();
   return(0);
  }
void OnDeinit(const int reason)
  {
   if(reason==REASON_REMOVE)
     {
      DeleteInfoPanel();
      IndicatorRelease(Screen01IndicatorHandle);
      IndicatorRelease(Screen02IndicatorHandle);
      IndicatorRelease(Screen03IndicatorHandle);
     }
  }

回测优化与框架扩展

策略测试器需指定三个时间范围最小值(即EA自动算出的最小周期)作为测试精度。优化时作者人工定周期、让其他参数跑遗传或穷举,双核约30分钟。结果显示最大余额回撤小于最大采收率测试,说明框架可产出稳健曲线。附件elderthreescreens.set提供参数范本。

该框架价值不在策略本身优劣,而在“改信号区块即可换系统”的工程思路。您可把iMA换成iRSI、iMACD,或在GetSignal加成交量过滤;也可将Screen数组改为动态数组支持N屏。对于想批量验证想法的交易者,这是极好的起点。

✦ 延伸阅读
同作者MQL5 Cookbook系列还有多币种EA、信号指标系统文章,可组合学习形成自己的库。

常见问题

复制原EA工程,增加三组ENUM_TIMEFRAMES与周期外部参数,为每个周期建独立iMA句柄与缓冲区,写GetMinimumTimeframe确定最小周期,并在GetSignal中按启用屏数组合条件,最后OnInit/OnDeinit配对管理句柄。
用PeriodSeconds把各周期转秒,在GetMinimumTimeframe里按Period>0的个数分支,用MathMin比较秒数返回最小ENUM_TIMEFRAMES,存为全局变量供CheckNewBar使用。
常见原因:指标句柄获取失败(原文打印bug致不易发现)、Screen周期全填0、或GetSignal括号未闭合编译虽过但逻辑截断;应先看日志句柄错误,确认参数Period>0且最小周期设置正确。
设为true后缓冲区索引0为最新柱,便于用buffer[1]与buffer[2]比较已完成柱的方向,符合三重滤网顺时判断习惯,避免索引错位导致信号反相。