初学者以 MQL5 实现对数字滤波器的实际实施

Nikolay Kositsin | 27 十一月, 2013

简介

在我的前中,对简单指标进行了代码分析,并稍稍涉及该指标与 MetaTrader 5 客户端的交互。现在,在我们继续前,我们应在 MetaEditor 的 "Toolbox"(工具箱)窗口的 "Errors"(错误)选项卡中仔细查看“EA 交易”的编译结果。在此基础上,您可以开始对我早先提议的 SMA 指标的代码进行进一步的研究。

指标编译错误

在我们的情形中,当编译两个代码版本的任意一个时,在没有更改的情况下编译过程相当顺利,且编译结果符合预期:


没有错误发生,随扩展名为 .mq5 的指标文件出现的还有具有 .ex5 扩展名的类似文件。

一般来说,使用代码就无法规避错误。它们不断地被编程人员制造出来。为此,MetaEditor 具有检查编译代码所有错误类型的内置机制,并在找到错误后提供生成错误的完整列表。


要找到错误的位置,您只需在 "Toolbox"(工具箱)窗口中双击含有错误信息的相应的行。在大多数情况下,编译程序将使用相应的 图标准确指出错误所在的代码行。


有一件事情是您应该要考虑到的。代码中的一个错误可生成一系列的编译错误。因此,为了去除这一系列错误,前往编译程序找到的错误所在的第一行,然后更正代码。当然,可能有许多这样的错误序列。因此,在代码中修正一个错误后我们必须重新编译代码,如果编译程序找到错误,我们需要在 "Toolbox"(工具箱)窗口的 "Errors"(错误)选项卡中找到第一行。


也许,理解这些最有效的方法是对我们代码有意义的、破坏性的影响,以了解编译程序如何应对有意制造出来的错误。技巧十分简单 - 在代码的特定部分制造错误,按下 MetaEditor 中的 "Compile"(编译)按钮然后查看编译结果。如果您能凭直觉记下这些对代码有破坏性影响的结果就更好了。无论如何,在使用 MQL5 代码时,这对您的后续工作是很有用的。

下面是指标源代码中可能的破坏性更改列表:

  1. 在任何运算符或变量中加入空格。
  2. 清除分号 ";"。
  3. 在代码的不同部分添加分号 ";"。
  4. 删除运算符。
  5. 删除或添加大括号或小括号。
  6. 删除逗号 ","。
  7. 在 OnCalculate() 函数中添加额外的输入参数。
  8. 变量的除数为零。
  9. 在 "if" 运算符代码行中用 "=" 取代 "==" 标记。
  10. 将变量的增长方向从 bar++ 改为 bar--。

当然,编译程序并不总是找出错误所在的位置。这就是这些准备工作值得完成以了解如何处理这种情况的原因之所在。好了,关于错误还有一个问题需要说明 - MetaEditor 编译程序只能确定 MQL5 语言本身的错误,在大多数情况下无法发现编程的逻辑错误!

您的 MQL5 词汇量

如果您聆听任何特定个人的说话,您会发现,相较任意人类语言的丰富性,说话人仅使用了一小部分词汇来表达思想和需求。事实表明,在大多数情况下人们使用的词汇量远远小于他们的可用词汇量。同样的原则也适用于 MQL5。起初,在掌握 MQL5 语言时,您要习惯该编程语言最常用的运算符和表达式。随着您学会这门语言,您可以逐渐扩大您的实际词汇量。

例如,您可以使用四种类型的变量(intdoubleboolstring)、if-else 条件运算符、for 循环运算符、{} compound 运算符和return 运算符。您还需全面学习如何使用分号 ";" 和逗号 ","。也许,学习数学和三角函数是最明智的。这些工具对于训练和练习您的初步编程技巧而言是绰绰有余了。

进一步改良指标

MQL5 可改良显示于 MetaTrader 客户端中的指标,这一过程相当简单和标准化。它们包括全局层面运算符

//---- 指标的作者
#property copyright "2010, MetaQuotes Software Corp."
//---- 作者网站链接
#property link      "https://www.mql5.com"
//---- 指标版本号
#property version   "1.00"
//----在主窗口中绘制指标
#property indicator_chart_window
//---- 一个缓存用于计算和绘制指标
#property indicator_buffers 1
//---- 仅使用一种图形来绘制
#property indicator_plots   1
//---- 画指标线
#property indicator_type1   DRAW_LINE
//---- 红色用于指标线的颜色
#property indicator_color1  Red
//---- 指标线是连续的曲线
#property indicator_style1  STYLE_SOLID
//---- 指标线的宽度为1
#property indicator_width1  1
//---- 显示指标的标签
#property indicator_label1  "SMA"

以及包括 OnInit()函数调用

//---- 初始化指标简短名称的变量
   string shortname;
   StringConcatenate(shortname,"FATL(",FATLShift,")");
//--- 创建数据窗口显示的标签
   PlotIndexSetString(0,PLOT_LABEL,shortname);
//---创建在独立窗口和工具提示中显示的名称
   IndicatorSetString(INDICATOR_SHORTNAME,shortname);
//--- 确定指标值的显示精度
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- 禁止显示空值
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
StringConcatenate() 函数使用下述公式组合指标名称的字符串:
   shortname = shortname + "SMA(" + MAPeriod + "," + MAShift + ")";

根据《将指标应用至其他指标》一文的建议,将 PlotIndexSetInteger() 函数调用添加到 OnCalculate() 中没有什么害处:

//---- 柱形重算循环中,计算“第一个”开始值
   if(prev_calculated==0)        //检查第一个指标值的计算
     {
      first=FATLPeriod-1+begin;  // 所有柱形的开始计算位置
      //--- 在“begin”柱的基础上增加数据开始位,因为 
      //   此计算是基于另外一个指标的值
      if(begin>0)
         PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod);
     
}
   else first=prev_calculated-1; // 新柱形计算的开始位置
在包含这些额外的代码后,指标的大小和复杂程度略有增加,这是自然的,现在指标的接口更加用户友好了。

作为新指标创建模板的先前工作的结果

所有这些无疑是令人感兴趣的,但还有一个很自然的问题 - 为什么要从零开始并重复指标的代码,而客户端中已经有两种版本的代码可用?一种是形式为 Moving Average.mq5 的技术指标,一种是 Custom Moving Average.mq5 自定义指标。答案很简单。要学习如何快速编写相似指标的代码,就用我以前提供的 SMA 指标代码作为模板,从而尽可能地节省您的智力资源。例如,您可以尝试以 MQL5 编写数字滤波器的代码,比如来自 Finware 的 FATL。

通常,计算数字滤波器的公式如下所示:

滤波器 = SUM (K(i) * CLOSE (i), FilterPeriod)

其中:

该公式和 SMA 指标公式没有太大的不同:

SMA = SUM ((1 / MAPeriod ) * CLOSE (i), MAPeriod)

不同之处在于,在其上进行数字滤波器计算的周期是严格固定且对于具体数字滤波器是个别的,K(i) 加权系数亦是如此。加权系数本身以及数字滤波器周期使用专门的算法进行计算。对这些算法的分析超出了本文的范围,因此我们仅限于使用 FATL 数字滤波器的现成值。对数字信号滤波的原理感兴趣的朋友可访问数字方法发生器网站(俄语)。MQL4 中 FATL 指标变体的公式并不是什么秘密:

     FATL =  0.4360409450 * Close[bar + 0]
           + 0.3658689069 * Close[bar + 1]
           + 0.2460452079 * Close[bar + 2]
           + 0.1104506886 * Close[bar + 3]
           - 0.0054034585 * Close[bar + 4]
           - 0.0760367731 * Close[bar + 5]
           - 0.0933058722 * Close[bar + 6]
           - 0.0670110374 * Close[bar + 7]
           - 0.0190795053 * Close[bar + 8]
           + 0.0259609206 * Close[bar + 9]
           + 0.0502044896 * Close[bar + 10]
           + 0.0477818607 * Close[bar + 11]
           + 0.0249252327 * Close[bar + 12]
           - 0.0047706151 * Close[bar + 13]
           - 0.0272432537 * Close[bar + 14]
           - 0.0338917071 * Close[bar + 15]
           - 0.0244141482 * Close[bar + 16]
           - 0.0055774838 * Close[bar + 17]
           + 0.0128149838 * Close[bar + 18]
           + 0.0226522218 * Close[bar + 19]
           + 0.0208778257 * Close[bar + 20]
           + 0.0100299086 * Close[bar + 21]
           - 0.0036771622 * Close[bar + 22]
           - 0.0136744850 * Close[bar + 23]
           - 0.0160483392 * Close[bar + 24]
           - 0.0108597376 * Close[bar + 25]
           - 0.0016060704 * Close[bar + 26]
           + 0.0069480557 * Close[bar + 27]
           + 0.0110573605 * Close[bar + 28]
           + 0.0095711419 * Close[bar + 29]
           + 0.0040444064 * Close[bar + 30]
           - 0.0023824623 * Close[bar + 31]
           - 0.0067093714 * Close[bar + 32]
           - 0.0072003400 * Close[bar + 33]
           - 0.0047717710 * Close[bar + 34]
           + 0.0005541115 * Close[bar + 35]
           + 0.0007860160 * Close[bar + 36]
           + 0.0130129076 * Close[bar + 37]
           + 0.0040364019 * Close[bar + 38]; 

与 MQL4 中的不同,在 MQL5 中,指标缓冲区中柱的计算是有方向的。因此,为了在 MQL5 指标中使用该公式,我们必须使用减量操作替换括号内的增量操作。同样地,由于 MQL5 中不存在 Close[] 时序数组,我们必须使用更合适的变体 price[] 来代替它。在 MetaEditor 中,使用下述菜单命令以自动执行该任务是很自然的:


反复出现的 Close [bar + 表达式应使用 price [bar - 予以替换:


在该对话框中,单击 "Replace All"(全部替换)按钮。如此一来,我们获得所需的 MQL5 FATL 指标公式计算:

     FATL =  0.4360409450 * price[bar - 0]
           + 0.3658689069 * price[bar - 1]
           + 0.2460452079 * price[bar - 2]
           + 0.1104506886 * price[bar - 3]
           - 0.0054034585 * price[bar - 4]
           - 0.0760367731 * price[bar - 5]
           - 0.0933058722 * price[bar - 6]
           - 0.0670110374 * price[bar - 7]
           - 0.0190795053 * price[bar - 8]
           + 0.0259609206 * price[bar - 9]
           + 0.0502044896 * price[bar - 10]
           + 0.0477818607 * price[bar - 11]
           + 0.0249252327 * price[bar - 12]
           - 0.0047706151 * price[bar - 13]
           - 0.0272432537 * price[bar - 14]
           - 0.0338917071 * price[bar - 15]
           - 0.0244141482 * price[bar - 16]
           - 0.0055774838 * price[bar - 17]
           + 0.0128149838 * price[bar - 18]
           + 0.0226522218 * price[bar - 19]
           + 0.0208778257 * price[bar - 20]
           + 0.0100299086 * price[bar - 21]
           - 0.0036771622 * price[bar - 22]
           - 0.0136744850 * price[bar - 23]
           - 0.0160483392 * price[bar - 24]
           - 0.0108597376 * price[bar - 25]
           - 0.0016060704 * price[bar - 26]
           + 0.0069480557 * price[bar - 27]
           + 0.0110573605 * price[bar - 28]
           + 0.0095711419 * price[bar - 29]
           + 0.0040444064 * price[bar - 30]
           - 0.0023824623 * price[bar - 31]
           - 0.0067093714 * price[bar - 32]
           - 0.0072003400 * price[bar - 33]
           - 0.0047717710 * price[bar - 34]
           + 0.0005541115 * price[bar - 35]
           + 0.0007860160 * price[bar - 36]
           + 0.0130129076 * price[bar - 37]
           + 0.0040364019 * price[bar - 38];

我们刚刚已经讨论过计算算法,现在可以开始对指标进行编码了。为此,首先在 MetaEditor 中打开 SMA_1_en.mq5 指标,然后将其另存为 FATL_en.mq5。指标模板已经就绪,现在我们需要在模板中替换指标计算算法并对某些变量做一些更改,这主要是表面层次的更改。您应该选择最后提到的 FATL 滤波器计算公式的整个代码块,并将它复制到 Windows 的剪贴板上。然后,在 FATL.mq5 指标代码中,删除循环运算符内除指标缓冲区最后初始化以外的所有代码:

//---- 指标计算的主循环
   for(bar=first; bar<rates_total; bar++)
     {
     


      //---- 用FATL初始化指标缓存单元
      ExtLineBuffer[bar]=FATL;
     
}

而删除的这部分代码,我们将从 Windows 剪贴板粘贴 FATL 数字滤波器计算算法予以取代。然后,我们应使用上文提到的替换程序,将文字 SMA 替换成更合适的 FATL。同样地,我们应分别将 MAPeriod 和 MAShift 输入变量的名称替换为 FATLPeriod 和 FATLShft。FATLPeriod 变量应从外部变量中删除,因为它具有大小为 39 的固定值。基于同样的原因,还需将其从 OnInit() 函数的 StringConcatenate() 运算符中删除。现在 iii 局部变量已没有存在的必要,因此可将其删除。最后,您可以将指标线的颜色改成蓝色,并稍稍加粗线条。

在对 SMA_1_en.mq5 代码进行了这些简单的操作后,我们获得了所需的指标代码 FATL_en.mq5

//+------------------------------------------------------------------+
//|                                                      Fatl_en.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
//---- 指标的作者
#property copyright "2010, MetaQuotes Software Corp."
//---- 作者网站链接
#property link      "https://www.mql5.com"
//---- 指标版本号
#property version   "1.00"
//----在主窗口中绘制指标
#property indicator_chart_window
//---- 一个缓存用于计算和绘制指标
#property indicator_buffers 1
//---- 仅使用一种图形来绘制
#property indicator_plots   1
//---- 画指标线
#property indicator_type1   DRAW_LINE
//---- 指标线的颜色为蓝色
#property indicator_color1  Blue
//---- 指标线是连续的曲线
#property indicator_style1  STYLE_SOLID
//---- 指标线的宽度为2个像素
#property indicator_width1  2
//---- 显示指标的标签
#property indicator_label1  "FATL"

//---- 指标的输入参数
input int FATLShift=0; // FATL horizontal shift in bars

//---- 声明并初始化一个变量来存储已计算的柱形的数量
int FATLPeriod=39;

//---- 声明动态数组, 
//     将用作指标的缓存
double ExtLineBuffer[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                |
//+------------------------------------------------------------------+  
void OnInit()
  {
//----+
//---- 将ExtLineBuffer动态数组变换为指标缓存
   SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//---- FILTERShift,指标的水平偏移
   PlotIndexSetInteger(0,PLOT_SHIFT,FATLShift);
//----设置指标的开始绘制位置
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,FATLPeriod);
//---- 初始化指标简短名称的变量
   string shortname;
   StringConcatenate(shortname,"FATL(",FATLShift,")");
//--- 创建数据窗口显示的标签
   PlotIndexSetString(0,PLOT_LABEL,shortname);
//---创建在独立窗口和工具提示中显示的名称
   IndicatorSetString(INDICATOR_SHORTNAME,shortname);
//--- 确定指标值的显示精度
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//--- 禁止显示空值
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//----+
  
}
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  |
//+------------------------------------------------------------------+
int OnCalculate(
                const int rates_total,     // 当前报价下的历史柱形数据
                const int prev_calculated, // 先前报价下的历史柱形数据
                const int begin,           // 开始有效计算的柱形位置
                const double &price[]      // 用于指标计算的价格数组
                )
  {
//----+   
//---- 检查用于计算的柱形数量是否足够
   if(rates_total<FATLPeriod-1+begin)
      return(0);

//---- 声明局部变量
   int first,bar;
   double Sum,FATL;

//---- 柱形重算循环中,计算“第一个”开始值
   if(prev_calculated==0)        //检查第一个指标值的计算
     {
      first=FATLPeriod-1+begin;  // 所有柱形的开始计算位置
      //--- 在“begin”柱的基础上增加数据开始位,因为 
      //   此计算是基于另外一个指标的值
      if(begin>0)
         PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod);
     
}
   else first=prev_calculated-1; // 新柱形计算的开始位置

//---- 指标计算的主循环
   for(bar=first; bar<rates_total; bar++)
     {
      //---- 
      FATL=0.4360409450*price[bar-0]
           + 0.3658689069 * price[bar - 1]
           + 0.2460452079 * price[bar - 2]
           + 0.1104506886 * price[bar - 3]
           - 0.0054034585 * price[bar - 4]
           - 0.0760367731 * price[bar - 5]
           - 0.0933058722 * price[bar - 6]
           - 0.0670110374 * price[bar - 7]
           - 0.0190795053 * price[bar - 8]
           + 0.0259609206 * price[bar - 9]
           + 0.0502044896 * price[bar - 10]
           + 0.0477818607 * price[bar - 11]
           + 0.0249252327 * price[bar - 12]
           - 0.0047706151 * price[bar - 13]
           - 0.0272432537 * price[bar - 14]
           - 0.0338917071 * price[bar - 15]
           - 0.0244141482 * price[bar - 16]
           - 0.0055774838 * price[bar - 17]
           + 0.0128149838 * price[bar - 18]
           + 0.0226522218 * price[bar - 19]
           + 0.0208778257 * price[bar - 20]
           + 0.0100299086 * price[bar - 21]
           - 0.0036771622 * price[bar - 22]
           - 0.0136744850 * price[bar - 23]
           - 0.0160483392 * price[bar - 24]
           - 0.0108597376 * price[bar - 25]
           - 0.0016060704 * price[bar - 26]
           + 0.0069480557 * price[bar - 27]
           + 0.0110573605 * price[bar - 28]
           + 0.0095711419 * price[bar - 29]
           + 0.0040444064 * price[bar - 30]
           - 0.0023824623 * price[bar - 31]
           - 0.0067093714 * price[bar - 32]
           - 0.0072003400 * price[bar - 33]
           - 0.0047717710 * price[bar - 34]
           + 0.0005541115 * price[bar - 35]
           + 0.0007860160 * price[bar - 36]
           + 0.0130129076 * price[bar - 37]
           + 0.0040364019 * price[bar - 38];

      //---- 用FATL初始化指标缓存单元
      ExtLineBuffer[bar]=FATL;
     
}
//----+     
   return(rates_total);
  
}
//+------------------------------------------------------------------+

编译后,可将指标应用至客户端中的图表进行测试:

FATL 指标的结果代码可作为构建其他类似滤波器的模板,这是很自然的。但现在问题变得更加简单。在我们的代码中,替换滤波器计算公式、用 DIGFILTER 替换文字 FATL 以及用滤波器所需维度初始化(现在)DIGFILTERPeriod 变量即已足够。

在客户端中创建数字滤波器的常用解决方案

我们刚刚讨论的指标是解决数字信号滤波这一普遍问题的一个变体。要是有一个代表常用解决方案、允许仅使用一个指标构建任何数字滤波器的指标就好了。对于 MetaTrader 4 客户端而言,这个问题在很久以前已使用 Sergei Ilyuhin 提供的 DF.dll 模块解决。因此,在 MetaTrader 5 中我们可以用它来轻松地解决问题。在该模块中,我们引入了 DigitalFilter() 函数:

DigitalFilter(int FType, int P1, int D1, int A1, int P2, int D2, int A2, double Ripple, int Delay, double& array[]); 

它允许您将数字滤波器系数作为 array[] 数组接收。函数使用 reference("&" 标记在该数组中位于此变量类型的声明之后)将数字滤波器系数写入此大小为 1500 的数组中。函数接收十个输入参数的值并返回数字滤波器的大小。因此,这足以构建通用数字滤波器。整个问题归结于基于全局层面在现有指标中组织 DLL 输入,在指标初始化代码块中获取系数数组,并基于这些系数在 OnCalculate() 中运行滤波器的通用计算。DigitalFilter() 函数的输入变量应置于指标的输入变量中。我们现在就开始。

导入 DF.dll 文件不会带来任何困难。它只有三行代码:

//---- 引入DLL 
#import "DF.dll"
int DigitalFilter(int FType, int P1, int D1, int A1, int P2, int D2, int A2, double Ripple, int Delay, double& array[]); 
#import

之后,将 DigitalFilter() 函数的所有外部变量作为指标的输入变量:

//---- 指标的输入参数
input FType_ FType=LPF;     //滤波器类型
                            //0 - 低通滤波器 (FATL/SATL/KGLP),1 - 高通滤波器 (KGHP), 
                            //2 - 带通滤波器 (RBCI / KGBP),3 - 带阻滤波器 (KGBS)
input int    P1 = 28;       //截断周期1,以柱形个数计
input int    D1 = 19;       //稳态过程截断周期1,以柱形个数计
input int    A1 = 40;       //延时带1的衰减,单位dB
input int    P2 = 0;        //截断周期2,以柱形个数计
input int    D2 = 0;        //稳态过程截断周期2,以柱形个数计
input int    A2 = 0;        //延时带2的衰减,单位dB
input int    Delay=0;       //延迟,以柱形个数计
input double Ripple=0.08;   //带宽中的波动,单位dB
input int    FILTERShift=0; //移动平均的水平方向偏移,以柱形计

在全局层面,我们将声明 FILTERPeriod 变量但不对其进行初始化:

//---- 声明并初始化一个变量来存储已计算的柱形的数量
int FILTERPeriod;

在全局层面,我们将声明一个动态数组用于存储滤波器系数:

//---- 声明动态数组, 
//     将用作指标的缓存
double FILTERTable[];

现在,我们考虑 OnInit() 函数的代码块。将 FILTERTable[] 数组用作 DigitalFilter() 函数的参数并不太合乎逻辑。为此,我们需要使数组的大小能容纳 1500 个元素,但其中仅 100 - 200 个元素会在 OnCalculate() 函数块中用到。在此情形下,我们在 OnInit() 函数中使用局部声明的 Array[1500] 数组反而会更好。来自该数组的必要数据量将被写入 FILTERTable[] 数组。在从 OnInit() 函数退出后,大规模的 Array[] 数组将被销毁,而必要数据将保留在 FILTERTable[] 数组中,其大小等于 FILTERPeriod 数字滤波器的长度。下面是用于此目的的代码变体:

//---- 计算数字滤波器的系数并确定FILTERTable[] 缓存的大小
   double Array[1500];
   FILTERPeriod=DigitalFilter(FType,P1,D1,A1,P2,D2,A2,Ripple,Delay,Array);
//----  改变数字滤波器所需系数的FILTERTable[]缓存的大小
   if(FILTERPeriod<=0)
     {
      Print("Input parameters are incorrect. Indicator can't operate!");
      return;
     
}
//---- 将大小为1500的临时数组中的数据,复制到大小为FILTERPeriod的主数组中
   ArrayCopy(FILTERTable,Array,0,0,FILTERPeriod);

OnCalculate() 函数中,滤波器计算的代码十分简单:

      //---- 数字滤波器计算公式
      FILTER=0.0;
      for(iii = 0; iii<FILTERPeriod; iii++)
         FILTER+= FILTERTable[iii] * price[bar - iii];

该指标代码的最终版本在 DFilter_en.mq5 文件中给出。我们可以对指标的接口稍作修改。事实上,指标输入变量的取值范围为 0-3。

input int FType = 0; //滤波器类型
                     //0 - 低通滤波器 (FATL/SATL/KGLP),1 - 高通滤波器 (KGHP),2 - 带通滤波器 (RBCI / KGBP),3 - 带阻滤波器 (KGBS)

这些值如果作为滤波器的名称而不是以数字形式出现,要更容易理解:0 - 低通滤波器 (FATL/SATL/KGLP)1 - 高通滤波器 (KGHP)2 - 带通滤波器 (RBCI/KGBP)3 - 带阻滤波器 (KGBS)。对于这种情况,MQL5 中存在名为枚举的特殊变量类型。在我们的示例中,我们需要在指标的输入参数前声明和初始化枚举:

//---- 声明和初始化数字滤波器类型
enum FType_ //滤波器类型
  {
   LPF, //低通滤波器 (FATL/SATL/KGLP)
   HPF, //高通滤波器 (KGHP)
   BPF, //带通滤波器 (RBCI/KGBP)
   BSF, //带阻滤波器 (KGBS)
  };

之后,我们需要在指标外部参数的声明中替换使用变量的类型:

input FType_ FType = LPF; //滤波器类型

如此一来,在指标的对话框中选择该参数的值的情形如下图所示:

与枚举声明一样,命名常量后跟单行注释,然后它们被选作输入参数。现在,我们有了通用数字滤波器源代码的最终版本:

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2010, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
/*
 * <<< METATRADER 5 的数字滤波器 >>> *
 *
 * DF.dll 文件应放在 "\MetaTrader 5\MQL5\Libraries\" 文件夹下。
 * DF.dll 需要另外三个DLLs,包括一个数学模块 
 * 处理 - bdsp.dll, lapack.dll, mkl_support.dll.
 * 对于Windows 32-位操作系统,这些 DLLs 必须安装到 "C:\Windows\System32\" 文件夹下 
 * 或者对于Windows 64-位操作系统,安装到 "C:\Windows\SysWOW64\" 文件夹下。 
 * 
 *
 * 在使用前,请确保:
 * 
 * 1. 终端设置中“允许加载DLL” 的选项已启用 
 *    (Tools->Options->Expert Advisors tab).
 * 2. 在"C:\Windows\System32\" 或"C:\Windows\SysWOW64\" 文件夹下
 *    Bdsp.dll, lapack.dll and mkl_support.dll 辅助数学库存在。
 *
 * 输入参数的描述:
 * 
 * Ftype  - 滤波器类型:0 - 低通滤波器 (FATL/SATL/KGLP), 1 - 高通滤波器 (KGHP),
 *          2 - 带通滤波器 (RBCI / KGBP), 3 - 带阻滤波器 (KGBS)
 * P1     - 截断周期1,以柱形个数计
 * D1     - 稳态过程截断周期1,以柱形个数计
 * A1     - 延时带1的衰减,单位dB
 * P2     - 截断周期2,以柱形个数计
 * D2     - 稳态过程截断周期1,以柱形个数计
 * A2     - 延时带2的衰减,单位dB
 * Ripple - 带宽, 单位dB
 * Delay  - 延迟,以柱形计
 *
 * 对于低通滤波器和HPF,P2, D2, A2 的值可忽略
 *
 * 条件:
 * 低通滤波器:                      P1>D1
 * 高通滤波器:                      P1<D1
 * 带通滤波器和带阻滤波器:D2>P2>P1>D1
 */
//+------------------------------------------------------------------+
//|      Digital Low Pass (FATL/SATL, KGLP) Filter    DFilter_en.mq5 | 
//|                    Digital Filter: Copyright (c) Sergey Ilyukhin |
//|                           Moscow, qpo@mail.ru  http://fx.qrz.ru/ |
//|                              MQL5 CODE: 2010,   Nikolay Kositsin |
//|                              Khabarovsk,   farria@mail.redcom.ru | 
//+------------------------------------------------------------------+
//---- 指标的作者
#property copyright "2005, Sergey Ilyukhin, Moscow"
//---- 作者网站链接
#property link      "http://fx.qrz.ru/"
//---- 指标版本号
#property version   "1.00"
//---- 在主窗口中绘制指标
#property indicator_chart_window
//---- 一个缓存用于计算和绘制指标
#property indicator_buffers 1
//---- 仅使用一种图形来绘制
#property indicator_plots   1
//---- 画指标线
#property indicator_type1   DRAW_LINE
//---- 指标线的颜色为蓝色
#property indicator_color1  DarkViolet
//---- 指标线是连续的曲线
#property indicator_style1  STYLE_SOLID
//---- 指标线的宽度为2个像素
#property indicator_width1  2
//---- 显示指标的标签
#property indicator_label1  "DFilter"
//---- 声明和初始化数字滤波器类型
enum FType_ //滤波器类型
  {
   LPF, //低通滤波器 (FATL/SATL/KGLP)
   HPF, //高通滤波器 (KGHP)
   BPF, //带通滤波器 (RBCI/KGBP)
   BSF, //带阻滤波器 (KGBS)
  };

//---- 指标的输入参数
input FType_ FType=LPF;     //滤波器类型
                            //0 - 低通滤波器 (FATL/SATL/KGLP),1 - 高通滤波器 (KGHP), 
                            //2 - 带通滤波器 (RBCI / KGBP),3 - 带阻滤波器 (KGBS)
input int    P1 = 28;       //截断周期1,以柱形个数计
input int    D1 = 19;       //稳态过程截断周期1,以柱形个数计
input int    A1 = 40;       //延时带1的衰减,单位dB
input int    P2 = 0;        //截断周期2,以柱形个数计
input int    D2 = 0;        //稳态过程截断周期2,以柱形个数计
input int    A2 = 0;        //延时带2的衰减,单位dB
input int    Delay=0;       //延迟,以柱形个数计
input double Ripple=0.08;   //带宽中的波动,单位dB
input int    FILTERShift=0; //移动平均的水平方向偏移,以柱形计

//---- 引用DLL 
#import "DF.dll"
int DigitalFilter(int FType,int P1,int D1,int A1,int P2,int D2,int A2,double Ripple,int Delay,double &array[]);
#import

//---- 声明并初始化一个变量来存储已计算的柱形的数量
int FILTERPeriod;

//---- 声明动态数组, 
//     将用作指标的缓存
double ExtLineBuffer[];

//---- 声明和初始化数组用于数字滤波器的系数
double FILTERTable[];
//+------------------------------------------------------------------+
//| 自定义指标初始化函数                                                |
//+------------------------------------------------------------------+  
void OnInit()
  {
//----+
//---- 将ExtLineBuffer动态数组变换为指标缓存
   SetIndexBuffer(0,ExtLineBuffer,INDICATOR_DATA);
//---- FILTERShift,指标的水平偏移
   PlotIndexSetInteger(0,PLOT_SHIFT,FILTERShift);
//----设置指标的开始绘制位置
   PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,FILTERPeriod);
//---- 初始化指标简短名称的变量
   string shortname;
   StringConcatenate(shortname,"FILTER(",FILTERShift,")");
//---- 创建数据窗口显示的标签
   PlotIndexSetString(0,PLOT_LABEL,shortname);
//---- 创建在独立窗口和工具提示中显示的名称
   IndicatorSetString(INDICATOR_SHORTNAME,shortname);
//---- 确定指标值的显示精度
   IndicatorSetInteger(INDICATOR_DIGITS,_Digits+1);
//---- 禁止绘制空值
   PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,0.0);
//---- 计算数字滤波器的系数并确定FILTERTable[] 缓存的大小
   double Array[1500];
   FILTERPeriod=DigitalFilter(FType,P1,D1,A1,P2,D2,A2,Ripple,Delay,Array);
//----  改变数字滤波器所需系数的FILTERTable[]缓存的大小
   if(FILTERPeriod<=0)
     {
      Print("Input parameters are incorrect. Indicator can't operate!");
      return;
     
}
//---- 将大小为1500的临时数组中的数据,复制到大小为FILTERPeriod的主数组中
   ArrayCopy(FILTERTable,Array,0,0,FILTERPeriod);
//----+
  
}
//+------------------------------------------------------------------+
//| 自定义指标迭代函数                                                  |
//+------------------------------------------------------------------+
int OnCalculate(
                const int rates_total,     // 当前报价下的历史柱形数据
                const int prev_calculated, // 先前报价下的历史柱形数据
                const int begin,           // 开始有效计算的柱形位置
                const double &price[]      // 用于指标计算的价格数组
                )
  {
//----+   
//---- 检查用于计算的柱形数量是否足够
   if(rates_total<FILTERPeriod-1+begin)
      return(0);

//---- 声明局部变量
   int first,bar,iii;
   double Sum,FILTER;

//---- 柱形重算循环中,计算“第一个”开始值
   if(prev_calculated==0)         // 检查第一个指标值的计算
     {
      first=FILTERPeriod-1+begin; // 所有柱形的开始计算位置
      //---- 在“begin”柱的基础上增加数据开始位,
      //     因为此计算是基于另外一个指标的值
      if(begin>0)
         PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FILTERPeriod);
     
}
   else first=prev_calculated-1;  // 新柱形计算的开始位置

//---- 指标计算的主循环
   for(bar=first; bar<rates_total; bar++)
     {
      //---- 数字滤波器计算公式
      FILTER=0.0;
      for(iii = 0; iii<FILTERPeriod; iii++)
         FILTER+= FILTERTable[iii] * price[bar - iii];

      //---- 用FILTER的值初始化指标缓存单元
      ExtLineBuffer[bar]=FILTER;
     
}
//----+     
   return(rates_total);
  
}
//+------------------------------------------------------------------+

仅依靠客户端的这种通用数字滤波器的 MQL5 实施,完全消除了从 FinWare 公司获取任何数字滤波器的需求。这给我们带来了巨大的便利,为我们使用这些指标打开了一扇新的大门。

总结

在参与所有这些代码操作后,我们获得了大量详实的信息。但近距离查看此过程的这些细节时,我们会发现所有一切完全合乎逻辑并易于理解,如果我们始于对最简单事物的分析并有意义和有意识地从易到难过渡的话。