初学者以 MQL5 实现对数字滤波器的实际实施·综合运用
🔧

初学者以 MQL5 实现对数字滤波器的实际实施·综合运用

(3/3)·从编译报错定位到客户端滤波器搭建,新手最易卡在看不见的逻辑错

案例拆解 第 3/3 篇
很多人以为编译通过就万事大吉,其实 MQL5 编译器只抓语法错,逻辑错照样让指标在盘面上静默失效。故意删个分号、改个等号,比看十遍文档更懂报错信息怎么读。这一篇就把滤波器从代码到客户端跑通的最后几步拆给你看。

用 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 策略测试器用历史数据验证系数表现,杠杆品种波动剧烈,存在显著回撤风险。

MQL5 / C++
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,做低通或高通时那三个值填了也会被忽略,别在面板里纠结它们。

MQL5 / C++
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 可能让曲线对短线噪音更敏感,但外汇与贵金属杠杆高,参数乱调会放大误判风险。

MQL5 / C++
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 调小一档可直观比对响应速度。

MQL5 / C++
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 位虚拟机取系数——工具是手段,别反过来被它拴住。

把重复编译排查交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到编译异常与逻辑校验提示,把重复劳动交给小布,你专注决策。

常见问题

一个错误常引发一串报错,优先双击 Errors 选项卡第一行定位修正,重新编译后再处理下一组,别从头挨个改。
编译器只管语法,逻辑错需人工核对;小布盯盘的 AIGC 模块可标记常见除零、条件误写等风险点,减少漏网之鱼。
函数签名由系统固定调用约定约束,擅自增删参数破坏接口匹配,编译直接报错且无法被客户端加载。
这属于逻辑错而非语法错,编译器不拦截,循环方向颠倒会让缓冲区按相反顺序填充,需手动复查遍历逻辑。
多基于内置数组与 iCustom 调用,重点在缓冲区绑定与滤波系数初始化,案例拆解可见本篇对应小节。