朋友今天一大早发消息过来,说他写了个MT5自定义指标,加载到图表上之后,K线直接错位了,后面的柱子全跑到前面去了,就像把整张图往左硬拽了一截。他第一反应就是Buffer数组越界,问我是不是这个原因。说实话,这个问题我三个月前也踩过,而且当时排查了整整一个晚上,最后发现还真不一定是数组越界,但确实和Buffer的声明方式有很大关系。
先说说最常见的错位原因,就是Buffer数组的索引和当前K线位置没对上。MT5里指标计算是从最左边的历史K线开始的,如果你在OnCalculate里用了ArraySetAsSeries(true),那索引0就是最新的K线,这时候如果你还用旧的循环习惯从0往右推,那图表上的数据就会整体右移,看起来就像错位了。我当时就是吃了这个亏,以为跟MT4一样默认左边是0,结果整个指标画出来的线全部偏了,而且越靠右偏差越大,因为累积了。解决方案其实很粗暴,要么统一不用SetAsSeries,要么在循环里把索引反过来写,我后来是干脆在指标开头固定写一句ArraySetAsSeries(ExtBuffer, true),然后所有循环都从0开始正向跑,这样逻辑上最不容易乱。
但如果你已经确认了序列方向没问题,图表还是错位,那就要看是不是Buffer数组的维度声明和实际绘图数量不匹配。比如你声明了3个Buffer,但PlotIndexDrawBegin或者PlotIndexFirstBar设置得不对,MT5默认是从第一个非空值开始画的,如果你在中间某根K线上手动赋值了空值或者NaN,那后面的数据就会往前补位,看起来就像整段线被压缩了。我遇到过一次很奇怪的情况,就是指标在某个特定时间点突然跳空,后面的线全部贴到前面去,查了半天发现是那个时间点我用了EMPTY_VALUE来填充,但MT5里EMPTY_VALUE是DBL_MAX,如果后面有计算逻辑没判空,就会把那个极大值当成有效数据,画出来的线直接飞到天上,然后系统自动缩放到正常范围,视觉上就变成错位了。这个坑特别隐蔽,建议你在每个Buffer赋值前都加一个if(value != EMPTY_VALUE)的判断,宁可多写几行也不要偷懒。
再说到数组越界,这个其实在MT5里不太容易直接导致图表错位,因为编译器会报错,最多是崩溃或者显示异常,但如果你用了[iCustom]调用其他指标,那情况就不一样了。我朋友那个问题最后查出来,是他引用了另一个自定义指标的Buffer,但那个指标本身在某个周期上只画了不到100根K线,他这边却取了第150根的数据,取出来的就是垃圾值,然后他把这个垃圾值赋给当前指标的Buffer,结果整条线就歪了。这种情况下,MT5不会报错,因为数组本身没有越界,你取的是别的指标内部数组的越界值,它返回的是0或者未初始化数据,但画出来就是错位的。我当时的解决办法是,在调用iCustom之前先检查目标指标的BarsCalculated()返回值,如果小于当前需要的索引数,就直接返回0,不做计算,这样至少不会污染当前的图表。另外,如果你用的是多时间周期指标,还要注意同步问题,不同周期的K线数量不一样,取索引的时候要按对应的周期来算,不然也容易错位。
最后说一个大家容易忽略的地方,就是编译器的优化选项和Buffer类型。如果你声明Buffer的时候用了float而没指定double,在某些极端情况下精度丢失会导致画线偏移,虽然这个偏移量很小,但在高倍缩放下肉眼能看出来。还有,如果你在OnInit里给Buffer分配了颜色和宽度,但后面在循环里又反复修改了Plot的样式,有时候MT5会重新计算绘图偏移,导致看起来像错位。我现在的习惯是,所有Buffer统一用double,样式只在OnInit里设置一次,后面不动,除非确实需要动态变色。另外,如果你用到了多个子窗口,记得检查每个窗口的IndicatorSetInteger(INDICATOR_LEVELS)或者固定最小值,有时候子窗口的自动缩放会让人误以为数据错位了,其实是坐标轴的问题。
简单说,遇到图表错位,先按顺序排查:首先,确认ArraySetAsSeries方向和循环索引是否匹配;然后,检查Buffer里有没有混入EMPTY_VALUE或者NaN;还有,看iCustom引用的外部指标是不是返回了无效数据;第四,检查Buffer类型和绘图属性设置。我那次最后定位到是外部指标的问题,改完之后跑了一个月的回测都没再出现过。你们有没有遇到过类似的错位情况,最后是怎么解决的?想听听其他朋友有没有更奇葩的案例。
先说说最常见的错位原因,就是Buffer数组的索引和当前K线位置没对上。MT5里指标计算是从最左边的历史K线开始的,如果你在OnCalculate里用了ArraySetAsSeries(true),那索引0就是最新的K线,这时候如果你还用旧的循环习惯从0往右推,那图表上的数据就会整体右移,看起来就像错位了。我当时就是吃了这个亏,以为跟MT4一样默认左边是0,结果整个指标画出来的线全部偏了,而且越靠右偏差越大,因为累积了。解决方案其实很粗暴,要么统一不用SetAsSeries,要么在循环里把索引反过来写,我后来是干脆在指标开头固定写一句ArraySetAsSeries(ExtBuffer, true),然后所有循环都从0开始正向跑,这样逻辑上最不容易乱。
但如果你已经确认了序列方向没问题,图表还是错位,那就要看是不是Buffer数组的维度声明和实际绘图数量不匹配。比如你声明了3个Buffer,但PlotIndexDrawBegin或者PlotIndexFirstBar设置得不对,MT5默认是从第一个非空值开始画的,如果你在中间某根K线上手动赋值了空值或者NaN,那后面的数据就会往前补位,看起来就像整段线被压缩了。我遇到过一次很奇怪的情况,就是指标在某个特定时间点突然跳空,后面的线全部贴到前面去,查了半天发现是那个时间点我用了EMPTY_VALUE来填充,但MT5里EMPTY_VALUE是DBL_MAX,如果后面有计算逻辑没判空,就会把那个极大值当成有效数据,画出来的线直接飞到天上,然后系统自动缩放到正常范围,视觉上就变成错位了。这个坑特别隐蔽,建议你在每个Buffer赋值前都加一个if(value != EMPTY_VALUE)的判断,宁可多写几行也不要偷懒。
再说到数组越界,这个其实在MT5里不太容易直接导致图表错位,因为编译器会报错,最多是崩溃或者显示异常,但如果你用了[iCustom]调用其他指标,那情况就不一样了。我朋友那个问题最后查出来,是他引用了另一个自定义指标的Buffer,但那个指标本身在某个周期上只画了不到100根K线,他这边却取了第150根的数据,取出来的就是垃圾值,然后他把这个垃圾值赋给当前指标的Buffer,结果整条线就歪了。这种情况下,MT5不会报错,因为数组本身没有越界,你取的是别的指标内部数组的越界值,它返回的是0或者未初始化数据,但画出来就是错位的。我当时的解决办法是,在调用iCustom之前先检查目标指标的BarsCalculated()返回值,如果小于当前需要的索引数,就直接返回0,不做计算,这样至少不会污染当前的图表。另外,如果你用的是多时间周期指标,还要注意同步问题,不同周期的K线数量不一样,取索引的时候要按对应的周期来算,不然也容易错位。
最后说一个大家容易忽略的地方,就是编译器的优化选项和Buffer类型。如果你声明Buffer的时候用了float而没指定double,在某些极端情况下精度丢失会导致画线偏移,虽然这个偏移量很小,但在高倍缩放下肉眼能看出来。还有,如果你在OnInit里给Buffer分配了颜色和宽度,但后面在循环里又反复修改了Plot的样式,有时候MT5会重新计算绘图偏移,导致看起来像错位。我现在的习惯是,所有Buffer统一用double,样式只在OnInit里设置一次,后面不动,除非确实需要动态变色。另外,如果你用到了多个子窗口,记得检查每个窗口的IndicatorSetInteger(INDICATOR_LEVELS)或者固定最小值,有时候子窗口的自动缩放会让人误以为数据错位了,其实是坐标轴的问题。
简单说,遇到图表错位,先按顺序排查:首先,确认ArraySetAsSeries方向和循环索引是否匹配;然后,检查Buffer里有没有混入EMPTY_VALUE或者NaN;还有,看iCustom引用的外部指标是不是返回了无效数据;第四,检查Buffer类型和绘图属性设置。我那次最后定位到是外部指标的问题,改完之后跑了一个月的回测都没再出现过。你们有没有遇到过类似的错位情况,最后是怎么解决的?想听听其他朋友有没有更奇葩的案例。
