初学者以 MQL5 实现对数字滤波器的实际实施(基础篇)
📘

初学者以 MQL5 实现对数字滤波器的实际实施(基础篇)

第 1/3 篇

「用 MQL5 搭一个能跑的数字滤波器」

想在 MT5 里亲手验证数字滤波器对价格序列的平滑效果,不必从理论公式啃起。MQL5 直接提供了数组与指标缓冲区接口,初学者也能在半小时内部署一个可加载的自定义指标。 下面这段示例用简单移动平均思路实现一阶低通滤波,核心是把前一根的滤波值与当前价格按系数混合。系数 alpha 越小,曲线越钝;越大则越贴近原始收盘价。 [CODE]//+------------------------------------------------------------------+

//一阶低通滤波器示例

//+------------------------------------------------------------------+ #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 //--- plot Filtered #property indicator_label1 "Filtered" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue //--- input double alpha = 0.1; // 平滑系数 //--- buffers double ExtFilteredBuffer[]; // 滤波输出 //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,ExtFilteredBuffer,INDICATOR_DATA); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total<2) return(0); ExtFilteredBuffer[0]=close[0]; for(int i=1;i<rates_total;i++) { ExtFilteredBuffer[i]=alpha*close[i]+(1-alpha)*ExtFilteredBuffer[i-1]; } return(rates_total); } //+------------------------------------------------------------------+[/CODE] 逐行拆解: #property indicator_chart_window 让指标直接画在主图价格上,方便对比。 double alpha = 0.1; 是外部可调参数,加载指标后在属性框改它即可实时看效果。 ExtFilteredBuffer[0]=close[0]; 用第一根收盘价初始化,避免数组空引用。 for 循环里 alpha*close[i]+(1-alpha)*ExtFilteredBuffer[i-1] 就是递推式:当前滤波值 = 新价格权重 + 旧滤波值残重。 把 alpha 调到 0.02 挂 EURUSD 日线,曲线会比原生收盘价滞后明显但噪声锐减;外汇与贵金属杠杆高、滑点无常,这类平滑只辅助判势,不预示方向,实盘前务必在策略测试器跑历史数据确认惯性偏差。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 一阶低通滤波器示例                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property indicator_chart_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="macro">#class="kw">property indicator_plots   class="num">1
class=class="str">"cmt">//---  plot Filtered
class="macro">#class="kw">property indicator_label1  "Filtered"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  clrDodgerBlue
class=class="str">"cmt">//---  class="kw">input
class="type">class="kw">double alpha = class="num">0.1;          class=class="str">"cmt">// 平滑系数
class=class="str">"cmt">//---  buffers
class="type">class="kw">double ExtFilteredBuffer[];  class=class="str">"cmt">// 滤波输出
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
   SetIndexBuffer(class="num">0,ExtFilteredBuffer,INDICATOR_DATA);
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
   if(rates_total<class="num">2) class="kw">return(class="num">0);
   ExtFilteredBuffer[class="num">0]=close[class="num">0];
   for(class="type">int i=class="num">1;i<rates_total;i++)
     {
      ExtFilteredBuffer[i]=alpha*close[i]+(class="num">1-alpha)*ExtFilteredBuffer[i-class="num">1];
     }
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

编译完先翻工具箱的错误页

在 MetaEditor 里点完编译,别直接关掉。切到 Toolbox(工具箱)窗口的 Errors(错误)选项卡,逐条看 EA 交易的编译结果。哪怕只标了一个 warning,也要确认它不是后续逻辑错位的源头。 我之前那篇拆过简单指标的代码,也提过它跟 MetaTrader 5 客户端怎么交互。现在要继续往下改 SMA 指标,前提是编译干净、没藏着未决错误。 实操上,开 MT5 载入早先那份 SMA 源码,编译一次,把 Errors 页截图或记下来。只有这一页全空,才值得你往下做参数和信号逻辑的改动。外汇与贵金属波动大,代码层级的小错可能在实盘放大成异常下单,高风险。

◍ 故意写错代码来摸清编译器脾气

在 MetaEditor 里正常编译两个未改动的指标版本时,过程很顺:.mq5 源文件旁边直接生成 .ex5,零报错。但写代码不可能永远不翻车,MetaEditor 内置了全量错误检查,编译完会在 Toolbox 的 Errors 选项卡丢出完整错误列表。 定位很简单——双击 Toolbox 里那行报错,编辑器通常会用箭头图标精确标到出错的代码行。要注意一个坑:一行错经常引发一串连锁报错。所以别从中间改,先跳到编译器报的第一行去修,修完重新编译,再看新列表的第一行。 最快的理解方式不是读文档,而是主动破坏。在代码里人为制造下列任一改动,按 Compile 看反应,直觉记住这些破坏长什么样:运算符或变量里插空格、删分号、乱加分号、删运算符、加减大括号或圆括号、删逗号、给 OnCalculate() 多加输入参数、变量除零、if 里用 = 代替 ==、把 bar++ 改成 bar--。 外汇与贵金属指标开发高风险,编译能过不代表策略能赚钱。最后留个底:MetaEditor 只认 MQL5 语法错,逻辑错它一概查不出,得靠你自己回测和盯盘验证。

「开写前先够用的那点语法」

刚碰 MQL5 的人容易被整本语言手册吓住,其实第一批能跑起来的程序,用到的语法只占全部可用词汇的一小撮。和日常说话一样,交易者写 EA 或指标时反复调用的也就是那几个固定零件。 最小可用集合很明确:四种基础变量 int、double、bool、string,配合 if-else 做分支、for 做循环、{} 组织复合语句、return 退出函数,再加上分号 ; 断句和逗号 , 分隔参数。这套组合拳足以把初步的行情判断逻辑写成可编译代码。 额外建议直接啃数学和三角函数那部分——MT5 里取均线斜率、算波动率都离不开它们。新手在策略测试器里用上面这些元素写个打印 K 线高低点的脚本,就能完成第一次「词汇量」验证。外汇与贵金属杠杆高,先用历史数据回测练语法,别急着上实盘。

给指标加上动态短名与绘制起点

在 MT5 里改指标的客户端显示,核心就是动 OnInit() 和 OnCalculate() 里的几行接口代码,不碰算法本身也能让图表更耐看。用 StringConcatenate() 拼出带参数的短名,再借 PlotIndexSetString() 和 IndicatorSetString() 推到数据窗口和 tooltip,交易者悬停就能看到当前加载的是 FATL(偏移) 还是 SMA(周期,偏移)。 OnCalculate() 里补一句 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod) 没什么副作用,反而能避免基于其他指标计算时在前几根柱画出无意义的连线。代码量上去一点,复杂度略增,但接口对用户更友好,值得做。 下面这段可直接拷进已有指标的属性声明后做对照:#property 那串定义了单缓冲、主窗口画线、红色实线宽1;真正改显示逻辑的是 shortname 拼接与 PlotIndexSet* 调用。外汇与贵金属波动剧烈,这类显示改良不改动信号逻辑,但高频重算时仍需注意客户端卡顿风险。

MQL5 / C++
class=class="str">"cmt">//---- 指标的作者
class="macro">#class="kw">property copyright "class="num">2010, MetaQuotes Software Corp."
class=class="str">"cmt">//---- 作者网站链接
class="macro">#class="kw">property link      "[MQL5官方文档]
class=class="str">"cmt">//---- 指标版本号
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//----在主窗口中绘制指标
class="macro">#class="kw">property indicator_chart_window
class=class="str">"cmt">//---- 一个缓存用于计算和绘制指标
class="macro">#class="kw">property indicator_buffers class="num">1
class=class="str">"cmt">//---- 仅使用一种图形来绘制
class="macro">#class="kw">property indicator_plots  class="num">1
class=class="str">"cmt">//---- 画指标线
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class=class="str">"cmt">//---- 红色用于指标线的颜色
class="macro">#class="kw">property indicator_color1  Red
class=class="str">"cmt">//---- 指标线是连续的曲线
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class=class="str">"cmt">//---- 指标线的宽度为1
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//---- 显示指标的标签
class="macro">#class="kw">property indicator_label1  "SMA"
class=class="str">"cmt">//---- 初始化指标简短名称的变量
  class="type">class="kw">string shortname;
  StringConcatenate(shortname,"FATL(",FATLShift,")");
class=class="str">"cmt">//--- 创建数据窗口显示的标签
  PlotIndexSetString(class="num">0,PLOT_LABEL,shortname);
class=class="str">"cmt">//---创建在独立窗口和工具提示中显示的名称
  IndicatorSetString(INDICATOR_SHORTNAME,shortname);
class=class="str">"cmt">//--- 确定指标值的显示精度
  IndicatorSetInteger(INDICATOR_DIGITS,_Digits+class="num">1);
class=class="str">"cmt">//--- 禁止显示空值
  PlotIndexSetDouble(class="num">0,PLOT_EMPTY_VALUE,class="num">0.0);
  shortname = shortname + "SMA(" + MAPeriod + "," + MAShift + ")";
class=class="str">"cmt">//---- 柱形重算循环中,计算“第一个”开始值
  if(prev_calculated==class="num">0)        class=class="str">"cmt">//检查第一个指标值的计算
   {
     first=FATLPeriod-class="num">1+begin;  class=class="str">"cmt">// 所有柱形的开始计算位置
     class=class="str">"cmt">//--- 在“begin”柱的基础上增加数据开始位,因为 
     class=class="str">"cmt">//   此计算是基于另外一个指标的值
     if(begin>class="num">0)
       PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,begin+FATLPeriod);
   
}
  else first=prev_calculated-class="num">1; class=class="str">"cmt">// 新柱形计算的开始位置

常见问题

先写一个带 OnInit 和 OnCalculate 的空指标框架,在 OnCalculate 里做加权平均或移动平均就算跑起来了,编译后拖到图表看线。
打开 MT5 工具箱切到错误页,红色条目会指出行号和类型,照着改完重新编译即可。
可以,小布能读你贴的指标代码并指出语法和逻辑坑,还能建议绘制起点和短名设置,省去翻错误页的时间。
能摸清编译器报错的脾气,比如缺分号、类型不对的提示样式,下次真写错一眼就懂。
动态短名让多周期实例不重名,绘制起点避免历史不足时画歪,都在 OnInit 里用 IndicatorSetString 和 SetIndexDrawBegin 设。