TSL语言基础 > SQL基础到TS-SQL > TS-SQL进阶 > TS-SQL与时间序列处理支持

时间序列数据分析的特殊需要    

  •   时间序列数据处理往往有着和其他数据处理不同的应用要求。我们回顾下TS-SQL对聚集函数的支持,如果我们需要求所有数据的最高气温、最低气温以及平均气温,使用select avgof(["平均气温"]),MaxOf(["最高气温"]),MinOf(["最低气温"]) from R end就可以了,但是我们在做天气研究的时候,往往我们需要的是每天的最近十日以来的最低气温、最高气温以及平均气温。
    这种需求就是移动的序列化支持,另外一种

    由于这个气温数据被序列化。
    聚集函数以及时间序列支撑函数进行时间序列分析是TS-SQL的特色,但正是由于其特殊性,即便是许多TSL的老用户对如何利用TS-SQL处理时间序列并不熟悉。下边我们来进阶下TS-SQL时间序列处理的功能。
    说到时间序列处理,事实上我们可以分割成两个部分,其中一个是时间序列化,另一个则是序列化分析处理,关于时间序列化而言,可以理解为数据整理的功能,例如,高频数据的秒、分钟,日间数据的日、周、月、年等数据的周期化,此外,序列化的同时还需要剔除掉非交易日的时间,这些内容大部分熟悉TSL语言的数据访问的用户大多非常熟悉,在这里我们反而不做重点介绍,详细可以查阅TS-SQL的select SelectList from markettable datekey BegTime to EndTime of TargetCode/TargetCodeList的相关用法。
    我们在这里要着重在序列化分析处理上,这是数据分析的能力,例如在markettable中,数据已经被序列化了,或者用户存在一个被序列化好的结果集,我们如何对其进行分析处理呢?
      REFOF,时间序列化的第一种需求,相对位置需求:
    在时间序列处理中,前后相关的位置的数据往往非常重要,例如,我们要访问N个交易日前或者N个交易日后的数据,也就是说,我们需要提取序列中相对于当前位置的数据。在标准的SQL中,缺乏这种能力,TS-SQL中加入了REFOF关键字。
    假定R结果集中包括有close列
    那么select ["close"]/refof(["close"],1) - 1 from R end;就可以返回出每天的涨幅,这足够简单吧。
    我们理解refof的功能是以向前推移当前序列到指定的行再计算第一个参数的表达式,第二个参数为推移的行数,如果第二个参数为负则朝后推移。
    花费这么多的气力来描述,仅仅只是提醒读者,我们不用把refof(["close"]+["open"],1)写成refof(["close"],1)+refof(["open"],1),前者的访问效率接近后者的两倍。
    了解序列的移动处理
    我们可能经常需要使用移动N条的标准差,移动N条的平均数,移动N条的最大值,这类应用需求在标准SQL中的应用简直是一场噩梦。
    很幸运的是,TS-SQL的所有聚集函数均支持移动处理,下面我们仅仅使用求平均的聚集函数,至于TS-SQL支持哪些聚集函数,用户可以TSDN找到答案。
    对于标准SQL的平均数聚集的定义为:Average ( [ DISTINCT ] Expression)
    其含义我们不多说,对于不是特别了解SQL的用户而言,可能比较陌生的是其Distinct前缀,也就是说,允许去重之后求平均,如果不带DISTINCT,则是所有的值求平均。
      TS-SQL的平均数聚类的定义为:
    AVGOF ( [ DISTINCT ] Expression [,BoolConditionExp[,N[,MovingFirst[,CacheId]]]] )
    和SQL的Average相比,TS-SQL增加了四个可选参数,一个是一个条件表达式,另外一个则是N,我们在这里姑且先不理会增加的第一、三、四个参数,对于第二个参数N,肯定是绝对多数人所希望看到的参数,这就是移动序列分析所必备的。
      我们来看一个典型的应用案例:
      R:=Rand(100,1);
      Return select [0],AvgOf([0]) as 1,AvgOf([0],True,10) as 2 from R end;
      我们将结果集以图形来呈现:

     得到的有三条线,一条是随机数线,一条是整体的平均值,是一条直线,另外一条蓝色线则是一个移动平均后的平滑线。
      也就是说,TS-SQL的Avgof支持移动平均,N的参数是否很容易理解呢?
      在现实应用中,我们在做移动的时候很可能需要有条件的移动,例如,一个高频交易明细序列,由于记录了所有的行情波动,包括买卖盘的波动,这样可能会出现有交易量为0的点。但是我们可能有一个需求,是要求最近N个有交易的点的价格的平均,这样在移动的时候我们就需要可以支持起条件移动。
      这个时候,TS-SQL的聚集函数中增加的第一个参数就非常有意义了。
      例如,我们在万科的明细里返回从昨日到当前的时间、价格、最近10个成交价的平均数,我们可以如此写:
      select datetimetostr(["date"]), ["close"], avgof(["close"], ["vol"]>0,10) from tradetable datekey now()-1 to now() of "SZ000002" end;
    为了更清晰地看到其运行的结果,我们不妨用一个简单的数据序列来替代。我们假定有结果集R,R为一维数组,内容如下array(1,3, 5, 0, 2,0, 4,5,3)
      R1:=sselect AvgOf( ThisRow,ThisRow>0,3) from R end;
    这里对于还不是特别掌握TS-SQL的读者,我们要做一个特别的提示,由于一维数组没有列概念,所以没有办法用[列标]的模式去访问列的内容,而ThisRow代表的是当前行的内容,对于一维数组而言,ThisRow存贮的就是当前下标对应的值(还有一个ThisRowIndex对应当前的下标),而select默认返回的结果集是一个二维结构,在处理一维数组的时候,往往希望返回的也是一维数组,用sselect而不是select则起到这个作用。

    我们在平台上运行可以知道R1的结果为array(1.00,2.00,3.00,3.00,3.333,3.333,3.667,3.667,4.00),前两个结果是在数据点不够的情况下,自动转换成了1个点的平均1/1=1,两个点的平均(1+3)/2=2,第三个点为(1+3+5)/3=3,第四个点则从0开始往前推移3个>0的点进行平均,得到的是(1+3+5)/3=3,第五个点为(2+5+3)/3=3.333,第六个点为(2+5+3)/3=3.333,第七个点为(4+2+5)/3=3.667,第八个点为(5+4+2)/3=3.667,第九个点为(3+5+4)/3=4。我们可以清晰地看到TS-SQL的条件移动平均是先满足条件,后计算数据的移动。
    上述这种移动平均我们可以理解为最近的三个大于0的数的平均。
    在许多时候,我们需要的带条件移动平均是最近三个中大于0的数的平均,现实中的典型应用是:移动的最近10个交易日中上涨时的平均成交量,对于这类的应用,上述条件移动平均就无法直接达到要求了,那么,我们是否具有折中的办法呢?其实,即便利用上述的条件移动平均也可以实现,只是需要对任务进行步骤分解,将其分解为:得到最近10个交易日中的上涨天数,利用上涨天数求移动平均,这里,我们需要使用到聚集函数CountIfOf来求天数,我们以万科为例,其中BegDate,EndDate为起止时间段:
      select N:=CountIfOf(["close"]>refof(["close"],1),10),AvgOf(["vol"],["close"]>refof(["close"],1),N) from markettable Datekey BegDate to EndDate of "SZ000002" end;
    在这里,我们利用了在select计算的过程中使用变量N将CountIfOf返回的个数记录了下来,并将N作为了AvgOf的参数,看起来是否是很巧妙呢?但是,我们在运行的时候,会遇到一个问题,就是N可能为0,因为在10个交易日中连续下跌的可能性是存在的,而0日平均是不被允许的,为了解决这个问题,我们可以将代码修改如下:
      select N:=CountIfOf(["close"]>refof(["close"],1),10),N?AvgOf(["vol"],["close"]>refof(["close"],1),N):0 from markettable Datekey BegDate to EndDate of "SZ000002" end;
    利用:表达式,我们把N为0的特殊情况彻底解决了。
      TS-SQL对上述问题的直接支持办法
    上述办法虽然可以解决问题,但是读者可能还是会觉得很麻烦,要是直接支持多好。事实上,目前的TSL语言本身就支持这种应用,我们还是用前边的R来做结果集,做一个移动的三个数据中大于0的数的平均。
       R1:=sselect selectOpt(64) AvgOf( ThisRow,ThisRow>0,3) from R end;
    先来看下结果是否正确,运行得到的结果是:
    array(1.00,2.00,3.00,4.00,3.5,2.00,3.00,4.5,4.00),为了方便起见,我们把R的内容在这里重复一下,R的内容为:array(1,3, 5, 0, 2,0, 4,5,3),原先的先判断再移动的结果为array(1.00,2.00,3.00,3.00,3.333,3.333,3.667,3.667,4.00)。
    由于前三个点没有为0的情况,所以前三个数据的结果与原来是一样的,依次为1,2,3,第四个点是(3+5)/2=4,第五个点是(5+2)/2=3.5,第六个点是2/1=2,第七个点是(4+2)/2=3,第八个点是(4+5)/2=4.5,第九个点是(4+5+3)/3=4。结果和我们验算的是一样的。
       OK,这样是否比前一种解决方案更好呢?
    假如有一种情况,我们既需要得到最近3个数据大于0的数的平均,又要最近3个数据中数据大于0的数的平均,我们可以怎么办呢?在N之后的参数可以解决这个需要,如:
    R1:=sselect AvgOf( ThisRow,ThisRow>0,3,true),AvgOf( ThisRow,ThisRow>0,3,false) from R end;该参数的意义就是决定N到底是先移动还是先判定条件,如果为真,就先移动,则计算N个内符合条件的,位假表示先判定,则计算最近N个符合条件的。