核心速览
这篇文章讲述了机器学习模型使用的数据其实是不合理的,不应该使用time-base的烛图,而应该使用tick bars与volume and dollar bars
当前背景
- 高波动的市场,如bitcon市场
- 面对的交易对手是算法或自动交易
Time-base candles
Time-base的两个缺点
- 对低频数据过采样
- 对高配数据采用不足
先来点哲学层面的东西:
人类作为生物,更倾向于按照日光周期来构建数据集;但是算法交易更倾向于依赖CPU周期,那CPU有周期吗?显然是没有的....
下面是一个列子关于time-base数据两个缺点的显现
数据来自于2013.5 - 2019.4 的Bitcon-US dollar的五分钟烛图
- 直方图表示五分钟内的价格变化直方图(横轴是变化大小的百分比,纵轴是数量)
- 红线是从右往左看烛图数量的累计概率密度曲线
- 绿线是从右往左看烛图总量(烛图数量*变化价格)的累计概率密度曲线
从图中可以看出:
- 大约70%的烛图的价格变化低于0.25%,其中甚至没有变化
- 20%的烛图解释了几乎67%的价格变化总量
- 2%的烛图解释了21%的价格变化总量
所以这就是Time-base的缺点,有时候我们带着这样的数据去做ML的训练,有70%的数据是没有意义的,而2%的数据中其实蕴含了更大的信息没有被使用;因此,我们希望找到一种好的方法,在市场活跃的时候多采样,在市场平静的时候少采样
替代烛图
de Prado’s book Advances in Financial Machine Learning给出了许多替代的烛图
- Tick bars: 按照交易的次数来绘制烛图(如每200笔交易做一次采样)
- Volume bars: 按照交易量来绘制烛图(如每10个比特币做一次采样)
- Tick Imbalance bars: 计算交易序列的Imbalance程度,一旦失衡超出一个threshold就做一次采样
Tick Bars
Tick bars就是基于交易的次数来画一个烛图,因为交易次数数据难以获取,下面是一个机构的研究结果,第一个幅图是连续的交易价格;第二幅图是四个小时间隔的Time-base bar;第三幅图是1000次交易记录一次的Tick Bar;
注意下面蓝色的小点点,这表示采样点随时间的变化,Tick Bars有效解决了Time-base bar过采样与采样不足的缺点
Tick Bars的统计性质
序列相关(自相关性)
因为每个观测都应该是独立的,不依赖与前一个的,考虑计算序列(对数收益)的自相关性(用$log(R_t) 与 log(R_{t-1}$计算相关系数)
注意到前五列的数据,是tick bar的,而后面的是time bar的; 意味着tick bar的自相关性通常低于time bar的采样
还可以通过Durbin-Waston测试来判断序列相关性的存在,DB统计量的含义为
Value | Meaning |
---|---|
DB-statistic << 2 | positive serial correlation |
DB-statistic ~ 2 | no first-order correlation |
DB-statistic >> 2 | negative serial correlation |
可以看到结果与相关系数的计算一致
回报的正态性
有的人士非常在意回报的正态性,特别是那些做风控的,做对冲基金的,因为知道回报的分布往往有利于帮助他们决策,因为回报往往是服从对数正态分布的,对回报取对数就会服从正态分布
我们可以通过两个统计检验来检验正态性,Jarque-Bera Test, Shapiro-Wilk Test
Jarque-Bera Test是用于检验偏度和峰度,进而推断是否服从正态分布
该图是Jarque-Bera Test的出的p-value,不论是time bar还是tick bar,对数回报都不服从正态分布
Shapiro-Wilk Test与普通的假设检验没有什么区别,看p-value与显著性水平的大小即可
再看看Shapiro-Wilk Test给出的p-value,结果基本与上面一致
volume and dollar bars
volume and dollar bars则是按照交易量来绘制烛图,他也是在市场活跃的时候多采样,在市场平静的时候少采样,只不过采样方法不一样; 如在1000\$时采样一次
用算法打败算法
因为Tick bar是基于交易次数的采样,事实上交易次数的多少也不能反映市场的活跃程度,他很容易被操纵;例如:一个熟悉的算法或交易者可能会自动发出非常小的重复订单来影响市场情绪(这称为冰山订单)
所以我们应该使用交易量来采样,但是显然会丢掉交易数量序列中的信息,同时也会被Wash trading(买卖双方是同一个人)所控制
构建volume and dollar bars
虽然交易量的数据容易获取,但是真的要划分至1000\$的数据,还是需要分钟级别的数据的,这里还是引用作者的研究成果,第一个幅图是连续的交易价格;第二幅图是四个小时间隔的Time-base bar;第三幅图是1000个bit-coin交易记录一次的volume bars;第四幅图是1M\$交易记录一次的dollar bars
统计性质
操作方法与各种展示图就不放了,讲一下结论即可
Volume与Time-base
- 自相关性:volume bars略低于Time-base bar
- 正态性:对数收益都不服从正态分布
Dollar与Time-base
- 自相关性:两者差异不大
- 正态性:对数收益都不服从正态分布
烛图改变是否真的有效
观察下图,下图是所有类型的烛条在一天内采样的个数,与bitcon历史价格的关系图
可以看出我们甚至只要关心烛条的采样数就能知道价格的变化(当然这样决定是很武断的),当然在每个采样点上的数据仍然有被挖掘的价值
Imbalance bars
Imbalance bars是通过计算序列的变化特征来构造一个计算变量,当这个计算变量超过某个threshold的时候,就会被采样一次
构造计算变量
根据分钟数据, 构造一个与分钟数据长度等同的计算变量signed tick(暂且理解为一个元素),t时刻的计算变量signed tick规则是:
- 当t时刻的价格高于t-1时刻,记为1
- 当t时刻的价格低于t-1时刻,记为-1
- 当t时刻的价格等于t-1时刻,记为0
最后将这些signed tick加和起来,得到给定T下的Imbalance $$ \theta_T = \sum_{t=1}^Ts_t $$
threshold设定
在新一个Imbalance bars构造前,我们计算signed tick的EWMA(移动平均,一种预测趋势的方法),然后用EWMA乘上tick序列的长度,如果$\theta_T$大于这个值,就采样一次,否则继续; 用简单的算法表述如下
# (最理想的)输入: 每笔交易的价格序列 [0,N]
price_sequence = input() # 算法的输入
T = 0 # 表示当前时刻
EWMA_last = 0 # 用于存储上一时刻的EWMA
tick_number = 200 # 预期tick序列的长度
theta_T = 0 # 用于储存theta_T
while len(T) <= N:
singed_tick = singed(price_sequence[T] - price_sequence[T-1]) # 计算singed_tick
EWMA_last = a * singed_tick + (1-a) * EWMA_last # 计算当前时刻的EWMA
theta_T += singed_tick
if abs(theta_T) > EWMA_last * tick_number:
sampling() # 触发一次采样
theta_T = 0 # 重置theta_T
但愿我没有理解错,我将原生的英文算法描述贴在下面,然后这里还有一个Github Page里面是de Prado的官方定义
A visual example
以下是一个实现的示例,引用作者的研究成果
总结
本文中,我们核心要理解的是为什么Time-base bar在financial Machine Learning领域已经不适用了;我们重点讲述了tick bar与Volume and dollar bar,虽然他们能强化对活跃市场的采样,减少平静时期的采样;但是同时他们各有缺点,前者会被高频小订单confusion,而后者又会受到Wash trading的影响;
后面我们又介绍了一个Imbalance bar的做法,这个似乎是一个更优的采样方式,似乎能把tick bar与Volume and dollar bar融合,但是他有个极大的缺点,就是tick_number是一个经验参数,设置的不好就会导致采样数量过多,或者很长时间也采样不了一次;
他们各有缺点,前者会被高频小订单confusion,而后者又会受到Wash trading的影响;
后面我们又介绍了一个Imbalance bar的做法,这个似乎是一个更优的采样方式,似乎能把tick bar与Volume and dollar bar融合,但是他有个极大的缺点,就是tick_number是一个经验参数,设置的不好就会导致采样数量过多,或者很长时间也采样不了一次;
而在学术领域中似乎Volume and dollar bar使用更多,重点关注即可..
A plot
这里贴一个构造time-base bar的代码(与这篇文章没啥关系)
import tushare as ts
ts.pro_api('xxx') # token需要去tushare官网获取
def get_data():
df = ts.pro_bar(ts_code='601318.SH',
freq='30min',
start_date='2016-09-10 09:00:00',
end_date='2020-01-10 17:00:00')
print(len(df))
df.to_csv('中国平安30min级数据.csv')
# ----------------------------------------
import pandas as pd
from pyecharts import options as opts
from pyecharts.charts import Kline
import numpy as np
def import_data():
data = pd.read_csv('中国平安30min级数据.csv',index_col='index',usecols=['index','trade_date','open','close','high','low'])
data.sort_index(ascending=False,inplace=True)
index = data.trade_date.apply(lambda x:str(x)[0:4] + '/' + str(x)[4:6] + '/' + str(x)[6:])
return np.array(data).tolist(), np.array(index).tolist()
# 绘制30min烛图
def draw_data(data,index):
c = (
Kline()
.add_xaxis(index)
.add_yaxis(
"kline",
data,
itemstyle_opts=opts.ItemStyleOpts(
color="#ec0000",
color0="#00da3c",
border_color="#8A0000",
border_color0="#008F28",
),
)
.set_global_opts(
xaxis_opts=opts.AxisOpts(is_scale=True),
yaxis_opts=opts.AxisOpts(
is_scale=True,
splitarea_opts=opts.SplitAreaOpts(
is_show=True, areastyle_opts=opts.AreaStyleOpts(opacity=1)
),
),
datazoom_opts=[opts.DataZoomOpts(type_="inside")],
title_opts=opts.TitleOpts(title="Kline-ItemStyle"),
)
.render("kline_itemstyle.html")
)
# 绘制
if __name__ == '__main__':
get_data()
data,index = import_data()
draw_data(data,index)