MQL5:创建自己的指标·综合运用
(3/3)· 前两步铺好概念与单点实现,本篇把输入校验、表示法与收束逻辑一次串起来
◍ 数组太小就别急着算
写自定义指标时,OnCalculate() 不是一来就能出图的。它依赖的 price[] 数组长度由 rates_total 决定,若这个数不够大,计算出来的东西根本不可信,不如直接退出等下一根 K 线。 对于做了两次指数平滑的“真实强弱指数”,price[] 的长度必须至少覆盖两个时间周期之和(r + s)。只要 rates_total < r + s,函数返回 0,指标既不计算也不绘制——图表上就是空白,这属于正常保护而非 bug。 下面这段是 MT5 里的典型前置判断与首次初始化。先卡住数据量门槛,再在第一次调用时把缓冲区的 0 号元素清零,避免历史残留影响首值。
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; }
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 策略测试器用历史数据核对起绘位置是否符合预期。
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); 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 指示器目录编译,看副图蓝线节奏。外汇与贵金属波动剧烈,指标仅描述强弱倾向,实际信号概率不等于确定性,请自担高风险。
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 上下,说明增量计算确实生效。外汇与贵金属杠杆高,指标验证逻辑前先用模拟盘,实盘误用可能放大滑点风险。
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">//+------------------------------------------------------------------+