初学者以 MQL5 实现对数字滤波器的实际实施(基础篇)
「用 MQL5 搭一个能跑的数字滤波器」
想在 MT5 里亲手验证数字滤波器对价格序列的平滑效果,不必从理论公式啃起。MQL5 直接提供了数组与指标缓冲区接口,初学者也能在半小时内部署一个可加载的自定义指标。 下面这段示例用简单移动平均思路实现一阶低通滤波,核心是把前一根的滤波值与当前价格按系数混合。系数 alpha 越小,曲线越钝;越大则越贴近原始收盘价。 [CODE]//+------------------------------------------------------------------+
| // | 一阶低通滤波器示例 |
|---|
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots 1
//--- plot Filtered
#property indicator_label1 "Filtered"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDodgerBlue
//--- input
double alpha = 0.1; // 平滑系数
//--- buffers
double ExtFilteredBuffer[]; // 滤波输出
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0,ExtFilteredBuffer,INDICATOR_DATA);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total<2) return(0);
ExtFilteredBuffer[0]=close[0];
for(int i=1;i<rates_total;i++)
{
ExtFilteredBuffer[i]=alpha*close[i]+(1-alpha)*ExtFilteredBuffer[i-1];
}
return(rates_total);
}
//+------------------------------------------------------------------+[/CODE]
逐行拆解:
#property indicator_chart_window 让指标直接画在主图价格上,方便对比。
double alpha = 0.1; 是外部可调参数,加载指标后在属性框改它即可实时看效果。
ExtFilteredBuffer[0]=close[0]; 用第一根收盘价初始化,避免数组空引用。
for 循环里 alpha*close[i]+(1-alpha)*ExtFilteredBuffer[i-1] 就是递推式:当前滤波值 = 新价格权重 + 旧滤波值残重。
把 alpha 调到 0.02 挂 EURUSD 日线,曲线会比原生收盘价滞后明显但噪声锐减;外汇与贵金属杠杆高、滑点无常,这类平滑只辅助判势,不预示方向,实盘前务必在策略测试器跑历史数据确认惯性偏差。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 一阶低通滤波器示例 | class=class="str">"cmt">//+------------------------------------------------------------------+ 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=class="str">"cmt">//--- plot Filtered class="macro">#class="kw">property indicator_label1 "Filtered" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrDodgerBlue class=class="str">"cmt">//--- class="kw">input class="type">class="kw">double alpha = class="num">0.1; class=class="str">"cmt">// 平滑系数 class=class="str">"cmt">//--- buffers class="type">class="kw">double ExtFilteredBuffer[]; class=class="str">"cmt">// 滤波输出 class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0,ExtFilteredBuffer,INDICATOR_DATA); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ 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[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { if(rates_total<class="num">2) class="kw">return(class="num">0); ExtFilteredBuffer[class="num">0]=close[class="num">0]; for(class="type">int i=class="num">1;i<rates_total;i++) { ExtFilteredBuffer[i]=alpha*close[i]+(class="num">1-alpha)*ExtFilteredBuffer[i-class="num">1]; } class="kw">return(rates_total); } class=class="str">"cmt">//+------------------------------------------------------------------+
编译完先翻工具箱的错误页
在 MetaEditor 里点完编译,别直接关掉。切到 Toolbox(工具箱)窗口的 Errors(错误)选项卡,逐条看 EA 交易的编译结果。哪怕只标了一个 warning,也要确认它不是后续逻辑错位的源头。 我之前那篇拆过简单指标的代码,也提过它跟 MetaTrader 5 客户端怎么交互。现在要继续往下改 SMA 指标,前提是编译干净、没藏着未决错误。 实操上,开 MT5 载入早先那份 SMA 源码,编译一次,把 Errors 页截图或记下来。只有这一页全空,才值得你往下做参数和信号逻辑的改动。外汇与贵金属波动大,代码层级的小错可能在实盘放大成异常下单,高风险。
◍ 故意写错代码来摸清编译器脾气
在 MetaEditor 里正常编译两个未改动的指标版本时,过程很顺:.mq5 源文件旁边直接生成 .ex5,零报错。但写代码不可能永远不翻车,MetaEditor 内置了全量错误检查,编译完会在 Toolbox 的 Errors 选项卡丢出完整错误列表。 定位很简单——双击 Toolbox 里那行报错,编辑器通常会用箭头图标精确标到出错的代码行。要注意一个坑:一行错经常引发一串连锁报错。所以别从中间改,先跳到编译器报的第一行去修,修完重新编译,再看新列表的第一行。 最快的理解方式不是读文档,而是主动破坏。在代码里人为制造下列任一改动,按 Compile 看反应,直觉记住这些破坏长什么样:运算符或变量里插空格、删分号、乱加分号、删运算符、加减大括号或圆括号、删逗号、给 OnCalculate() 多加输入参数、变量除零、if 里用 = 代替 ==、把 bar++ 改成 bar--。 外汇与贵金属指标开发高风险,编译能过不代表策略能赚钱。最后留个底:MetaEditor 只认 MQL5 语法错,逻辑错它一概查不出,得靠你自己回测和盯盘验证。
「开写前先够用的那点语法」
刚碰 MQL5 的人容易被整本语言手册吓住,其实第一批能跑起来的程序,用到的语法只占全部可用词汇的一小撮。和日常说话一样,交易者写 EA 或指标时反复调用的也就是那几个固定零件。 最小可用集合很明确:四种基础变量 int、double、bool、string,配合 if-else 做分支、for 做循环、{} 组织复合语句、return 退出函数,再加上分号 ; 断句和逗号 , 分隔参数。这套组合拳足以把初步的行情判断逻辑写成可编译代码。 额外建议直接啃数学和三角函数那部分——MT5 里取均线斜率、算波动率都离不开它们。新手在策略测试器里用上面这些元素写个打印 K 线高低点的脚本,就能完成第一次「词汇量」验证。外汇与贵金属杠杆高,先用历史数据回测练语法,别急着上实盘。
给指标加上动态短名与绘制起点
在 MT5 里改指标的客户端显示,核心就是动 OnInit() 和 OnCalculate() 里的几行接口代码,不碰算法本身也能让图表更耐看。用 StringConcatenate() 拼出带参数的短名,再借 PlotIndexSetString() 和 IndicatorSetString() 推到数据窗口和 tooltip,交易者悬停就能看到当前加载的是 FATL(偏移) 还是 SMA(周期,偏移)。 OnCalculate() 里补一句 PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,begin+FATLPeriod) 没什么副作用,反而能避免基于其他指标计算时在前几根柱画出无意义的连线。代码量上去一点,复杂度略增,但接口对用户更友好,值得做。 下面这段可直接拷进已有指标的属性声明后做对照:#property 那串定义了单缓冲、主窗口画线、红色实线宽1;真正改显示逻辑的是 shortname 拼接与 PlotIndexSet* 调用。外汇与贵金属波动剧烈,这类显示改良不改动信号逻辑,但高频重算时仍需注意客户端卡顿风险。
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">// 新柱形计算的开始位置