从基础到中级:指标(二)·基础篇:用最少代码画出你的第一条移动平均线(基础篇)
(1/3)·很多交易者以为写指标必须懂深奥编程,其实 MetaTrader 5 把脏活都干了,你只差想清楚计算方法
◍ 新手也能写指标的起点
在 MT5 里做指标,新手最容易卡在「是不是得先精通 MQL5 才能动手」这个错觉上。实际上上一篇文章已经证明:只要正确响应平台抛出的事件,画图这类基础活儿 MT5 自己就接管了,初学者稍加努力就能产出可跑的东西。 不少人回头看那段代码会觉得「这跟我见过的指标长得太不一样,只是画了点东西,能干的事太少了」。这很正常——知识是培养出来的,不是一夜长出来的。本系列不打算教「照做就行」的死规矩,而是把事件响应这条线一点点铺开。 上一节我们用的事件响应骨架,核心就是 OnInit 初始化、OnCalculate 算数据,剩下的交给终端。先把这套跑通,后面才有资格谈加逻辑。
「用最少代码写出九周期 EMA」
很多人想看怎么在 MT5 里搓一条像样的移动平均线,而不是只把价格原样画出来。指数移动平均(EMA)按周期数加权,公式表达为:当前 EMA = P × (2/(N+1)) + M × (1 - 2/(N+1)),其中 P 是当前价格,M 是上一根 K 线的 EMA,N 是周期数。九周期 EMA 之所以常被提及,是因为它简单却很难手动盯盘跟住。 初始计算有个坑:第一根 K 线根本没有“前一个 EMA”可用。 workaround 是把链首的 M 直接置 0,这样就能顺下去算。基于前文那版只回显价格的代码,其实只改第 22 行就够了——把 gl_buffer[c] = price[c] 换成 EMA 递推式。 下面是把前文代码 01 改完后的完整指标代码(代码 02),除了第 22 行其他原封不动: [CODE] 见 code 字段。跑起来后图表上会画出一条线,看着不像九周期 EMA?用 MT5 自带 Exponential MA 叠加验证,周期填 9、价格选 Close,两条线会完全重合——说明这三行计算逻辑是对的。 不过代码 02 里藏了个小问题:当 prev_calculated 不为 0 时,M 的初值没在循环外保留,重算会漂移。修正版(代码 03)把上一根 EMA 存住再迭代,附录里两份代码都留着,你自己 diff 一下就懂为什么数学表达式照抄会出事。外汇和贵金属波动大,这类指标只作概率参考,实盘前务必在策略测试器跑历史数据。
class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "Daniel Jose" class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrBlack class=class="str">"cmt">//+----------------+ class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//+----------------+ class="type">class="kw">double gl_buffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buffer, INDICATOR_DATA); class="kw">return INIT_SUCCEEDED; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[]) { for (class="type">int c = prev_calculated; c < rates_total; c++) gl_buffer[c] = price[c] * (class="num">2.0 / (class="num">9.0 + class="num">1.0)) + (c > class="num">0 ? gl_buffer[c-class="num">1] : class="num">0) * (class="num">1.0 - class="num">2.0 / (class="num">9.0 + class="num">1.0)); class="kw">return rates_total; }; class=class="str">"cmt">//+------------------------------------------------------------------+
把平滑系数抽成宏定义
前面那段直接计算里把周期数 9 写死在算式中,调参时要改两处、容易漏。把 N 用宏提出来,只维护一个 def_N 就够了。 下面这段改写把平滑因子统一成 (2./(1.0+def_N)),def_N 设为 9 时权重约 0.2,和原版数值完全一致。 [CODE] #define def_N 9 for(int c=prev_calculated; c<rates_total; c++) { double M=(c ? gl_buffer[c-1] : 0); gl_buffer[c]=(price[c]-M)*(2./(1.0+def_N))+M; } #undef def_N [/CODE] M 承接上一根缓冲值,首根用 0 初始化,避免数组越界。外汇与贵金属波动大,这类自定义指标仅作辅助参考,实盘前务必在 MT5 策略测试器跑一遍不同 def_N(如 5、14)看曲线滞后变化。
class="macro">#define def_N class="num">9 for(class="type">int c=prev_calculated; c<rates_total; c++) { class="type">class="kw">double M=(c ? gl_buffer[c-class="num">1] : class="num">0); gl_buffer[c]=(price[c]-M)*(class="num">2./(class="num">1.0+def_N))+M; } class="macro">#undef def_N
◍ 把递归平滑写进指标缓冲区
这段脚本把一个周期为 9 的递归平滑线塞进 MT5 自定义指标里,核心就是拿前一刻的缓冲值和新价做加权。权重写死成 2/(1+9),也就是 0.2 的新价占比,老值占 0.8,属于典型慢速跟随。 OnInit 里只干一件事:把 gl_buffer 绑定到 0 号数据缓冲,返回 INIT_SUCCEEDED 让终端放行。OnCalculate 的循环从 prev_calculated-1 起步,避免每次重算全历史,只在新增 bar 上递推,这在多周期加载时能少烧不少 CPU。 逐行看那句赋值:c 为 0 时直接用 price[0]*0.2 起手;之后每个位置都是 (price[c]-gl_buffer[c-1])*0.2 + gl_buffer[c-1],等价于 gl_buffer[c-1] + 0.2*(price[c]-gl_buffer[c-1])。你把这个 9 改成 4,线会明显更贴价格,改 19 则更接近均线。外汇和贵金属波动大,这类递归线滞后可能被突发事件打穿,实盘前先在策略测试器跑一遍不同品种验证。
class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrBlack class=class="str">"cmt">//+----------------+ class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class=class="str">"cmt">//+----------------+ class="type">class="kw">double gl_buffer[]; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buffer, INDICATOR_DATA); class="kw">return INIT_SUCCEEDED; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double &price[]) { for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) gl_buffer[c] = (c ? ((price[c] - gl_buffer[c - class="num">1]) * class="num">2. / (class="num">1 + class="num">9)) + gl_buffer[c - class="num">1] : price[c] * class="num">2. / (class="num">1 + class="num">9)); class="kw">return rates_total; }; class=class="str">"cmt">//+------------------------------------------------------------------+