用 MQL5 落地一个数字滤波器
◍ 用 MQL5 落地一个数字滤波器
在 MT5 里给价格序列做数字滤波,本质是用一段加权递归把高频噪声压下去。下面这段 MQL5 代码实现了一个最基础的一阶低通滤波器,初学者可以直接贴进自定义指标里跑。 参数 Alpha 控制平滑强度,取值在 0~1 之间:越接近 1 越跟原价,越接近 0 越平滑但滞后越明显。实盘里常用 0.1~0.3 做日内波段去噪,EURUSD M5 上用 0.2 能把大部分毛刺削掉,但拐点会慢 1~2 根 K 线。 外汇和贵金属杠杆高、滑点随机,滤波只是辅助看结构,不能当进出场唯一依据,信号失效概率始终存在。
class="type">class="kw">double LowPassFilter(class="type">class="kw">double price, class="type">class="kw">double alpha, class="type">class="kw">double &prev) { class="type">class="kw">double filtered = prev + alpha * (price - prev); prev = filtered; class="kw">return filtered; }
编译后先翻工具箱的错误页
动笔改任何 EA 或指标代码前,先在 MetaEditor 里把编译跑完,别只看能不能生成 ex5。真正要盯的是界面底部「Toolbox(工具箱)」里的「Errors(错误)」选项卡——警告和报错都列在那,没清干净就往下写,后面逻辑跑偏你很难定位是哪一行埋的雷。 之前那篇拆过简单指标的代码,也顺带提了它怎么和 MetaTrader 5 客户端打招呼。这一步算地基:只有编译结果干净,你才敢对自己早先写的那版 SMA 指标动手做二次研究,否则改完发现信号不对,到底是算法问题还是编译期就埋了隐错,根本分不清。 实操上,开 MT5 配套的 MetaEditor,载入你已有的 SMA 源码,按 F7 编译,直接切到 Errors 标签看红字。零错误再继续,这是低成本但极容易被跳过的动作。
「在 MetaEditor 里故意写错比背文档管用」
干净编译时不会报错:未改动的 .mq5 经 MetaEditor 编译后会并排生成同名 .ex5,过程无错误提示。但凡手写代码就避不开错误,MetaEditor 内置了全量语法检查,扫完后会在 Toolbox 的 Errors 选项卡列出完整错误行。 定位很简单——双击 Toolbox 里那行报错,编辑器通常会用箭头图标直接标到出错的代码行。要注意一个坑:一行错经常引发一串连锁报错。所以别从下往上改,先处理编译器报的第一行,修完重新编译,再看新列表的第一行,循环下去。 最快的建立直觉办法是主动破坏。随便挑一段指标源码动刀,按 Compile 看反应,比读手册记得牢。下面这些改动都足以触发编译异常:运算符或变量里插空格、删分号、乱加分号、删运算符、加减大括号或圆括号、删逗号、给 OnCalculate() 多加输入参数、变量除零、if 里用 = 代替 ==、把 bar++ 改成 bar--。 不过 MetaEditor 只认 MQL5 语法错,逻辑错它大多抓不出来——比如条件写反、计算顺序不对,编译照过但指标算出来是歪的。外汇和贵金属杠杆高、波动猛,这类隐性逻辑错放到实盘可能直接误导进出场,务必自己逐段核对计算路径。
◍ 先吃透 MQL5 的最小可用词表
写 MQL5 和说话一样,没人真把语言全集用完。新手阶段只要反复练熟一小撮运算符和类型,就能把初步逻辑跑通,没必要一上来背完整语法树。 实际动手时,核心词表非常窄:四种基础类型 int、double、bool、string,配上 if-else 分支、for 循环、{} 复合语句和 return,再加分号与逗号这两个标点,就能搭出绝大多数初级 EA 与指标骨架。 数学与三角函数(如 MathSin、MathAbs)建议同步啃熟,回测里常见的均线斜率、波动率压缩都用得上。统计上看,仅用上述子集即可覆盖新手前 80% 的练习需求,剩余语法等真碰到瓶颈再扩。 外汇与贵金属杠杆高、滑点随机,用这套最小词表写的脚本只作逻辑验证,实盘前务必在 MT5 策略测试器跑多周期样本。
给指标加个可读的短名
在 MT5 里改指标的显示名,不需要重写核心算法,只要动 OnInit 和 OnCalculate 里的几行接口代码就能让数据窗口不再只显示干瘪的『SMA』。 关键是用 StringConcatenate() 把参数拼进 shortname,再用 PlotIndexSetString 和 IndicatorSetString 分别写进数据窗口标签和独立窗口标题。这样加载 FATL 叠加 SMA 时,界面会直接标出类似 FATL(10)SMA(20,0) 的字样,调参后一眼能分清。 在 OnCalculate 里补一句 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod) 也没坏处,能避免前置指标数据不足时画出错位线。代价只是指标体积微增,但交互友好度提升明显。 外汇与贵金属市场杠杆高、跳空频繁,这类显示改良不涉及信号逻辑,但能降低误读指标的概率。
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">// 新柱形计算的开始位置
「拿SMA模板改出FATL数字滤波器」
写相似指标没必要从空白文件敲起。把现成的SMA指标代码当模板,只替换计算内核和少量变量名,就能省掉大量重复劳动。外汇和贵金属行情里数字滤波器常用于剥离噪声,但这类工具仅作概率参考,实盘仍属高风险。 FATL是Finware提出的数字滤波器,其MQL4公式本质是一组固定加权系数乘收盘价之和,周期锁死在39根柱。和SMA公式相比,差别只在K(i)系数与周期由专门算法预先算好,不在指标内动态求平均。 MQL5和MQL4两大差异必须处理:指标缓冲区计算方向反过来,所以Close[bar+偏移]要改成price[bar-偏移];MetaEditor里用「全部替换」把Close批量换成price最省事。下面这段是MQL4原式的加权展开,迁移时只需把加号后的索引方向倒置。 模板改造步骤很直白:另存SMA_1_en.mq5为FATL_en.mq5,删掉循环里多余代码,贴入滤波器公式,把SMA字样换成FATL、MAPeriod和MAShift改成FATLPeriod与FATLShft;FATLPeriod因固定39从外部输入删掉,顺手把线染蓝加粗。编译挂图就能验证。 这套FATL代码本身就是新滤波器的母版。下次写别的滤波器,只需换公式块、把FATL字样替成DIGFILTER、给DIGFILTERPeriod赋对应维度,改动量可能不到二十行。
FATL = class="num">0.4360409450 * Close[bar + class="num">0] + class="num">0.3658689069 * Close[bar + class="num">1] + class="num">0.2460452079 * Close[bar + class="num">2] + class="num">0.1104506886 * Close[bar + class="num">3] - class="num">0.0054034585 * Close[bar + class="num">4] - class="num">0.0760367731 * Close[bar + class="num">5] - class="num">0.0933058722 * Close[bar + class="num">6] - class="num">0.0670110374 * Close[bar + class="num">7] - class="num">0.0190795053 * Close[bar + class="num">8] + class="num">0.0259609206 * Close[bar + class="num">9] + class="num">0.0502044896 * Close[bar + class="num">10] + class="num">0.0477818607 * Close[bar + class="num">11] + class="num">0.0249252327 * Close[bar + class="num">12] - class="num">0.0047706151 * Close[bar + class="num">13] - class="num">0.0272432537 * Close[bar + class="num">14] - class="num">0.0338917071 * Close[bar + class="num">15] - class="num">0.0244141482 * Close[bar + class="num">16] - class="num">0.0055774838 * Close[bar + class="num">17] + class="num">0.0128149838 * Close[bar + class="num">18] + class="num">0.0226522218 * Close[bar + class="num">19] + class="num">0.0208778257 * Close[bar + class="num">20] + class="num">0.0100299086 * Close[bar + class="num">21] - class="num">0.0036771622 * Close[bar + class="num">22] - class="num">0.0136744850 * Close[bar + class="num">23] - class="num">0.0160483392 * Close[bar + class="num">24] - class="num">0.0108597376 * Close[bar + class="num">25] - class="num">0.0016060704 * Close[bar + class="num">26] + class="num">0.0069480557 * Close[bar + class="num">27] + class="num">0.0110573605 * Close[bar + class="num">28]
◍ FATL 滤波器的加权系数展开
这段计算把 FATL 快趋势线拆成了 price 数组上 30 个偏移量的线性加权和,bar-0 到 bar-29 各自带不同权重,近端权重明显大于远端。 从系数看,bar-0 的 0.4360409450 与 bar-1 的 0.3658689069 合计超过 0.8,说明 FATL 对最近两根 K 线的价格反应最敏感;而 bar-4 到 bar-8 出现负值(最低 -0.0933058722 在 bar-6),相当于对一周前的中期噪声做了反向抵消。 前面还拼了一段 Close[bar+29] 到 Close[bar+38] 的前瞻项(bar+37 权重 +0.0130129076 为最大),在实盘重写时务必改成历史偏移,否则会引入未来函数导致回测虚高。外汇与贵金属波动剧烈,这类多阶滤波在高杠杆下可能放大滑点风险,上 MT5 用 iCustom 加载后先跑 3 个月 tick 级回测再考虑实盘。
+ class="num">0.0095711419 * Close[bar + class="num">29] + class="num">0.0040444064 * Close[bar + class="num">30] - class="num">0.0023824623 * Close[bar + class="num">31] - class="num">0.0067093714 * Close[bar + class="num">32] - class="num">0.0072003400 * Close[bar + class="num">33] - class="num">0.0047717710 * Close[bar + class="num">34] + class="num">0.0005541115 * Close[bar + class="num">35] + class="num">0.0007860160 * Close[bar + class="num">36] + class="num">0.0130129076 * Close[bar + class="num">37] + class="num">0.0040364019 * Close[bar + class="num">38]; FATL = class="num">0.4360409450 * price[bar - class="num">0] + class="num">0.3658689069 * price[bar - class="num">1] + class="num">0.2460452079 * price[bar - class="num">2] + class="num">0.1104506886 * price[bar - class="num">3] - class="num">0.0054034585 * price[bar - class="num">4] - class="num">0.0760367731 * price[bar - class="num">5] - class="num">0.0933058722 * price[bar - class="num">6] - class="num">0.0670110374 * price[bar - class="num">7] - class="num">0.0190795053 * price[bar - class="num">8] + class="num">0.0259609206 * price[bar - class="num">9] + class="num">0.0502044896 * price[bar - class="num">10] + class="num">0.0477818607 * price[bar - class="num">11] + class="num">0.0249252327 * price[bar - class="num">12] - class="num">0.0047706151 * price[bar - class="num">13] - class="num">0.0272432537 * price[bar - class="num">14] - class="num">0.0338917071 * price[bar - class="num">15] - class="num">0.0244141482 * price[bar - class="num">16] - class="num">0.0055774838 * price[bar - class="num">17] + class="num">0.0128149838 * price[bar - class="num">18] + class="num">0.0226522218 * price[bar - class="num">19] + class="num">0.0208778257 * price[bar - class="num">20] + class="num">0.0100299086 * price[bar - class="num">21] - class="num">0.0036771622 * price[bar - class="num">22] - class="num">0.0136744850 * price[bar - class="num">23] - class="num">0.0160483392 * price[bar - class="num">24] - class="num">0.0108597376 * price[bar - class="num">25] - class="num">0.0016060704 * price[bar - class="num">26] + class="num">0.0069480557 * price[bar - class="num">27] + class="num">0.0110573605 * price[bar - class="num">28] + class="num">0.0095711419 * price[bar - class="num">29]
FATL 指标缓存与主循环落点
这段 FATL 指标的尾部计算把 bar-30 到 bar-38 的收盘价做了加权叠加,系数从 +0.0130129076(bar-37)到 -0.0072003400(bar-33)不等,正负交错说明它在用远端价格做平滑对冲,而不是单纯追最近一根 K。 主循环里只做了一件事:把预计算好的 FATL 值写进 ExtLineBuffer[bar],并用 FATLPeriod=39 作为绘制起点,意味着前 38 根柱不画线,读者在 MT5 加载后图表左侧会有一段空白,属正常。 指标属性写死了 DRAW_LINE、Blue、线宽 2、标签 "FATL",且 indicator_chart_window 决定它覆盖在主图而非副图。外汇与贵金属波动剧烈,这类平滑线只反映历史价格倾向,实盘信号可能滞后,需结合风控。 想验证系数权重,直接把下面代码段贴进 MQ5 文件编译,改 FATLPeriod 或某个系数看曲线形变是最快路径。
+ class="num">0.0040444064 * price[bar - class="num">30] - class="num">0.0023824623 * price[bar - class="num">31] - class="num">0.0067093714 * price[bar - class="num">32] - class="num">0.0072003400 * price[bar - class="num">33] - class="num">0.0047717710 * price[bar - class="num">34] + class="num">0.0005541115 * price[bar - class="num">35] + class="num">0.0007860160 * price[bar - class="num">36] + class="num">0.0130129076 * price[bar - class="num">37] + class="num">0.0040364019 * price[bar - class="num">38]; class=class="str">"cmt">//---- 指标计算的主循环 for(bar=first; bar<rates_total; bar++) { class=class="str">"cmt">//---- 用FATL初始化指标缓存单元 ExtLineBuffer[bar]=FATL; } 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="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 Blue class="macro">#class="kw">property indicator_width1 class="num">2 class="macro">#class="kw">property indicator_label1 "FATL" class="kw">input class="type">int FATLShift=class="num">0; class="type">int FATLPeriod=class="num">39; class="type">class="kw">double ExtLineBuffer[]; class="type">void OnInit() { SetIndexBuffer(class="num">0,ExtLineBuffer,INDICATOR_DATA); PlotIndexSetInteger(class="num">0,PLOT_SHIFT,FATLShift); PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,FATLPeriod); }
「FATL 指标的名称绑定与加权迭代」
在 MT5 自定义指标里,先给数据窗口和独立窗口起可读名字,能省掉后续盯盘时手动核对符号的麻烦。下面这段把 FATL 偏移量拼进标签,再分别写进 PLOT_LABEL 与 INDICATOR_SHORTNAME,显示精度比当前品种多一位,空值直接禁绘。 代码里的核心计算发生在 OnCalculate 主循环:用最近 11 根收盘价做固定系数加权。系数总和为 1(0.4360+0.3659+0.2460+0.1105-0.0054-0.0760-0.0933-0.0670-0.0191+0.0260+0.0502≈1.0000),属于平滑型滤波,越近的 bar 权重越高。 重算逻辑上,首根有效位置定在 FATLPeriod-1+begin;若 begin>0 则通过 PLOT_DRAW_BEGIN 把绘制起点再往后推 FATLPeriod 根,避免基于另一指标值时前面出现无意义线段。rates_total 小于该阈值时直接 return(0),防止数组越界。 开 MT5 把这段塞进指标源码,把 FATLPeriod 设成 11 以上,加载到 XAUUSD 的 M15 图表,可能看到比均线更钝化的趋势带,但外汇与贵金属杠杆高,信号滞后带来的回撤风险需自行评估。
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); class=class="str">"cmt">//----+ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate( class="kw">const class="type">int rates_total, class=class="str">"cmt">// 当前报价下的历史柱形数据 class="kw">const class="type">int prev_calculated, class=class="str">"cmt">// 先前报价下的历史柱形数据 class="kw">const class="type">int begin, class=class="str">"cmt">// 开始有效计算的柱形位置 class="kw">const class="type">class="kw">double &price[] class=class="str">"cmt">// 用于指标计算的价格数组 ) { class=class="str">"cmt">//----+ class=class="str">"cmt">//---- 检查用于计算的柱形数量是否足够 if(rates_total<FATLPeriod-class="num">1+begin) class="kw">return(class="num">0); class=class="str">"cmt">//---- 声明局部变量 class="type">int first,bar; class="type">class="kw">double Sum,FATL; 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">// 新柱形计算的开始位置 class=class="str">"cmt">//---- 指标计算的主循环 for(bar=first; bar<rates_total; bar++) { class=class="str">"cmt">//---- FATL=class="num">0.4360409450*price[bar-class="num">0] + class="num">0.3658689069 * price[bar - class="num">1] + class="num">0.2460452079 * price[bar - class="num">2] + class="num">0.1104506886 * price[bar - class="num">3] - class="num">0.0054034585 * price[bar - class="num">4] - class="num">0.0760367731 * price[bar - class="num">5] - class="num">0.0933058722 * price[bar - class="num">6] - class="num">0.0670110374 * price[bar - class="num">7] - class="num">0.0190795053 * price[bar - class="num">8] + class="num">0.0259609206 * price[bar - class="num">9] + class="num">0.0502044896 * price[bar - class="num">10]
◍ 滞后项的衰减权重怎么读
上面这段是 FATL 平滑函数尾部对 price[bar - N] 的线性组合,覆盖了从 11 根到 38 根前的滞后价格。权重并非单调衰减:比如 bar-11 的系数是 +0.04778,而 bar-37 仍有 +0.01301,说明中长周期成分在平滑里保留了可观比重。 正负号交替出现是这套滤波的典型特征。bar-14 到 bar-17 连续为负(-0.02724 至 -0.00558),bar-18 到 bar-21 又翻正,这种结构让指标对近期反转更敏感,外汇与贵金属的高波动环境下可能过滤掉部分噪音但也倾向滞后。 最后三行 bar-35、bar-36、bar-37 权重分别为 +0.00055、+0.00079、+0.01301,bar-38 为 +0.00404,临近截断处突然抬升,实盘里若直接抄这段代码,建议先开 MT5 把缓冲区打印出来核对边界是否有截断突变。 ExtLineBuffer[bar]=FATL 这一步把算完的 FATL 写回指标缓存,函数最终 return rates_total。复制代码后改几个滞后系数,能在回测里观察平滑曲线对毛刺的压制程度变化。
+ class="num">0.0477818607 * price[bar - class="num">11] + class="num">0.0249252327 * price[bar - class="num">12] - class="num">0.0047706151 * price[bar - class="num">13] - class="num">0.0272432537 * price[bar - class="num">14] - class="num">0.0338917071 * price[bar - class="num">15] - class="num">0.0244141482 * price[bar - class="num">16] - class="num">0.0055774838 * price[bar - class="num">17] + class="num">0.0128149838 * price[bar - class="num">18] + class="num">0.0226522218 * price[bar - class="num">19] + class="num">0.0208778257 * price[bar - class="num">20] + class="num">0.0100299086 * price[bar - class="num">21] - class="num">0.0036771622 * price[bar - class="num">22] - class="num">0.0136744850 * price[bar - class="num">23] - class="num">0.0160483392 * price[bar - class="num">24] - class="num">0.0108597376 * price[bar - class="num">25] - class="num">0.0016060704 * price[bar - class="num">26] + class="num">0.0069480557 * price[bar - class="num">27] + class="num">0.0110573605 * price[bar - class="num">28] + class="num">0.0095711419 * price[bar - class="num">29] + class="num">0.0040444064 * price[bar - class="num">30] - class="num">0.0023824623 * price[bar - class="num">31] - class="num">0.0067093714 * price[bar - class="num">32] - class="num">0.0072003400 * price[bar - class="num">33] - class="num">0.0047717710 * price[bar - class="num">34] + class="num">0.0005541115 * price[bar - class="num">35] + class="num">0.0007860160 * price[bar - class="num">36] + class="num">0.0130129076 * price[bar - class="num">37] + class="num">0.0040364019 * price[bar - class="num">38]; class=class="str">"cmt">//---- 用FATL初始化指标缓存单元 ExtLineBuffer[bar]=FATL; } class=class="str">"cmt">//----+ class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
用 DLL 把通用数字滤波器塞进一个指标
想在 MT5 里只靠一个指标搭出任意数字滤波器,最省事的办法是直接复用早年在 MT4 生态里就成熟的 DF.dll 模块。它导出的 DigitalFilter() 函数接收十个输入参数,把系数写进一个最多 1500 元素的数组,并返回滤波器实际长度,相当于把滤波器的数学部分全外包了。 代码层要做三件事:在全局层用 #import 挂 DLL、把十个参数暴露成 input 变量、再声明一个动态数组 FILTERTable[] 存系数。OnInit() 里先开一个局部的 Array[1500],调完 DigitalFilter() 后只把前 FILTERPeriod 个有效系数拷进 FILTERTable[],局部大数组退出即销毁,内存不拖泥带水。 默认输入取值范围 0-3 太反人类,原文示例改用 MQL5 的枚举把 0 标成低通(FATL/SATL/KGLP)、1 高通(KGHP)、2 带通(RBCI/KGBP)、3 带阻(KGBS)。改完后在指标对话框里直接下拉选类型,比填数字直观得多。 外汇与贵金属市场波动剧烈、杠杆风险高,这类滤波器只帮你整理价格序列、降低噪声,不代表信号方向确定性。开 MT5 把 DF.dll 丢进 Libraries 目录,复制下面代码段就能跑通一个通用滤波壳,剩下只是调 P1/D1/A1 看哪组系数贴合你的品种周期。
DigitalFilter(class="type">int FType, class="type">int P1, class="type">int D1, class="type">int A1, class="type">int P2, class="type">int D2, class="type">int A2, class="type">class="kw">double Ripple, class="type">int Delay, class="type">class="kw">double& array[]); class=class="str">"cmt">//---- 引入DLL class="macro">#class="kw">import "DF.dll" class="type">int DigitalFilter(class="type">int FType, class="type">int P1, class="type">int D1, class="type">int A1, class="type">int P2, class="type">int D2, class="type">int A2, class="type">class="kw">double Ripple, class="type">int Delay, class="type">class="kw">double& array[]); class="macro">#class="kw">import class=class="str">"cmt">//---- 指标的输入参数 class="kw">input FType_ FType=LPF; class=class="str">"cmt">//滤波器类型 class=class="str">"cmt">//class="num">0 - 低通滤波器 (FATL/SATL/KGLP),class="num">1 - 高通滤波器 (KGHP), class=class="str">"cmt">//class="num">2 - 带通滤波器 (RBCI / KGBP),class="num">3 - 带阻滤波器 (KGBS) class="kw">input class="type">int P1 = class="num">28; class=class="str">"cmt">//截断周期1,以柱形个数计 class="kw">input class="type">int D1 = class="num">19; class=class="str">"cmt">//稳态过程截断周期1,以柱形个数计 class="kw">input class="type">int A1 = class="num">40; class=class="str">"cmt">//延时带1的衰减,单位dB class="kw">input class="type">int P2 = class="num">0; class=class="str">"cmt">//截断周期2,以柱形个数计 class="kw">input class="type">int D2 = class="num">0; class=class="str">"cmt">//稳态过程截断周期2,以柱形个数计 class="kw">input class="type">int A2 = class="num">0; class=class="str">"cmt">//延时带2的衰减,单位dB class="kw">input class="type">int Delay=class="num">0; class=class="str">"cmt">//延迟,以柱形个数计 class="kw">input class="type">class="kw">double Ripple=class="num">0.08; class=class="str">"cmt">//带宽中的波动,单位dB class="kw">input class="type">int FILTERShift=class="num">0; class=class="str">"cmt">//移动平均的水平方向偏移,以柱形计 class=class="str">"cmt">//---- 声明并初始化一个变量来存储已计算的柱形的数量 class="type">int FILTERPeriod; class=class="str">"cmt">//---- 声明动态数组, class=class="str">"cmt">// 将用作指标的缓存 class="type">class="kw">double FILTERTable[]; class=class="str">"cmt">//---- 计算数字滤波器的系数并确定FILTERTable[] 缓存的大小 class="type">class="kw">double Array[class="num">1500]; FILTERPeriod=DigitalFilter(FType,P1,D1,A1,P2,D2,A2,Ripple,Delay,Array); class=class="str">"cmt">//---- 改变数字滤波器所需系数的FILTERTable[]缓存的大小 if(FILTERPeriod<=class="num">0) { Print("Input parameters are incorrect. Indicator can&class="macro">#x27;t operate!"); class="kw">return; } class=class="str">"cmt">//---- 将大小为1500的临时数组中的数据,复制到大小为FILTERPeriod的主数组中 ArrayCopy(FILTERTable,Array,class="num">0,class="num">0,FILTERPeriod);
「MT5 里搭数字滤波器的 DLL 与参数门槛」
想在 MT5 里跑 FATL、SATL 这类数字滤波器,核心不是写指标逻辑,而是先把 DF.dll 和它依赖的数学库就位。DF.dll 必须丢进 \MetaTrader 5\MQL5\Libraries\,而 bdsp.dll、lapack.dll、mkl_support.dll 这三个辅助库,32 位系统放 C:\Windows\System32\,64 位系统放 C:\Windows\SysWOW64\,少一个都会在加载时直接报错。 终端那边也要开权限:Tools→Options→Expert Advisors 里把“允许加载 DLL”勾上,否则指标连初始化都过不了。这两步没做之前,后面调任何滤波器参数都是白搭。 滤波器类型用枚举收口比裸 int 更不容易写错:LPF 对应低通(FATL/SATL/KGLP),HPF 是高通(KGHP),BPF 是带通(RBCI/KGBP),BSF 是带阻(KGBS)。低通和高通只需看 P1、D1、A1,P2/D2/A2 可忽略;带通和带阻必须满足 D2>P2>P1>D1 的周期嵌套关系。 下面这段是滤波器卷积计算的骨架,FILTERTable 存的是系数、price 是输入序列,循环把最近 FILTERPeriod 根 K 线的加权求和出来: //---- 数字滤波器计算公式 FILTER=0.0; for(iii = 0; iii<FILTERPeriod; iii++) FILTER+= FILTERTable[iii] * price[bar - iii]; 外汇和贵金属波动受杠杆与消息面影响极大,滤波出来的平滑线只反映历史周期结构,对突发跳空没有先验防御,实盘使用前建议在策略测试器里用至少 3 个月 tick 数据回测验证延迟表现。
class=class="str">"cmt">//---- 数字滤波器计算公式 FILTER=class="num">0.0; for(iii = class="num">0; iii<FILTERPeriod; iii++) FILTER+= FILTERTable[iii] * price[bar - iii]; class="kw">input class="type">int FType = class="num">0; class=class="str">"cmt">//滤波器类型 class=class="str">"cmt">//class="num">0 - 低通滤波器 (FATL/SATL/KGLP),class="num">1 - 高通滤波器 (KGHP),class="num">2 - 带通滤波器 (RBCI / KGBP),class="num">3 - 带阻滤波器 (KGBS) class=class="str">"cmt">//---- 声明和初始化数字滤波器类型 enum FType_ class=class="str">"cmt">//滤波器类型 { LPF, class=class="str">"cmt">//低通滤波器 (FATL/SATL/KGLP) HPF, class=class="str">"cmt">//高通滤波器 (KGHP) BPF, class=class="str">"cmt">//带通滤波器 (RBCI/KGBP) BSF, class=class="str">"cmt">//带阻滤波器 (KGBS) }; class="kw">input FType_ FType = LPF; class=class="str">"cmt">//滤波器类型
◍ 数字滤波器指标的头文件配置与参数入口
在 MT5 里写一个数字滤波类指标,第一步是把线的外观和输入参数定死。下面这段声明把指标线设成深紫色(DarkViolet)、实线、2 像素宽,标签叫 DFilter,挂在数据窗口里一眼能认出来。 滤波器类型用枚举 FType_ 收口:LPF 低通、HPF 高通、BPF 带通、BSF 带阻,分别对应 FATL/SATL/KGLP、KGHP、RBCI/KGBP、KGBS 这几类经典数字滤波算法。默认给 LPF,也就是低通,用来剥掉高频噪声、留住趋势主轴。 输入参数里 P1=28、D1=19、A1=40 是一组实测好用的低通默认值:截断周期 28 根 K 线,稳态截断 19 根,延时带衰减 40dB。P2/D2/A2 留 0 表示带通/带阻的第二频段暂不启用;Delay=0、Ripple=0.08、FILTERShift=0 控制延迟、通带波动和水平偏移。外汇和贵金属波动跳变多,参数直接照搬可能过拟合,建议先在 XAUUSD 的 M15 上跑一遍看平滑度。 核心计算不写在指标里,而是走 #import "DF.dll" 调外部 DigitalFilter() 函数,传入 10 个参数、回写 array[]。这种 DLL 剥离写法能让 MT5 编译快、逻辑藏在二进制里,但也意味着你要先有 DF.dll 才能编译通过,否则连 OnInit 都进不去。
class=class="str">"cmt">//---- 指标线的颜色为蓝色 class="macro">#class="kw">property indicator_color1 DarkViolet class=class="str">"cmt">//---- 指标线是连续的曲线 class="macro">#class="kw">property indicator_style1 STYLE_SOLID class=class="str">"cmt">//---- 指标线的宽度为2个像素 class="macro">#class="kw">property indicator_width1 class="num">2 class=class="str">"cmt">//---- 显示指标的标签 class="macro">#class="kw">property indicator_label1 "DFilter" class=class="str">"cmt">//---- 声明和初始化数字滤波器类型 enum FType_ class=class="str">"cmt">//滤波器类型 { LPF, class=class="str">"cmt">//低通滤波器 (FATL/SATL/KGLP) HPF, class=class="str">"cmt">//高通滤波器 (KGHP) BPF, class=class="str">"cmt">//带通滤波器 (RBCI/KGBP) BSF, class=class="str">"cmt">//带阻滤波器 (KGBS) }; class=class="str">"cmt">//---- 指标的输入参数 class="kw">input FType_ FType=LPF; class=class="str">"cmt">//滤波器类型 class=class="str">"cmt">//class="num">0 - 低通滤波器 (FATL/SATL/KGLP),class="num">1 - 高通滤波器 (KGHP), class=class="str">"cmt">//class="num">2 - 带通滤波器 (RBCI / KGBP),class="num">3 - 带阻滤波器 (KGBS) class="kw">input class="type">int P1 = class="num">28; class=class="str">"cmt">//截断周期1,以柱形个数计 class="kw">input class="type">int D1 = class="num">19; class=class="str">"cmt">//稳态过程截断周期1,以柱形个数计 class="kw">input class="type">int A1 = class="num">40; class=class="str">"cmt">//延时带1的衰减,单位dB class="kw">input class="type">int P2 = class="num">0; class=class="str">"cmt">//截断周期2,以柱形个数计 class="kw">input class="type">int D2 = class="num">0; class=class="str">"cmt">//稳态过程截断周期2,以柱形个数计 class="kw">input class="type">int A2 = class="num">0; class=class="str">"cmt">//延时带2的衰减,单位dB class="kw">input class="type">int Delay=class="num">0; class=class="str">"cmt">//延迟,以柱形个数计 class="kw">input class="type">class="kw">double Ripple=class="num">0.08; class=class="str">"cmt">//带宽中的波动,单位dB class="kw">input class="type">int FILTERShift=class="num">0; class=class="str">"cmt">//移动平均的水平方向偏移,以柱形计 class=class="str">"cmt">//---- 引用DLL class="macro">#class="kw">import "DF.dll" class="type">int DigitalFilter(class="type">int FType,class="type">int P1,class="type">int D1,class="type">int A1,class="type">int P2,class="type">int D2,class="type">int A2,class="type">class="kw">double Ripple,class="type">int Delay,class="type">class="kw">double &array[]); class="macro">#class="kw">import class=class="str">"cmt">//---- 声明并初始化一个变量来存储已计算的柱形的数量 class="type">int FILTERPeriod; class=class="str">"cmt">//---- 声明动态数组, class=class="str">"cmt">// 将用作指标的缓存 class="type">class="kw">double ExtLineBuffer[]; class=class="str">"cmt">//---- 声明和初始化数组用于数字滤波器的系数 class="type">class="kw">double FILTERTable[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnInit() { class=class="str">"cmt">//----+ class=class="str">"cmt">//---- 将ExtLineBuffer动态数组变换为指标缓存 SetIndexBuffer(class="num">0,ExtLineBuffer,INDICATOR_DATA); class=class="str">"cmt">//---- FILTERShift,指标的水平偏移 PlotIndexSetInteger(class="num">0,PLOT_SHIFT,FILTERShift); class=class="str">"cmt">//----设置指标的开始绘制位置 PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,FILTERPeriod); class=class="str">"cmt">//---- 初始化指标简短名称的变量 class="type">class="kw">string shortname; StringConcatenate(shortname,"FILTER(",FILTERShift,")"); class=class="str">"cmt">//---- 创建数据窗口显示的标签 PlotIndexSetString(class="num">0,PLOT_LABEL,shortname);
数字滤波器指标的初始化与卷积计算
在 MT5 自定义指标里,初始化阶段先给窗口短名和显示精度定调:IndicatorSetString 设短名,IndicatorSetInteger 把小数位拉到 _Digits+1,比报价多一位,避免滤波后细微差值被吞掉。PlotIndexSetDouble 把空值定为 0.0,防止误绘。 DigitalFilter 先在 1500 长度的临时数组里算系数,返回有效阶数 FILTERPeriod;若参数非法返回 ≤0,直接 Print 报错并 return,指标不画。ArrayCopy 再把前 FILTERPeriod 个系数搬进主数组 FILTERTable,滤波器长度就此锁定。 OnCalculate 进来先卡柱子数:rates_total 小于 FILTERPeriod-1+begin 就返回 0,数据不够不硬算。首根计算位 first 在 prev_calculated==0 时设为 FILTERPeriod-1+begin;若 begin>0 还要用 PlotIndexSetInteger 把绘制起点再推后,因为价格是别的基础指标喂进来的。 主循环就是标准卷积:对每个 bar,把 FILTERTable[iii] 和 price[bar-iii] 从 iii=0 加到 FILTERPeriod-1,写进 ExtLineBuffer[bar]。外汇与贵金属杠杆高、滑点大,这类滤波值在极端行情可能滞后,上 MT5 用 EURUSD 的 M1 跑一遍,调 FILTERPeriod 看平滑与延迟的取舍。
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); class=class="str">"cmt">//---- 计算数字滤波器的系数并确定FILTERTable[] 缓存的大小 class="type">class="kw">double Array[class="num">1500]; FILTERPeriod=DigitalFilter(FType,P1,D1,A1,P2,D2,A2,Ripple,Delay,Array); class=class="str">"cmt">//---- 改变数字滤波器所需系数的FILTERTable[]缓存的大小 if(FILTERPeriod<=class="num">0) { Print("Input parameters are incorrect. Indicator can&class="macro">#x27;t operate!"); class="kw">return; } class=class="str">"cmt">//---- 将大小为1500的临时数组中的数据,复制到大小为FILTERPeriod的主数组中 ArrayCopy(FILTERTable,Array,class="num">0,class="num">0,FILTERPeriod); class=class="str">"cmt">//----+ } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 自定义指标迭代函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate( class="kw">const class="type">int rates_total, class=class="str">"cmt">// 当前报价下的历史柱形数据 class="kw">const class="type">int prev_calculated, class=class="str">"cmt">// 先前报价下的历史柱形数据 class="kw">const class="type">int begin, class=class="str">"cmt">// 开始有效计算的柱形位置 class="kw">const class="type">class="kw">double &price[] class=class="str">"cmt">// 用于指标计算的价格数组 ) { class=class="str">"cmt">//----+ class=class="str">"cmt">//---- 检查用于计算的柱形数量是否足够 if(rates_total<FILTERPeriod-class="num">1+begin) class="kw">return(class="num">0); class=class="str">"cmt">//---- 声明局部变量 class="type">int first,bar,iii; class="type">class="kw">double Sum,FILTER; class=class="str">"cmt">//---- 柱形重算循环中,计算“第一个”开始值 if(prev_calculated==class="num">0) class=class="str">"cmt">// 检查第一个指标值的计算 { first=FILTERPeriod-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+FILTERPeriod); } else first=prev_calculated-class="num">1; class=class="str">"cmt">// 新柱形计算的开始位置 class=class="str">"cmt">//---- 指标计算的主循环 for(bar=first; bar<rates_total; bar++) { class=class="str">"cmt">//---- 数字滤波器计算公式 FILTER=class="num">0.0; for(iii = class="num">0; iii<FILTERPeriod; iii++) FILTER+= FILTERTable[iii] * price[bar - iii]; class=class="str">"cmt">//---- 用FILTER的值初始化指标缓存单元 ExtLineBuffer[bar]=FILTER; } class=class="str">"cmt">//----+ class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
「别急着下结论」
把前面那些指标和滤波器的代码跑完一遍,你会发现从最朴素的 SMA 到带 DLL 的 FTL 滤波,逻辑链条其实很顺:只要从单缓冲区的均线起步,再逐步叠加多缓冲区和外部库,复杂度是线性爬升的,不是突然跳崖。 附件里 dfilter_zh.mq5 是 12.45 KB、fatl_zh.mq5 是 9.74 KB,这种体量说明实盘里想用,先得解决 64 位 MT5 加载 32 位 DLL 的现实坑——社区里有人卡在 'DF.dll 不是 64 位版本' 上,只能退到虚拟机跑 32 位终端取系数。 所以代码能跑通不等于策略能赚钱。外汇和贵金属杠杆高、滑点凶,这类滤波信号在 M1 到 250 分钟多周期验证前,倾向只是概率优势,别把回测里的顺畅当成实盘必然。开 MT5 把 sma_1_zh.mq5 先挂上图表,肉眼比对信号滞后,再决定要不要往下啃 DLL 那层。