【宽客学院】自定义标注

stockranker
ai
宽客学院
标签: #<Tag:0x00007f5bf545c0b0> #<Tag:0x00007f5beb82ff20> #<Tag:0x00007f5beb82fde0>

(iQuant) #1
作者:bigquant
阅读时间:15分钟
本文由BigQuant宽客学院推出,难度标签:☆☆☆☆

导语:本文标题为自定义标注,其实就是想告诉大家如何灵活地对数据进行标注,从而得到预测能力更强的机器学习算法。

谈标注一词之前,我们先简单了解机器学习算法中的分类和回归。

分类问题是监督学习的一个核心问题。在监督学习中,当输出变量Y取有限个离散值时,预测问题便成为分类问题。监督学习从数据中学习一个分类模型,称为分类器(classifier)。分类器对新的输入进行输出的预测,这个过程称为分类。

当输出变量Y为有限个离散值时,成为分类问题,那如果输出变量Y是连续值时,又该怎样处理呢?可能大家马上想到这其实就是回归问题,用回归算法就可以解决。的确如此,但很多时候,回归算法预测效果不好。此时,我们可以对连续性数值进行标注,将Y标注为多个类别,这时又可以通过分类算法来解决。对数据进行标注在图像识别、文本分析、语音分析中经常遇到,标注的思想也广泛存在于机器学习领域。将数据标注为多个离散值成为分类标注,将数据标注为连续性数据称为回归标注。

对股票进行标注然后结合股票的特征是否能训练出一个有预测能力的模型呢?这正是许多机器学习算法在在量化选股领域的尝试。股票标注可以直接影响到AI策略的效果,可见其重要性,接下来我们详细介绍如何对股票进行标注。

数据标注应注意的几点:

  • 数据标注既包括分类标注也包括回归标注。分类标注为将数据分为具有区分性的多个类别,回归标注后数据为连续性数据。分类标注比较常用。

  • 数据标注时,应尽可能结合机器学习的算法预测目的。如果目标是想预测收益率较高的股票,在标注时应结合股票收益率;如果目标是想预测波动率较低的股票,在标注时应结合股票波动率。

  • 数据标注时,应尽可能将数据区别开来,但又不可分得太细。比如,通过股票收益率将股票分为五类,分别为高收益、较高收益、一般、较低收益、低收益,因此此时就可以采取分类算法。如果分得太细,可能算法在训练集上会学到不少数据噪音,泛化能力不强。

  • 分类标注中标注后的数据不一定是具体的类别,而是具体的数值。比如,'数值>=20’为高收益股票,"15<数值<20"为较高收益股票,"10<数值<15"为一般股票,“5<数值<10”为较低收益股票,“数值<5”为低收益股票。

数据标注和特征工程一样重要,共同决定了机器学习算法的预测能力。数据标注确定的标注结果和特征工程确定的因子数据合并起来就形成了训练集数据,已经可以训练出一个学习算法。当我们得到学习算法后,传入测试集的因子数据就可以得到预测结果,通过回测就可以开发AI策略。如下图所示:

image

在BigQuant上,数据标注有专门的模块接口,方便大家高效灵活地进行标注。本文简单枚举了一些标注数据的应用例子,希望大家理解以便开发出更好的AI策略。

1.根据收益率进行标注

代码示例:

# 标注语句  这句代码的意思就是根据收益率将股票标注在[0,40]之间,标注结果称为股票分数
label_expr = [
    # 将百分比收益乘以100
    'return * 100',
    # where 将分数限定在[-20, 20]区间,+20将分数调整到 [0, 40] 区间,如果想改变标注区间,修改具体数值即可
    'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(20)
]

# 标注函数
m = M.fast_auto_labeler.v7(
    # instrument表示股票池  start_date和end_date决定了股票时间段
    instruments=['000001.SZA', '600519.SHA'], start_date='2017-01-01', end_date='2017-02-01',
    # label_expr表示标注过程   hold_days为持仓时间
    label_expr=label_expr, hold_days=5,
    # 如果涉及相对收益率,还要传入比较基准  sell_at和buy_at表示以什么价格计算持仓期间的收益率
    benchmark='000300.SHA', sell_at='open', buy_at='open')

标注结果为:

这幅柱状图描述了整个训练集中各个label的分布情况,比如,最右边的柱状图表示标注为23分的观测有两个。

代码解读:

  • 根据未来几天的收益率进行标注可以直接修改hold_days。
  • 在计算持仓期间的收益率可以直接修改sell_at和buy_at参数,修改为’close’表明以收盘价计算收益率。
  • 修改标注语句label_expr可以直接修改标注分数区间。
  • instrument、start_date、end_date共同决定了对什么时间段哪些股票进行标注。
  • 如果不涉及相对收益率可以不用传入benchmark参数。
  • 标注函数的参数:is_regression (boolean) 表明label是否用来训练回归类模型。默认是False。
  • 标注函数的参数:filter_price_limit (boolean) 表明是否过滤一字涨跌停的数据。默认为True。
  • 标注函数的参数:plot_charts (boolean) 表明是否绘制结果数据,默认为True。如果修改为False,就不会输出上面的label柱状分布图。

更丰富的标注表达式:

# 标注数据为:相对收益率
label_expr = ['(return-benchmark_return) * 100', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(20)] 
# 标注数据为:经过波动率调整后的收益率(类似于夏普比率)
label_expr = ['(return / volatility**0.5) * 20', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(20)]   
# 标注数据为:经过平均真实波幅调整后的收益率 
label_expr = ['return / atr * 20', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(15)]   
# 支持更复杂的表达式
label_expr = ['100 * return / exp(0.6 * log(volatility)', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(20)]

2.标注结果分析

代码示例:

m.data.read_df()

结果解读:

  • 标注结果为DataSource类型,因此需要用read_df方法查看。
  • date和instrument为关键列,可以唯一确定一条观测。
  • return为持有期的收益率。一般常用其对股票进行标注。
  • return_f1表示持有一天的收益率。
  • volatility为持有期的收益率波动率,可用于衡量风险。
  • not_available 为布尔型变量,表明该观测当天label是否为缺失值。
  • benchmark_volatility 表示基准收益率波动率。
  • label表示标注具体的数值结果。
  • cannot_trading_f1表示次日是否停牌,停牌的话就不能交易。
  • 在标注结果里,最重要的三列就是date、instrument和label。

查看标注分布和绘图:

3.更灵活的标注

3.1 通过内置变量自动标注

class conf:
    start_date = '2011-01-01'
    end_date='2017-07-04'
    split_date = '2015-01-01'
    instruments = D.instruments(start_date, end_date)
    label_expr = ['(sell_price-buy_price)/atr*5', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(8)]
    hold_days = 3
    features = ['ta_sma_10_0/ta_sma_20_0']
 
m1 = M.fast_auto_labeler.v7(
    instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
    label_expr=conf.label_expr, hold_days=conf.hold_days,
    benchmark='000300.SHA', sell_at='open', buy_at='open')

代码解读:

  • buy_price指以buy_at的价格计算买入价格
  • sell_price指以sell_at的价格计算卖出价格
  • atr指50日平均真实波幅

3.2 手动计算标注

# 基础参数配置
class conf:
    start_date = '2010-01-01'
    end_date='2017-07-10'
    split_date = '2015-01-01'
    instruments = D.instruments(start_date, end_date)
    hold_days = 3
    features = ['ta_sma_10_0/ta_sma_20_0']

# 手动计算标注
df = D.history_data(conf.instruments, start_date=conf.start_date,end_date=conf.end_date,fields=['close','open','high','low','amount'])
 
# 增加一列数据的函数
def add_column(df, series, name):
    df[name] = series
    return df

# 计算atr函数
def atr(high,low,close,window):
    a=high-low
    b=np.abs(close.shift(1)-high)
    c=np.abs(close.shift(1)-low)
    tr=a.where(a>b,b)
    tr=tr.where(tr>c,c)
    return tr.rolling(window).mean()

# 计算ATR 
df = df.groupby('instrument').apply(lambda x:add_column(x,atr(x.high,x.low,x.close,50),'ATR'))
# 计算标注,标注这个地方可以试一试用 'close' 
df = df.groupby('instrument').apply(lambda x:add_column(x,(x.open.shift(-4)-x.open.shift(-1))/x.ATR,'label'))

# # 对标注数据进行一些转化,上下界限制
df['label']=df.label*5+10
df.label=df.label.where(df.label<20,20)
df.label=df.label.where(df.label>0,0)
 
# 删除一部分数据
df.drop(['low','high','amount','close','ATR','open'],axis=1,inplace=True)
# df['label'] = df['label']*10 # 如果小数太多,这是会影响到转为整数几乎一样,导致模型训练会出错
# 标注要为int格式
df.label = df.label.astype('int')
label_ds = DataSource.write_df(df)


# 计算特征数据
m2 = M.general_feature_extractor.v5(
    instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
    features=conf.features)
# 数据预处理:缺失数据处理,数据规范化,T.get_stock_ranker_default_transforms为StockRanker模型做数据预处理
m3 = M.transform.v2(
    data=m2.data, transforms=T.get_stock_ranker_default_transforms(),
    drop_null=True, astype='int32', except_columns=['date', 'instrument'],
    clip_lower=0, clip_upper=200000000)
# 合并标注和特征数据
m4 = M.join.v2(data1=label_ds, data2=m3.data, on=['date', 'instrument'], sort=True)
# 训练数据集
m5_training = M.filter.v2(data=m4.data, expr='date < "%s"' % conf.split_date)

代码解读:

  • 本例子对股票进行标注是纯手动计算,不是通过lebel_expr,这样更灵活。
  • 本例单独编写了一个函数计算atr,单独计算了一个函数对数据框增加列。
  • 标注结果保存为DataSourse类型,可直接传入M.join.v2模块。

3.3 根据收益率排序对股票标注


# 基础参数配置
class conf:
    start_date = '2011-01-01'
    end_date='2017-07-04'
    split_date = '2015-01-01'
    instruments = D.instruments(start_date, end_date)
    label_expr = ['return * 100', 'where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}'.format(10)]
    hold_days = 3
    features = ['ta_sma_10_0/ta_sma_20_0']

# 给数据做标注:给每一行数据(样本)打分,一般分数越高表示越好
m1 = M.fast_auto_labeler.v7(
    instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
    label_expr=conf.label_expr, hold_days=conf.hold_days,
    benchmark='000300.SHA', sell_at='open', buy_at='open')

# 通过收益率排序进行标注
df = m1.data.read_df()
def label(df):
    # 收益率排序结果
    df['label'] = df['m:Return'].rank(pct=True)/0.05+1
    # 转化为整型
    df['label'] = df['label'].astype(np.int32)
    return df

df = df.groupby('date').apply(label)
# 标注结果存储为DataSource
label_ds = DataSource.write_df(df)

# 计算特征数据
m2 = M.general_feature_extractor.v5(
    instruments=conf.instruments, start_date=conf.start_date, end_date=conf.end_date,
    features=conf.features)
# 数据预处理:缺失数据处理,数据规范化,T.get_stock_ranker_default_transforms为StockRanker模型做数据预处理
m3 = M.transform.v2(
    data=m2.data, transforms=T.get_stock_ranker_default_transforms(),
    drop_null=True, astype='int32', except_columns=['date', 'instrument'],
    clip_lower=0, clip_upper=200000000)
# 合并标注和特征数据
m4 = M.join.v2(data1=label_ds, data2=m3.data, on=['date', 'instrument'], sort=True)
# 训练数据集
m5_training = M.filter.v2(data=m4.data, expr='date < "%s"' % conf.split_date)

代码解读:

  • 关键步骤在于label函数,对收益率数据进行排序再标注。
  • 标注结果需要存储为DataSourse类型,以便传入M.join.v2。

小结: 可以看出,对股票数据标注的方法丰富多样,因此策略研究者的开发空间非常大,好的标注结果结合好的特征选择可以直接决定AI算法预测能力。


   本文由BigQuant宽客学院推出,版权归BigQuant所有,转载请注明出处。


数据标注与特征因子的疑问
WorldQuant 101alpha因子构建及因子测试
【宽客学院】选股+择时策略组合
可视化策略部分可否自定义一些变量?
策略研究常用功能
自动数据标注底层逻辑是怎么样的
(iQuant) #2

在之前介绍的文章里,主要使用的是M.fast_auto_labeler模块进行数据标注。现在,我们还可以使用 M.advanced_auto_labeler 进行快速、灵活标注。

克隆策略
In [1]:
## 基础配置
class conf:
    start_date = '2014-01-01'
    end_date='2017-07-17'
    split_date = '2015-01-01'
    instruments = D.instruments(start_date, end_date)
    hold_days = 30
    features = ['rank_pb_lf_0']
    # 数据标注标注
    label_expr = [
    # 计算未来一段时间(hold_days)的收益
    'shift(close, -5) / shift(open, -1)',
    # 极值处理:用1%和99%分位的值做clip
    'clip(label, all_quantile(label, 0.01), all_quantile(label, 0.99))',
    # 将分数映射到分类,这里使用20个分类,这里采取等宽离散化
    'all_wbins(label, 20)',
    # 过滤掉一字涨停的情况 (设置label为NaN,在后续处理和训练中会忽略NaN的label)
    'where(shift(high, -1) == shift(low, -1), NaN, label)'
    ]
#  M.advanced_auto_labeler模块的文档:https://bigquant.com/docs/module_advanced_auto_labeler.html
m1 = M.advanced_auto_labeler.v1(instruments=conf.instruments, start_date=conf.start_date, end_date=conf.split_date,
                               label_expr=conf.label_expr, benchmark='000300.SHA', cast_label_int=True)   
[2018-01-19 20:42:58.929149] INFO: bigquant: advanced_auto_labeler.v1 开始运行..
[2018-01-19 20:42:58.936363] INFO: bigquant: 命中缓存
[2018-01-19 20:42:58.937975] INFO: bigquant: advanced_auto_labeler.v1 运行完成[0.008855s].
In [3]:
# 标注数据为:
m1.data.read_df().head()
Out[3]:
m:open m:high m:low m:amount date m:close instrument m:benchmark_open m:benchmark_instrument m:benchmark_close label
0 707.651367 718.161072 703.564331 596223744.0 2014-01-02 714.073975 000001.SZA 2323.433105 000300.SHA 2321.978027 9
1 933.813477 943.163330 925.632385 387391072.0 2014-01-02 933.813477 000002.SZA 2323.433105 000300.SHA 2321.978027 7
2 47.303352 48.238041 46.531219 11417556.0 2014-01-02 47.994209 000004.SZA 2323.433105 000300.SHA 2321.978027 13
3 23.169001 23.354353 22.890972 7473591.0 2014-01-02 23.076324 000005.SZA 2323.433105 000300.SHA 2321.978027 8
4 151.738464 152.046249 148.968384 43051928.0 2014-01-02 149.891739 000006.SZA 2323.433105 000300.SHA 2321.978027 4

其他标注

未来10天的收益率

In [ ]:
'shift(close, -10) / shift(open, -1)'

标注相对收益率

In [ ]:
'shift(close, -5) / shift(open, -1) - shift(benchmark_close, -5) / shift(benchmark_open, -1)'

标注未来一段时间收益率与最大回撤之比

In [ ]:
# 分子为未来五天的收益率,分母为未来5天的最大回撤。最大回撤式子为:最后一天收盘价/期间内最高价
'(shift(close, -5)/close - 1) / (shift(close, -5) / shift(ts_max(close, 5), -5))'  # 在计算未来五天收益率的时候,根据需求,有时也用close,上面例子用的是shift(open, -1)

标注未来5天价格的标准差

In [ ]:
'shift(std(close, 5), -5)'  # 理解起来可能有点困难,首先根据std计算出标准差;然后因为是标注未来一段时间的标准差,因此需要用shift漂移一下

(a20180322) #3

[quote=“iQuant, post:1, topic:1229”]
label_expr = [‘return * 100’, ‘where(label > {0}, {0}, where(label < -{0}, -{0}, label)) + {0}’.format(10)]
[/quote]这行标注在可视化视图里怎么写


(a20180322) #4

在可视化视图标注里增加下面这段,收益就从默认年化100多将为60多,是什么原因


(iQuant) #5

您好,因为你修改了标注方式,这样会导致训练出不一样的模型,效果因此不一样。


(a20180322) #6

谢谢支持。但加的这段标注其实和默认的shift(close, -5) / shift(open, -1)意思差不多,就是标注下未来5天收益率,结果相差这么大,好比让电脑分别标注苹果一斤和一公斤多少钱,结果得到结果是完全不同的,请问深层次原因是什么


(a20180322) #7

把两个因子互换下位置shift(open, -1)/shift(close, -5),加到原来标注里,收益就变成负,有两个疑问
1.两个因子互换位置,算法就识别成完全不同因子,是否对表达式依赖过强,这样很容易差之毫厘谬以千里
2.原来标注并没有删除,只是加入基本一个意思标注,结果差很大,请问是否对好因子加权不够


(大胡子) #8

shift(open, -1)/shift(close, -5) 这个是因子还是标注,这个可以加到标注里吗?


(puppetect) #9

请问fast_auto_labeler里的is_regression和advanced_auto_labeler里的cast_label_int两个参数名字不同但功能是相同的吗?都用来指定分类/回归标注?(如果是,能统一命名吗)