如何对选股因子进行量化回测?

引言 上一篇推文《

什么是多因子量化选股模型?

》主要介绍了多因子模型产生的理论背景、基本原理和实现步骤,而《

【手把手教你】Python量化Fama-French三因子模型

》则对国内A股市场的三因子模型进行了实证分析。多因子量化模型研究的对象主要是因子,因此单因子的回测和有效性检验是整个多因子模型的重要组成部分。本文结合Python开源包Alphalens,以A股市场真实场景数据,手把手教你对单因子进行量化回测。Alphalens是Quantopian公司(美帝最大的量化回测平台之一,国内几个基本上都是仿他们家的)三大知名Python开源包之一,其他两个分别是Zipline(策略回测,一直没安装成功)和Pyfolio(策略分析)。Alphalens主要提供因子收益分析、因子IC分析、因子换手分析和事件研究等回测框架,由于简单易上手和科学稳定等优点,是量化分析师最常用的回测工具包之一。
数据预处理 数据预处理是使用Alphalens做单因子回测的最主要工作,包括获因子数据,数据归一化和异常值处理等,将原始数据整理成符合要求的格式后,后面的分析就变得很简单,基本上都是一行代码搞掂。本文以市盈率(PE)指标为例,使用tushare的每日指标接口(daily_basic)获取A股市场3000多只股票2011-2019年收盘价和市盈率数据,为大家展示如何使用alphalens做单因子的历史回测。
#先引入后面可能用到的包(package)
import alphalens #使用pip安装即可
import pandas as pd  
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline   

#正常显示画图时出现的中文和负号
from pylab import mpl
mpl.rcParams['font.sans-serif']=['SimHei']
mpl.rcParams['axes.unicode_minus']=False
#还是使用tushare获取数据
import tushare as ts
token='输入token'
pro=ts.pro_api(token)
#获取当前交易的股票代码和名称
def get_code():
    df = pro.stock_basic(exchange='', list_status='L')
    #剔除2017年以后上市的新股次新股
    df=df[df['list_date'].apply(int).values<20170101]
    #剔除st股
    df=df[-df['name'].apply(lambda x:x.startswith('*ST'))]
    codes=df.ts_code.values
    return codes 
df=pro.daily_basic(ts_code=get_code()[0],start_date='20110101')
for code in get_code()[1:]:
    df1=pro.daily_basic(ts_code=code,start_date='20110101')
    df=pd.concat([df,df1])
df_new=df.loc[:,['ts_code','trade_date','close','pe_ttm']]
df_new.loc[:,'trade_date']=pd.to_datetime(df_new.trade_date)
#设定双重重索引的数据格式
df_new=df_new.set_index(['trade_date','ts_code'])
#根据第一索引排序
df_new=df_new.sort_index()
#查看数据前几行
df_new.head()

其中,pe_ttm是动态市盈率,是本文重点考察的单因子,数据预处理的第一步是建立以日期、股票代码的双重索引,如上表所示。先来看下全市场收盘价和市盈率的描述性统计,市盈率有440万个观测值(删除了市盈率为负的观测值),均值为206,标准差高达12698,最大值为2653832,而75%分位数才77.3,可见均值受极端值影响很大,有必要对原始数据进行归一化和缩尾处理。factor_new是处理后的因子数据。prices是收盘价数据(前复权),必须转化为日期为索引,列名是相应股票代码或名称格式的数据形式。

df_new.describe().round(2)#对数据进行缩尾处理,并进行标准化
def winsor_data(data):
    q=data.quantile([0.02,0.98])
    data[data<q.iloc[0]]=q.iloc[0]
    data[data>q.iloc[1]]=q.iloc[1]
    return data
#数据标准化
def MaxMinNormal(data):
    """[0,1] normaliaztion"""
    x = (data - data.min()) / (data.max() - data.min())
    return x
factor=df_new['pe_ttm'].groupby('trade_date').apply(winsor_data)
factor_new=factor.groupby('trade_date').apply(MaxMinNormal)
factor_new.hist(figsize=(12,6),bins=20)

prices=df_new['close'].unstack()

prices.head()

alphalens库所用到是函数名称都非常长,其中数据预处理函数是get_clean_factor_and_forward_returns,要想了解函数的详细参数可以输入help(函数名),这里不考虑分组(行业),其他参数均使用默认,factor_new为因子数据,prices为收盘价数据,quantiles=10表示将数据分成10个分位数进行考察,period是调仓周期,10,20,60,表示分布考察10、20、60日调仓情况。进行到这一步,表明数据清洗工作已完成,下面主要围绕单因子的收益率、信息比率和换手率进行回测和分析。


data=alphalens.utils.get_clean_factor_and_forward_returns(factor_new,prices,
                                                          quantiles=10,periods=(10,20,60))
data.head()

收益率分析Returns Analysis

单因子的收益率分析主要围绕10、20、60日(可自己设定)调仓周期,将因子数据从小到大排列,分为10个分位数进行回测,一行代码便可实现,但函数名称实在太冗长了。第一张表报告了不同调仓期的alpha、beta、高分位(top quantile)和低分位(bottom quantile)的收益情况。柱状图清晰地显示出该因子(市盈率)随着分位的提高收益由正转负,可见市盈率因子与股票收益存在一定的反向关系,即市盈率越小,未来收益越高,与理论和直觉基本一致。小提琴图是多收益率的分布,60日调仓的收益更加集中,方差更小,收益率更加可信。  由于对因子进行了归一化化处理,因此可以以因子值为权重构建一个全市场的股票组合进行回测。对于cumulative return by quantile的评价标准是累计收益率是否发散,越发散越好,说明因子的区分度越高,60天调仓期出现了明显的发散。好的信号:波动小,在某一个方向的占比处于绝对地位,如大部分的Top minus bottom都是正的。


alphalens.tears.create_returns_tear_sheet(data)

信息分析 Information analysis

信息分析说白了是考察IC和IR指标。IC全称为Information Coefficient, 即信息系数,代表因子预测股票收益的能力。其原理是通过计算全部股票在调仓周期期初排名和调仓周期期末收益排名的线性相关度(correlation)。 不难理解,IC绝对值越大的因子,选股能力越强。IC的值在[-1,1]区间内,最大值为1,表示该因子100%准确,对应的是排名分最高的股票,选出来的股票在下个调仓周期中,涨幅最大;相反,如果IC为-1,则代表排名最高的股票,在下个调仓周期中,涨幅最大,是一个完全反向指标,也是非常有意义的,使用的时候反向操作即可。一般而言,当IC的绝对值<0.05时,说明该因子对于的股票没有任何预测能力或较差,当IC绝对值>0.05时,因子选股能力有一定的意义,当IC大于0.5时因子稳定获取超额收益能力较强。


IR全称是Information Ratio,等于IC的多周期均值/IC的标准差,或等于超额收益均值/超额收益标准差,代表因子获取稳定Alpha的能力。 整个回测时段由多个调仓期组成,每一个周期都会计算出一根不同的IC值,IR等于多个调仓周期的IC均值除以IC的标准差。因此IR兼顾了因子的选股能力和因子选股能力的稳定性,IR值越大越好。


information analysis表给出了每个调仓周期下,IC的均值、方差以及t统计量,还有偏度、峰度以及年化的IR。因子信息分析好的评判标准是:IC的均值较高,方差小,而t统计量大(一般大于2)或p-value小(一般小于5%)。从结果上看,PE因子的IC均值都比较小,看来不是很理想的选股因子偏度和峰度主要考察和正态分布的偏离程度,主要用于分析小概率事件的影响。

alphalens.tears.create_information_tear_sheet(data)

Q-Q plot图是查看样本分布与正态分布的对比情况,如果满足正态分布,Q-Q图上的点应该在y=x上。虽然有些IC的取值挺大的,但是尾部比较大,均值大是由于极度的尖峰和右偏造成的。还需具体考察IC的分布来决定一个因子的预测能力是不是可靠。


热力图提供了不同时间段因子的预测能力表现,有利于进行因子的情景分析。通过热力图我们可以轻易识别哪些时间段IC值比较好,哪些时间段IC比较差,有没有突发的情况,然后可以对此进行情景分析。

ic=alphalens.performance.mean_information_coefficient(data,by_time='1y')
from pyecharts import Bar
attr=ic.index.strftime('%Y')
v1=list(ic['10D'].round(2))
v2=list(ic['20D'].round(2))
v3=list(ic['60D'].round(2))
bar=Bar('IC均值:2006-2019')
bar.add('10D',attr,v1)
bar.add('20D',attr,v2)
bar.add('60D',attr,v3)
bar



因子换手率 Turnover analysis

实际交易是有手续费的,因子对单因子的回测还需要考虑换手率的影响,换手率越高说明交易次数越多,可能产生的手续费越高。如果一个因子,今天给一个股票打了很高的分,明天就打很低的分,这将导致对该股频繁的买卖,从而造成很高的手续费。所以,因子的换手率分析也是很重要的一个部分。


还有一个角度可以查看因子造成的换手率高低,就是因子的自相关性。如果因子的自相关性低,那么就一会儿高,一会儿低,往往会造成很大的换手率。而因子的自相关性高,就不会有这样的问题。因子的截面换手率越低,因子的自相关性越高

alphalens.tears.create_turnover_tear_sheet(data)

结语 本文以动态市盈率作为选股因子,旨在为大家展示如何利用alphalens进行单因子的历史回测,没有考虑行业或市值大小的分组因素影响,实战应用到量化因子筛选时还需考虑更多因素和细节的处理,包括调仓周期和分位数确定等,本文仅提供一个因子量化回测的一般分析框架,不构成任何投资建议。关于alphalens的详细使用教程可参看其官网或东北证券金融工程研报《Alphalens使用教程》,公众号后台回复“Alphalens ”即可下载。
关于Python金融量化


专注于分享Python在金融量化领域的应用。加入知识星球,可以免费获取30多g的量化投资视频资料、公众号文章Python完整源码、量化投资前沿分析框架,与博主直接交流、结识圈内朋友等。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容