初学者以 MQL5 实现对数字滤波器的实际实施·综合运用
(3/3)·从编译报错定位到客户端滤波器搭建,新手最易卡在看不见的逻辑错
用 DF.dll 在 MT5 搭一个通用数字滤波器
MT4 时代 Sergei Ilyuhin 写的 DF.dll 已经把「单指标构建任意数字滤波器」这件事解决掉了,MT5 直接复用这套 DLL 即可,不用再为每种滤波逻辑单独造轮子。核心就是 DigitalFilter() 这个函数:丢进去 10 个参数,它用引用方式把系数写进一个数组,并返回滤波器长度。 函数要求接收系数的数组尺寸给到 1500,但实际在 OnCalculate() 里常用的只有 100–200 个点。所以在 OnInit() 里声明局部 Array[1500] 承接全部系数,再把前 FILTERPeriod 个拷进全局动态数组 FILTERTable[],退出初始化后大数组自动销毁,只留必需数据,省内存也干净。 输入参数里 FType 用 0–3 的数字不够直观,MQL5 的枚举类型可以把它改成 LPF / HPF / BPF / BSF 这种名字,对应低通、高通、带通、带阻。改完后在指标对话框里选类型就不是猜数字了。 下面这段是 DLL 引入、输入声明和初始化拿系数的关键代码,复制到 mq5 里就能编译跑。外汇和贵金属行情高频跳动,这类滤波器对参数敏感,实盘前务必在 MT5 策略测试器用历史数据验证系数表现,杠杆品种波动剧烈,存在显著回撤风险。
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);
「四类数字滤波器的参数约束与 DLL 部署」
在 MT5 里做价格行为滤波,先得认清楚四种滤波器类型的边界:低通(LPF,对应 FATL/SATL/KGLP)、高通(HPF,对应 KGHP)、带通(BPF,对应 RBCI/KGBP)、带阻(BSF,对应 KGBS)。它们不是随便填参数就能跑的,周期参数有硬约束——低通要求 P1>D1,高通要求 P1<D1,而带通和带阻必须满足 D2>P2>P1>D1,否则滤波器的幅频响应会直接失真。 调用外部计算核心时,DF.dll 必须丢进 \MetaTrader 5\MQL5\Libraries\ 目录,它依赖 bdsp.dll、lapack.dll、mkl_support.dll 三个数学库。32 位系统放 C:\Windows\System32\,64 位系统放 C:\Windows\SysWOW64\;同时终端的「允许加载 DLL」选项(Tools→Options→Expert Advisors)要勾上,不然指标初始化会直接失败。 下面这段是滤波器卷积计算的骨架,FILTERTable 存的是系数、price 是输入序列: FILTER=0.0; for(iii = 0; iii<FILTERPeriod; iii++) FILTER+= FILTERTable[iii] * price[bar - iii]; 逐行看:第一行把累加器清零;for 循环从 0 扫到 FILTERPeriod-1,用系数表对历史价格做加权和;每次把第 iii 个系数乘上 bar-iii 处的价格累进去,最终 FILTER 就是这一根 K 线的滤波输出。 参数面板里 FType 用枚举比裸 int 更稳,LPF/HPF/BPF/BSF 直接可读。带通和带阻才用得到 P2、D2、A2,做低通或高通时那三个值填了也会被忽略,别在面板里纠结它们。
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 里做自定义数字滤波指标,第一步是把显示属性与滤波器类型先钉死。下面这段声明把第一条指标线设成深紫实线、宽度 2 像素,标签写 DFilter,意味着你在数据窗口看到的不是默认灰线,而是一条可一眼区分的滤波输出。 滤波器类型用枚举锁死四种路数:LPF 低通(对应 FATL/SATL/KGLP)、HPF 高通(KGHP)、BPF 带通(RBCI/KGBP)、BSF 带阻(KGBS)。输入参数里 P1=28、D1=19、A1=40 是低通默认组,P2/D2/A2 全 0 代表二阶参数未启用;Delay=0、Ripple=0.08dB 控制延时与通带波动,FILTERShift=0 则是水平偏移。 核心计算不写在 mq5 里,而是走外部 DF.dll 的 DigitalFilter() 函数,传入 10 个参数拿回数组引用。OnInit() 中把 ExtLineBuffer 绑成指标缓存 0,用 PlotIndexSetInteger 设偏移与绘制起点,StringConcatenate 拼出 "FILTER(0)" 短名。开 MT5 新建指标时,照这套 input 抄一遍,改 P1 到 14 可能让曲线对短线噪音更敏感,但外汇与贵金属杠杆高,参数乱调会放大误判风险。
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 大小的 double 数组 Array 承接,返回的 FILTERPeriod 才是真正可用的滤波器长度。若它小于等于 0,直接 Print 报错并 return,指标不往下跑——参数填错时这是第一道闸。 ArrayCopy 把 Array 前 FILTERPeriod 个系数搬进主数组 FILTERTable,临时的大数组就此作废。OnCalculate 进来先卡柱子数:rates_total 小于 FILTERPeriod-1+begin 就返回 0,数据不够不硬算。 首根计算位置 first 在 prev_calculated==0 时设为 FILTERPeriod-1+begin;如果 begin>0,再用 PlotIndexSetInteger 把 PLOT_DRAW_BEGIN 推到 begin+FILTERPeriod,防止前面几根用了未就绪的引用。后续新柱只从 prev_calculated-1 续算,省 CPU。 主循环就是标准的 FIR 卷积:对每个 bar,FILTER 清零后跑 iii 从 0 到 FILTERPeriod-1,累加 FILTERTable[iii]*price[bar-iii],写进 ExtLineBuffer[bar]。外汇与贵金属杠杆高、滑点大,这类滤波值在实盘可能滞后于突变行情,上 MT5 把 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">//+------------------------------------------------------------------+
「把工具请下神坛」
跑完前面几节里 dfilter、fatl、sma 那一串指标代码后会发现,所谓高级滤波其实就是从最朴素的均线起步,再一层层加差分和限带逻辑。sma.mq5 只有 5.33 KB,fatl_zh.mq5 也不过 9.74 KB,体积都不大,但把价格序列从易到难拆开看,每一步都合逻辑。 外汇和贵金属市场高杠杆、滑点无常,这类自定义指标只能帮你看清结构,不能替你屏蔽亏损概率。真要验证效果,直接把附带的 dfilter_zh.mq5 拖进 MT5 用 M1 到 250 分钟多周期跑一遍,比读任何结论都实在。 写代码时若卡在 64 位 MT 加载不了旧 DLL,就像评论区有人被迫开 32 位虚拟机取系数——工具是手段,别反过来被它拴住。