初学者以 MQL5 实现对数字滤波器的实际实施(基础篇)
「用 MQL5 亲手搭一个实时数字滤波器」
在 MT5 里做价格滤波,不一定要调现成指标。MQL5 允许你直接写一个自定义的 OnCalculate,把输入价格序列接进一个最简递归滤波器,看着缓冲区的数值随每根新 K 线滚动更新。 下面这段是最小可跑框架:声明一个 double 数组做缓冲区,在每次计算里用上一根的输出和当前价格做一次一阶低通迭代。系数 alpha 取 0.1 时,滤波器对突发行情的响应会明显慢于裸价,适合剥掉高频噪声。
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 是平滑强度,越小越钝。
OnInit 里 SetIndexBuffer 把数组绑到索引 0 的绘图缓冲。
OnCalculate 是核心:首次跑 prev_calculated==0 时把第 0 根收盘价直接灌进缓冲;之后从断点续算,用 alpha*close + (1-alpha)*前一根缓冲 做一阶递归。
外汇和贵金属本身是高杠杆高风险品种,这类滤波只帮你降噪,不预示方向。开 MT5 把上面代码存成 SimpleLP.mq5 编译挂上 EURUSD 的 M1,把 alpha 从 0.1 改到 0.3,能直接看到副图曲线跟价变紧——这就是验证递归系数作用的最快路径。
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 这些变量你本地若没定义,编译会报未声明,先确认自己指标里有没有对应输入参数再跑。外汇和贵金属行情跳空频繁,这类基于衍生值的画线在重大数据夜可能滞后,当参考而非信号本身。
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">// 新柱形计算的开始位置