从基础到中级:指标(二)·基础篇:用最少代码画出你的第一条移动平均线(基础篇)
📊

从基础到中级:指标(二)·基础篇:用最少代码画出你的第一条移动平均线(基础篇)

(1/3)·很多交易者以为写指标必须懂深奥编程,其实 MetaTrader 5 把脏活都干了,你只差想清楚计算方法

含代码示例偏理论 第 1/3 篇
初学者常卡在"写指标=写复杂系统"的错觉里,对着空白编辑器发呆。上一篇文章里那几行能画线的代码,被很多人嫌弃"不像正经指标"就丢一边。其实 Nine-period EMA 这类简单系统难在遵守,不在编码——你只是还没想清楚权重从哪来。

◍ 新手也能写指标的起点

在 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 一下就懂为什么数学表达式照抄会出事。外汇和贵金属波动大,这类指标只作概率参考,实盘前务必在策略测试器跑历史数据。

MQL5 / C++
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)看曲线滞后变化。

MQL5 / C++
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 则更接近均线。外汇和贵金属波动大,这类递归线滞后可能被突发事件打穿,实盘前先在策略测试器跑一遍不同品种验证。

MQL5 / C++
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">//+------------------------------------------------------------------+
把重复劳动交给小布
这些诊断与指标原型思路小布盯盘的 AIGC 已内置,打开对应品种页即可看到基础均线模板,你专注调参和决策就好。

常见问题

非加权(如 SMA)给每个价格同等权重;加权可基于时间、交易量或价格,EMA 按周期数时间加权。选错权重类型,信号滞后或噪声表现会完全不同。
上一篇只是演示事件响应与绘图机制,没套用任何均值算法。本篇会把它改造成真正按周期计算的移动平均线指标。
VWAP 是成交量加权平均速度/价格,权重来自交易量分布而非价格本身。具体方向在源码里由输入数组顺序决定,概念上量在前、价在后。
可以,小布内置的 AIGC 能根据品种页上下文输出含代码示例的模板,你改周期参数即可,不用从零写。
九期 EMA 结构单纯,但外汇贵金属杠杆高、跳空频繁,信号可能失效。它难在纪律执行,不代表概率上稳赢。