通达信公式异常值不是以报错的形式出现的。公式照样编译通过,图上照样有线条,只是某些位置的值明显偏离常理:一根竖线插到图外,或者一小段线突然断开。这类输出在编译期不会给出任何提示,只能靠自己去发现和处理。
下面把异常值分成两类来源——除零与空值——分别说明它们是怎么产生的,给出把保护动作写在除法之前的处理写法,最后给一套验证办法。全文只讨论计算与写法,不评价任何公式的效果。
一、异常值在图上是什么样
先建立一个可以对照的印象。把公式挂到副图上,正常情况下曲线在纵轴的某个范围内波动。出现异常值时,常见的表现有三种:一条从图底部贯到顶部的竖线;曲线在某个位置突然跳到极高的位置再落回来;一段区间内曲线整体缺失,只有前一段和后一段。
这三种表现的成因不同。竖线多半来自某一根上的取值异常大;跳变来自计算过程中的一个中间量异常,被后续的平滑带开了几根;整段缺失则是那一段没有有效的输出值,图上什么都不画。按表现分类,能缩小排查范围。
判断是否真的异常,有一个简单办法:把鼠标逐根移过去看数值,找到突变的起点。突变的那一根就是问题所在的位置,把公式里用到的中间量在那根上逐层打印出来,就能确定是哪一步算出来的。
逐层打印的做法是把中间量逐个赋值成输出,一次只放一个,挂到副图上看它的曲线形状。多个中间量同时输出会让副图挤成一片,也很难判断异常来自哪一层。一次一个,从最靠近输入的中间量开始往后推,找到第一个出现异常的那一层。处理通达信公式异常值,这一步比在整段源码里来回找要快。
二、除零是怎么发生的
除法在公式里出现得很自然:算比例、算占比、算归一化的位置,都要除以一个量。问题在于分母可能取到零。以下几种情形会让分母为零。
- 成交量类:停牌或长期无成交的品种,某些周期上的成交量为零。
- 价格极差类:最高价与最低价相等的 K 线,两者之差为零。一字涨停或跌停的 K 线属于这一类。
- 均线差值类:两条均线在某一根上取到完全相同的值,差值做分母时为零。
- 区间长度类:把某个位置除以区间的宽度,当区间宽度为零时出现除零。
分母为零时,除法的输出取值不由公式控制。不同版本对这种情况的处理可能不同,有的返回一个约定的值,有的给出一个极大的数。正因为结果不可预期,写作时应当自己加保护,而不是依赖软件的默认行为。
三、空值的来源
空值与除零不同,它不是数值计算出来的结果,而是值本身不存在。常见来源有三处。一是数据长度不足:向后取值的函数在序列起点附近没有可取的项,返回空。二是数据缺失:本地日线数据没有下载完整,缺失区间上的序列为空。三是品种本身尚无数据:新上市的品种在上市之前没有行情。
空值的麻烦之处在于它会向后传播。一个含空值的中间量参与比较,比较的结果未必是假;参与加减,结果还是空。空值一路传到最后,就成了图上那段缺失的区间。
处理空值的方向与处理除零相反:除零要保护分母,空值要在源头判断数据是否够长。用累计周期数一类函数可以拿到序列的当前位置,把它作为条件限制输出的范围,起点附近的那几根就不会参与输出。
{ 用累计周期数限制输出的起始位置 }
LEN:=BARSCOUNT(CLOSE);
{ 前 10 根不输出(取值不确定),从第 11 根开始输出 }
X:IF(LEN>10,MA(CLOSE,20),DRAWNULL);
四、把保护写在除法之前
处理除零有一个写法上的讲究。直觉做法是把判断写在 IF 里,条件成立时输出一个安全值,不成立时才做除法。这种写法的问题是,条件函数的作用是从两个取值里选一个,两个取值本身仍然会被算出来,除法那一步照样发生。
更稳妥的做法是先把分母替换掉,再做除法。这样进入除法的分母已经确定不为零,也就不存在除零这一步。
{ 写法一:先替换分母,再做除法 }
DENOM:=VOL;
SAFE:=IF(DENOM=0,1,DENOM); { 分母为零时替换成 1 }
RATE1:AMOUNT/SAFE;
{ 写法二:把条件做成整体判断,输出空值占位 }
RATE2:IF(DENOM=0,DRAWNULL,AMOUNT/DENOM);
{ 两种写法的差别:第一种在分母为零那根输出原始值,
第二种把那一根留空,图上不画 }
两种写法没有优劣之分,选择取决于你要什么。要图上连续不断线,用第一种;要明确标出数据不可用的位置,用第二种。无论用哪一种,都要在源码里用注释写明为什么加这一步,否则过一段时间回头看会不知道为什么有这个替换。
保护写法还会影响输出数量的统计。用空值占位的那一种,被占位的那几根不计入计数;用替换分母的那一种,那几根会带着一个有值的输出进入统计。两者在同一个区间上算出的计数不同,跨区间比较时要保持写法一致。
五、为什么不报错反而更麻烦
编译期检查的是语法,比如括号是否配对、变量名是否重复、函数名是否存在。除零与空值都属于运行期的取值问题,语法上完全正确,所以编译不会给出提示。这决定了它们只能靠输出本身来发现。
另一个原因是异常值出现在少数几根上。一段几百根的序列里出现两三根异常,图上的整体形态看不出问题,只有把那几根放大或者把鼠标移过去看数值才能发现。如果这几根上正好有你关心的输出,判断就会受它影响。
通达信公式异常值的处理,因此更适合放在写作阶段而不是事后排查。凡是源码里出现除法的位置,都先问一句分母可能取到零吗;凡是向后取值的函数,都先问一句起点附近几根怎么办。两个问题在写的时候各花几秒钟,比事后到图上找那几根异常点要省事。
六、验证处理是否生效
第一步是把输出挂到副图上,找到历史上你已知有停牌或一字板的品种区段,看那个位置的输出是断线还是取值。两种情况都能接,关键是与你设计时的预期一致。
第二步是把手算值对一遍。挑一根分母不为零的 K 线,用行情数据手算一次,与公式输出对照。这一步确认的是正常路径没有被保护写法改坏——加了替换之后,非零路径上的计算应当与加保护之前完全一致。
第三步是把保护前后的两条输出同时挂上,看它们在哪几根上有差别。差别出现的位置,就是分母曾经取到零的位置,把这些位置记录成一张清单,回头核对是否与停牌、一字板的日期对得上。对得上,说明保护命中的正是预期的那几根。
站内还有几处可以对照查看:公式编写与语法函数 一栏讲条件与取值类函数的写法,主图副图指标 一栏讲输出量纲与坐标轴的适配,公式报错与卡顿排查 一栏讲编译期提示的对照。三处侧重点不同,按需要挑一处看即可。
文中出现的函数名、文件名与菜单路径以通达信金融终端个人版为参照;不同版本对除零与空值的处理可能有差异,请以你所用版本的「帮助 → 关于」与公式编辑器内的函数说明为准。
