高频交易中,咱们一般首先基于tick级的报价信息和交易信息来生成信号量,而后将这些信号量转化成离散的买卖信号,譬如说 1 (买入), 0 (不变), -1(卖出),接着根据资金和已有头寸以及其余优化规则来生成订单发送到交易系统。本文要讨论第二个步骤,即如何将信号量转化成离散的买卖信号,也就是把一个浮点数类型的数组signal转化成一个取值为1,0或-1的整型数组direction。编程
若是转化规则简单,譬如超过某一个阈值t1为+1 (买入信号),低于某一个阈值t2为-1(卖出信号),其余状况为0,那么实现起来也很简单。譬如在DolphinDB中用下面这个表达式就能够实现。数组
iif(signal > t1, 1, iif(signal <t2, -1, 0))
实践中,为了让系统更加的健壮,不要频繁的切换买卖方向,一般不会这么处理。一个经常使用的作法是这样:当信号量超过某一个阈值t1时,开始转化为买入信号,后续的信号量在衰减到低于t10以前,一直保持买入信号(+1);同理当信号量低于某一个阈值t2时,开始转化为卖出信号,后续的信号量在加强到大于t20以前,一直保持卖出信号(-1);其余状况为0。这儿t1, t10, t2, t20知足下面的规则:app
t1 > t10 > t20 > t2
当系统按照上面的规则运行时,决定买卖方向的除了当前的信号量值,还有前一个买卖信号的状态,这是典型的路径依赖问题。一般咱们认为路径依赖问题不适合向量化的方法来处理,或者说须要很是高的技巧。而咱们用来回测高频数据的语言一般都是脚本语言(譬如DolphinDB和kdb+),脚本语言在处理量化问题时效率很高,可是若是须要逐行处理路径依赖问题,解析成本会很高,效率低下。今天咱们会介绍一些技巧,如何化解这个矛盾?dom
咱们先找出买入信号。在一个向量中找到大于t1的点很容易(买入信号的临界点),找到不多是买入信号的点也很简单(小于t10)。这样咱们把一个向量上的点分红了三种状态,买入信号临界点(+1),不多是买入信号的点(0),其余状态未知的点(NULL)。根据前面的规则,若是状态未知的点,前面出现了买入临界点,那么该点也应该置为买入信号点;若是前面出现了非买入信号点(0),那么该点也应该置为非买入信号点。所以咱们可使用front fill来实现。咱们用一样的方法能够找出卖出信号(卖出信号为+1,其余信号为0)。二者相减能够获得最终的信号。可能还存在一些为null的信号,把这部分信号替换为0。DolphinDB的所有代码以下:编程语言
buy = iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)).ffill() sell = iif(signal <t2, 1h, iif(signal > t20, 0h, 00h)).ffill() direction = (buy - sell).nullFill(0h)
上面的代码能够合并成单个表达式:ide
direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)
一个简单的测试以下:函数
t1= 60 t10 = 50 t20 = 30 t2 = 20 signal =10 20 70 59 42 49 19 25 26 35 direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h) [-1,-1,1,1,0,0,-1,-1,-1,0]
若是改用kdb+脚原本实现,则表达式以下:性能
direction: 0h^fills(-).(0N 1h)[(signal>t1;signal<t2)]^'(0N 0h)[(signal<t10;signal>t20)]
若是使用pandas实现,代码以下:测试
t1 = 60 t10 = 50 t20 = 30 t2 = 20 signal = pd.Series([10,20,70,59,42,49,19,25,26,35]) direction = (signal.apply(lambdas: 1 if s > t1 else (0 if s < t10 else np.nan)) - signal.apply(lambdas: 1 if s < t2 else (0 if s > t20 else np.nan))).ffill().fillna(0)
下面咱们生成一个长度为1000万的在0~100之间的随机信号数组,测试DolphinDB、kdb+和pandas的性能。测试使用的机器配置以下:优化
CPU:Intel(R) Core(TM) i7-7700 CPU @3.60GHz 3.60 GHz
内存:16GB
OS:Windows 10
DolphinDB耗时330ms, kdb+耗时800ms,pandas耗时6.8s左右。DolphinDB的测试脚本以下:
t1= 60 t10 = 50 t20 = 30 t2 = 20 signal = rand(100.0, 10000000) timer direction = (iif(signal >t1, 1h, iif(signal < t10, 0h, 00h)) - iif(signal <t2, 1h, iif(signal > t20, 0h, 00h))).ffill().nullFill(0h)
kdb+的测试脚本以下:
t1:60 t10:50 t20:30 t2:20 signal: 10000000 ? 100.0 \t 0h^fills(-).(0N 1h)[(signal>t1;signal<t2)]^'(0N 0h)[(signal<t10;signal>t20)]
pandas的测试脚本以下:
import time t1= 60 t10= 50 t20= 30 t2= 20 signal= pd.Series(np.random.random(10000000) * 100) start= time.time() direction= (signal.apply(lambdas:1 if s > t1 else (0 if s < t10 else np.nan)) - signal.apply(lambdas:1 if s < t2 else (0 if s > t20 else np.nan))).ffill().fillna(0) end= time.time() print(end- start)
经过上面这个例子,也不难发现,DolphinDB和kdb+的脚本在本质上有不少共性的东西。kdb+的脚本基本上能逐句逐词的翻译成DolphinDB脚本。区别在于kdb+是从左到右解析脚本的,而DolphinDB跟常规的编程语言同样,是从右到左;kdb+喜欢用符号来表明某一个功能,而DolphinDB更喜欢用函数来表达某一个功能,可读性会比较好但也会冗长一点。