MQL5:创建自己的指标·综合运用
📊

MQL5:创建自己的指标·综合运用

(3/3)· 前两步铺好概念与单点实现,本篇把输入校验、表示法与收束逻辑一次串起来

含代码示例偏理论 第 3/3 篇
很多交易者拿到向导生成的指标草稿就直接挂盘,却没意识到参数边界和数组越界会让曲线在极端行情里静默失效。把输入检查和表示法补齐,才是自定义指标能反复用的前提。

◍ 数组太小就别急着算

写自定义指标时,OnCalculate() 不是一来就能出图的。它依赖的 price[] 数组长度由 rates_total 决定,若这个数不够大,计算出来的东西根本不可信,不如直接退出等下一根 K 线。 对于做了两次指数平滑的“真实强弱指数”,price[] 的长度必须至少覆盖两个时间周期之和(r + s)。只要 rates_total < r + s,函数返回 0,指标既不计算也不绘制——图表上就是空白,这属于正常保护而非 bug。 下面这段是 MT5 里的典型前置判断与首次初始化。先卡住数据量门槛,再在第一次调用时把缓冲区的 0 号元素清零,避免历史残留影响首值。

MQL5 / C++
class=class="str">"cmt">//--- 如果price[]数组太小
 if(rates_total<r+s) class="kw">return(class="num">0); class=class="str">"cmt">// 不进行计算或者绘图,直接退出
class=class="str">"cmt">//--- 如果这是第一次调用 
  if(prev_calculated==class="num">0)
    {
      class=class="str">"cmt">//--- 把索引0的数据设为0
     MTMBuffer[class="num">0]=class="num">0.0;
     AbsMTMBuffer[class="num">0]=class="num">0.0;
    }
逐行拆解:第 2 行用 rates_total 与 r+s 比较,不满足就 return(0),指标这轮不画;第 4 行判断 prev_calculated==0,即指标刚加载或历史清空后首次跑;第 7、8 行把 MTMBuffer 和 AbsMTMBuffer 的 0 号位赋 0.0,给后续递归计算一个干净起点。 开 MT5 新建指标时,先把你用的 r 和 s 加起来,确认品种最小历史 bars 数大于它,否则加载后看不到线别以为代码写错了。外汇与贵金属波动剧烈,历史不足时盲目计算信号易误导,属高风险操作。

MQL5 / C++
class=class="str">"cmt">//--- 如果price[]数组太小
 if(rates_total<r+s) class="kw">return(class="num">0); class=class="str">"cmt">// 不进行计算或者绘图,直接退出
class=class="str">"cmt">//--- 如果这是第一次调用 
  if(prev_calculated==class="num">0)
    {
      class=class="str">"cmt">//--- 把索引0的数据设为0
     MTMBuffer[class="num">0]=class="num">0.0;
     AbsMTMBuffer[class="num">0]=class="num">0.0;
    }

「改默认价与补全绘制属性」

指标算对了不代表就能直接上图。被其他 MQL5 程序用 iCustom() 或 IndicatorCreate() 调用时,默认吃的是 Close 价;想换源价,得在属性里从 ENUM_APPLIED_PRICE 挑,比如典型价就写 #property indicator_applied_price PRICE_TYPICAL,公式是 (最高+最低+收盘)/3。 若只在后台取数,这步可省;但要往图表上画,就得在 OnInit() 里补齐四样东西:起绘柱号、DataWindow 里的线标签、悬停弹窗的短名、以及小数点位数(只改显示,不动精度)。 下面这段是 True_Strength_Index_ver2 的初始化核心。SetIndexBuffer 把 7 个数组映射到指标槽,0 号是输出线,其余 1~6 是中间计算缓冲;PlotIndexSetInteger 用 r+s-1 定起绘位置,避免前面样本不足画出畸值。 PlotIndexSetString 设标签、IndicatorSetString 设短名、IndicatorSetInteger 设两位小数——全跑完返回 0。把两个版本都挂上再滚到图表开头,差异一眼能看出来:缺设置的版本左侧会多画无效段。 外汇和贵金属波动大、杠杆高,这类显示修正不影响信号本身,实盘前请在 MT5 策略测试器用历史数据核对起绘位置是否符合预期。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标初始函数                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;         |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 指标缓冲区映射关系
   SetIndexBuffer(class="num">0,TSIBuffer,INDICATOR_DATA);
   SetIndexBuffer(class="num">1,MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">2,AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">3,EMA_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">4,EMA2_MTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">5,EMA_AbsMTMBuffer,INDICATOR_CALCULATIONS);
   SetIndexBuffer(class="num">6,EMA2_AbsMTMBuffer,INDICATOR_CALCULATIONS);
class=class="str">"cmt">//--- 设定从哪一个柱开始画指标
   PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,r+s-class="num">1);
   class="type">class="kw">string shortname;
   StringConcatenate(shortname,"TSI(",r,",",s,")");
class=class="str">"cmt">//--- 设置在数据窗口(DataWindow)中的显示标签
   PlotIndexSetString(class="num">0,PLOT_LABEL,shortname);   
class=class="str">"cmt">//--- 设置单独子窗口或者弹出帮助时候的显示名称
   IndicatorSetString(INDICATOR_SHORTNAME,shortname);
class=class="str">"cmt">//--- 设置指标数值显示的精确度
   IndicatorSetInteger(INDICATOR_DIGITS,class="num">2);
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }

从真实强弱指数看指标编写骨架

以真实强弱指数(TSI)为例,在 MT5 里写自定义指标有几条绕不开的骨架:先用 MQL5 向导把指标基础设置跑通,给 OnCalculate() 挑好入参;中间计算要加数组就补,并用 SetIndexBuffer() 绑到缓冲区,类型标 INDICATOR_CALCULATIONS 的留给内部算。 OnCalculate() 每次报价或历史重算都会被调,所以里面能复用现成调试过的函数就别手搓,可读性和出错率都好看些。顺手做点视觉调整,别的程序和人用起来不费劲。 附带的 ver1 源码里默认 r=25、s=13 双指数平滑周期,indicator_buffers 只挂了 1 个缓冲区画主线;想验证就直接把下面代码丢进 MT5 指示器目录编译,看副图蓝线节奏。外汇与贵金属波动剧烈,指标仅描述强弱倾向,实际信号概率不等于确定性,请自担高风险。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|真实强度指数。mq5
class=class="str">"cmt">//| class="num">2009 年 MetaQuotes 软件公司版权所有。|
class=class="str">"cmt">//|https://www.mql5.com |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "class="num">2009, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class="macro">#class="kw">property indicator_separate_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 TSI
class="macro">#class="kw">property indicator_label1  "TSI"
class="macro">#class="kw">property indicator_type1   DRAW_LINE
class="macro">#class="kw">property indicator_color1  Blue
class="macro">#class="kw">property indicator_style1  STYLE_SOLID
class="macro">#class="kw">property indicator_width1  class="num">1
class=class="str">"cmt">//--- 输入参数
class="kw">input class="type">int      r=class="num">25;
class="kw">input class="type">int      s=class="num">13;
class=class="str">"cmt">//--- 指示器缓冲区
class="type">class="kw">double      TSIBuffer[];
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指示器初始化函数
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 指示器缓冲区映射
   SetIndexBuffer(class="num">0,TSIBuffer,INDICATOR_DATA);
class=class="str">"cmt">//---

◍ 指标收尾的返回逻辑

自定义指标的 OnCalculate 收尾,最容易写错的是返回值。标准做法是直接 return(rates_total),把当前已计算的 K 线总数交还给终端,下次新 tick 进来时 prev_calculated 就会等于这个数,避免从头重算。 如果前面初始化函数里写了 return(0),那只是 OnInit 的正常退出,和指标主循环无关。两者返回值语义不同,混用会导致图表不刷新或 CPU 占用异常。 在 MT5 里挂上这段空壳指标,用 EURUSD 的 M1 跑十分钟,你能看到 prev_calculated 从 0 逐步涨到 600 上下,说明增量计算确实生效。外汇与贵金属杠杆高,指标验证逻辑前先用模拟盘,实盘误用可能放大滑点风险。

MQL5 / C++
  class="kw">return(class="num">0);
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                |
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[])
  {
class=class="str">"cmt">//---
class=class="str">"cmt">//--- 为下一次调用返回 prev_calculated 的值
   class="kw">return(rates_total);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
让小布替你跑这套校验
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到指标参数异常与缓冲区状态提示,你只需专注算法逻辑本身。

常见问题

原文示例用 r=25、s=13 基于 Blau 的经典设定,实际可结合品种波动周期做概率性微调,没有必优值。
单线指标通常一个缓冲区对应一种绘图;多元素组合时才会出现缓冲区多于可见绘图的情况,需手动声明。
独立窗口需在属性中声明不在主图绘制,并指定指标缓冲区与绘图样式,否则默认尝试叠加到价格坐标。
小布盯盘支持对接 MT5 导出的自定义指标文件做可视化诊断,但具体计算仍由你的算法决定,外汇贵金属属高风险。
价格类型会改变 mtm 绝对值平滑结果,倾向影响阈值灵敏度,建议用历史分段回看比较而非直接认定某类更准。