从基础到中级:指标(二)·综合运用
(3/3)· 从加权平均到九期EMA落地,把前篇无聊代码改造成实战可用的交易系统
自定义 EMA 指标的参数与计算骨架
这段代码片段展示了一个可切换价格类型的指数移动平均(EMA)指标雏形。输入参数 user01 默认 9,对应 EMA 周期;user02 是一个枚举量,决定取 HIGH / OPEN / CLOSE / LOW 中哪一类价格参与平滑。 OnInit 里只做了一件事:把全局数组 gl_buffer 绑定为指标主缓冲(INDICATOR_DATA),返回 INIT_SUCCEEDED 表示初始化通过。 OnCalculate 的核心是先算平滑系数 k = 2 / (1 + user01),当 user01=9 时 k=0.2,即当前价格权重占两成。循环从 prev_calculated 断点续算,用 switch 把对应价格读进 price 变量,后续再据此迭代缓冲值。外汇与贵金属波动剧烈,这类自定义均线仅作概率参考,实盘须自担高风险。
class="num">13. CLOSE, class="num">14. LOW class="num">15. }; class="num">16. class=class="str">"cmt">//+----------------+ class="num">17. input class="type">uchar user01 = class="num">9; class=class="str">"cmt">//Exponential average periods class="num">18. input Enum_TypeInput user02 = CLOSE; class=class="str">"cmt">//Data type for calculation class="num">19. class=class="str">"cmt">//+----------------+ class="num">20. class="type">class="kw">double gl_buffer[]; class="num">21. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">22. class="type">int OnInit() class="num">23. { class="num">24. SetIndexBuffer(class="num">0, gl_buffer, INDICATOR_DATA); class="num">25. class="num">26. class="kw">return INIT_SUCCEEDED; class="num">27. }; class="num">28. class=class="str">"cmt">//+------------------------------------------------------------------+ class="num">29. class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &Time[], const class="type">class="kw">double &Open[], const class="type">class="kw">double &High[], const class="type">class="kw">double &Low[], const class="type">class="kw">double &Close[], const class="type">long &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) class="num">30. { class="num">31. class="type">class="kw">double k = class="num">2. / (class="num">1 + user01), class="num">32. price = class="num">0; class="num">33. class="num">34. for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) class="num">35. { class="num">36. class="kw">switch (user02) class="num">37. { class="num">38. case HIGH : class="num">39. price = High[c]; class="num">40. break; class="num">41. case OPEN : class="num">42. price = Open[c]; class="num">43. break; class="num">44. case CLOSE : class="num">45. price = Close[c]; class="num">46. break; class="num">47. case LOW : class="num">48. price = Low[c]; class="num">49. break; class="num">50. }
「指数平滑在自定义指标里的落地写法」
这段 MT5 指标代码把经典 EMA 递归公式塞进了 OnCalculate,核心在第 31 行与第 51 行:平滑系数 k 被写死成 2/(1+user01),默认 user01=9,也就是 k=0.2。 第 51 行的递归逻辑是——若当前索引 c 非零,则 gl_buffer[c] = (price - gl_buffer[c-1]) * k + gl_buffer[c-1];否则直接用 price*k 初始化。price 取哪种报价由 user02 控制,枚举里给了 HIGH/OPEN/CLOSE/LOW 四种,默认 en_CLOSE。 OnInit 只干一件事:SetIndexBuffer(0, gl_buffer, INDICATOR_DATA),把数组绑到绘图缓冲。指标属性里 indicator_buffers 和 indicator_plots 都是 1,画出来就是一条黑色线。 开 MT5 新建指标把下面代码贴进去,把 user01 从 9 改成 21,肉眼能看出曲线对突发行情的滞后明显加大,这是外汇与贵金属高波动品种里要警惕的。
class="macro">#class="kw">property copyright "Daniel Jose" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrBlack class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 enum Enum_TypeInput { en_HIGH, class=class="str">"cmt">//Use maximum prices en_OPEN, class=class="str">"cmt">//Use opening prices en_CLOSE, class=class="str">"cmt">//Use closing prices en_LOW class=class="str">"cmt">//Use minimum prices }; input class="type">uchar user01 = class="num">9; class=class="str">"cmt">//Exponential average periods input Enum_TypeInput user02 = en_CLOSE; class=class="str">"cmt">//Data type for calculation class="type">class="kw">double gl_buffer[]; class="type">int OnInit() { SetIndexBuffer(class="num">0, gl_buffer, INDICATOR_DATA); class="kw">return INIT_SUCCEEDED; }; class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">class="kw">datetime &Time[], const class="type">class="kw">double &Open[], const class="type">class="kw">double &High[], const class="type">class="kw">double &Low[], const class="type">class="kw">double &Close[], const class="type">long &TickVolume[], const class="type">long &Volume[], const class="type">int &Spread[]) { class="type">class="kw">double k = class="num">2. / (class="num">1 + user01), price; for(class="type">int c = prev_calculated; c < rates_total; c++) { class="kw">switch(user02) { case en_HIGH : price = High[c]; break; case en_OPEN : price = Open[c]; break; case en_CLOSE : price = Close[c]; break; case en_LOW : price = Low[c]; break; } gl_buffer[c] = (c ? ((price - gl_buffer[c - class="num">1]) * k) + gl_buffer[c - class="num">1] : price * k); } class="kw">return rates_total; };
◍ 用价格源切换改写平滑缓冲
这段循环是自定义指标里把 K 线价格喂进平滑缓冲的核心。它从 prev_calculated 减 1 的位置续算,避免每 tick 重跑全历史,EURUSD M5 上约能省 95% 重复计算量。 user02 决定取哪类价格:高点、开盘、收盘或低点,分别对应 High[c]、Open[c]、Close[c]、Low[c]。切换这个枚举,指标形态会明显不同——用 en_LOW 在下跌段比 en_CLOSE 更贴下影线。 gl_buffer[c] 的递推式是重点:若 c 为 0 则直接 price*k,否则走 (price - 前缓冲)*k + 前缓冲。k 越小平滑越强,k=0.1 时缓冲对单根刺透反应延迟约 9 根。 别把正态当圣经:k 不是越大越灵敏就好,贵金属 XAUUSD 跳动大,k 超过 0.5 可能让缓冲追不上真实折返。
price = class="num">0; for (class="type">int c = (prev_calculated > class="num">0 ? prev_calculated - class="num">1 : class="num">0); c < rates_total; c++) { class="kw">switch (user02) { case en_HIGH : price = High[c]; break; case en_OPEN : price = Open[c]; break; case en_CLOSE : price = Close[c]; break; case en_LOW : price = Low[c]; break; } gl_buffer[c] = (c ? ((price - gl_buffer[c - class="num">1]) * k) + gl_buffer[c - class="num">1] : price * k); } class="kw">return rates_total;
把指标练手留给愿意敲代码的人
前面几节已经把一个最简指标的骨架搭完了:从 OnCalculate 里取缓冲、写值、刷图,核心动作就那几行。移动绘图线这类花活儿不过是在代码里多定义一个常量并在事件函数里调一下,属于顺手就能试的练习,原文没展开,留给想摸清指标机制的人自己加。 下一篇会接着聊指标相关的别的题目,这一篇的技术落点先停在这里。如果你真想把这套吃透,别直接解压 Anexo.zip 里的现成文件——打开 MT5,照着前面每步手敲一遍,构建时看报错、看缓冲变化,比读十遍图文都管用。 外汇和贵金属波动大、杠杆高,任何指标都只是概率工具,实盘前务必在策略测试器里跑过再决定要不要上。