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

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

第 1/3 篇

「用 MQL5 亲手搭一个实时数字滤波器」

在 MT5 里做价格滤波,不一定要调现成指标。MQL5 允许你直接写一个自定义的 OnCalculate,把输入价格序列接进一个最简递归滤波器,看着缓冲区的数值随每根新 K 线滚动更新。 下面这段是最小可跑框架:声明一个 double 数组做缓冲区,在每次计算里用上一根的输出和当前价格做一次一阶低通迭代。系数 alpha 取 0.1 时,滤波器对突发行情的响应会明显慢于裸价,适合剥掉高频噪声。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="type">class="kw">double filterBuffer[];
class="kw">input class="type">class="kw">double alpha = class="num">0.1;
class="type">int OnInit() {
   SetIndexBuffer(class="num">0, filterBuffer, INDICATOR_DATA);
   class="kw">return INIT_SUCCEEDED;
}
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[]) {
   if (prev_calculated == class="num">0) {
      filterBuffer[class="num">0] = close[class="num">0];
   }
   for (class="type">int i = prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">1; i < rates_total; i++) {
      filterBuffer[i] = alpha * close[i] + (class="num">1 - alpha) * filterBuffer[i-class="num">1];
   }
   class="kw">return rates_total;
}
逐行拆解: #property indicator_separate_window 让指标画在独立副图,不和价格叠一起。 #property indicator_buffers 1 声明只用 1 个数据缓冲区。 double filterBuffer[] 就是装滤波结果的动态数组。 input double alpha = 0.1 暴露一个外部参数,0.1 是平滑强度,越小越钝。 OnInitSetIndexBuffer 把数组绑到索引 0 的绘图缓冲。 OnCalculate 是核心:首次跑 prev_calculated==0 时把第 0 根收盘价直接灌进缓冲;之后从断点续算,用 alpha*close + (1-alpha)*前一根缓冲 做一阶递归。 外汇和贵金属本身是高杠杆高风险品种,这类滤波只帮你降噪,不预示方向。开 MT5 把上面代码存成 SimpleLP.mq5 编译挂上 EURUSD 的 M1,把 alpha 从 0.1 改到 0.3,能直接看到副图曲线跟价变紧——这就是验证递归系数作用的最快路径。

MQL5 / C++
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">1
class="type">class="kw">double filterBuffer[];
class="kw">input class="type">class="kw">double alpha = class="num">0.1;
class="type">int OnInit() {
   SetIndexBuffer(class="num">0, filterBuffer, INDICATOR_DATA);
   class="kw">return INIT_SUCCEEDED;
}
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[]) {
   if (prev_calculated == class="num">0) {
      filterBuffer[class="num">0] = close[class="num">0];
   }
   for (class="type">int i = prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">1; i < rates_total; i++) {
      filterBuffer[i] = alpha * close[i] + (class="num">1 - alpha) * filterBuffer[i-class="num">1];
   }
   class="kw">return rates_total;
}

◍ 编译报错先过一遍再改代码

在 MetaEditor 里点开 Toolbox(工具箱)窗口,切到 Errors(错误)选项卡,先把 EA 的编译结果逐条看清楚。这一步不能跳——很多 SMA 指标的逻辑改动,编译期就能暴露数组越界或参数类型不匹配。 之前那篇拆过简单指标的代码,也提过它跟 MetaTrader 5 客户端怎么交互。现在要继续往下改我早先给的 SMA 指标,前提是编译错误为零或者你已确认每条 warning 无害。 开 MT5 验证路径:MetaEditor 载入旧 SMA 源码 → 按 F7 编译 → 看 Errors 页有无红条。有红条先消掉,再动指标计算逻辑。外汇与贵金属波动大,改完参数请在策略测试器用历史数据跑一遍,高风险。

让编译器替你抓语法漏勺

在 MT5 里写指标,最直观的反馈来自 MetaEditor 的编译环节。正常无改动的 .mq5 文件点编译后,会顺带生成同名的 .ex5 文件,过程零报错,这说明语法层是过关的。 但人为改代码几乎必然引入错误。MetaEditor 内置了全量语法检查,报错会列在 Toolbox 窗口的 Errors 选项卡。双击任一行,编辑器会自动跳到对应代码行并标出位置。 一个坑是:单行错误常引发一连串报错。正确做法只改编译器报出的第一行,重新编译,再处理下一处序列。反复来过,直到 Errors 清空。 想摸清编译器脾气,最硬核的办法是主动破坏。下面这些改动都能触发不同报错类型:运算符或变量间插空格、清掉分号、乱加分号、删运算符、增删大括号或圆括号、删逗号、给 OnCalculate() 加多余入参、写零作除数、if 里用 = 替 ==、把 bar++ 改成 bar--。 外汇与贵金属交易高风险,指标仅作辅助参考。要注意 MetaEditor 只认 MQL5 语法错,逻辑错它管不了——比如条件写反却语法正确,编译照过,实盘可能直接给你反着算。

「先吃透 MQL5 的最小可用词表」

观察过日常对话就知道,一个人说话实际用到的词,远少于他认识的总词汇量。写 MQL5 也是同一回事:刚上手时,没必要把整本语言参考都背下来,先把最高频的那撮运算符和表达式练熟就够开张。 最小可用集合可以压到很薄:四种变量类型 int、double、bool、string,配上 if-else 条件分支、for 循环、{} 复合语句块和 return 返回,再加上分号 ; 与逗号 , 的断句规则。这套组合拳足以支撑你写出第一个指标或脚本。 若想让程序真正处理行情,数学与三角函数那部分越早碰越好——它们直接决定你能否把价格序列算成信号。外汇与贵金属杠杆高、滑点随机,任何计算逻辑都先在 MT5 策略测试器里跑历史再谈信任。

◍ 给指标加个会说话的短名字

在 MT5 里改指标的客户端显示,不需要推倒重写,核心就是往 OnInit() 里塞几行全局运算符和函数调用,把指标名拼成带参数的短串。 StringConcatenate() 按公式把周期、偏移拼进 shortname,再用 PlotIndexSetString() 和 IndicatorSetString() 分别写进数据窗口标签和独立窗口名。顺手用 IndicatorSetInteger() 把显示精度加到 _Digits+1,空值用 PlotIndexSetDouble() 禁掉,界面就比出厂默认友好一截。 在 OnCalculate() 补 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod) 没坏处,尤其当指标算的是别的数据源——从 begin 柱起跳能避免前面画空中楼阁。代码量涨一点、逻辑重一点是自然代价。 下面这段可直接拷进 MQ5 文件比对:FATLShift、MAPeriod 这些变量你本地若没定义,编译会报未声明,先确认自己指标里有没有对应输入参数再跑。外汇和贵金属行情跳空频繁,这类基于衍生值的画线在重大数据夜可能滞后,当参考而非信号本身。

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">// 新柱形计算的开始位置

常见问题

先准备一个干净的自定义指标文件,用最少的代码把价格数组接进来,再套一层简单滑动平均逻辑,能编译跑通就是最小可用起点。
别急着改,先把报错按行号过一遍,确认是语法漏写还是逻辑类型不对,再动手改,避免越改越乱。
小布可以帮你梳理最小可用词表和常见语法坑,把重复的检查劳动交给它,你专注滤波逻辑本身。
先吃透数组、循环、OnCalculate 和绘图函数这组最小词表,能覆盖大部分基础指标开发场景。
起一个简短且带明确用途的词组,比如“实时滤波v1”,在指标属性里填好,图表上加载时一眼能认出。