本文参考海通证券《价量新因子测试》内容,感谢分析师冯佳睿、罗蕾在研报中提供的思路和方法。以下为我们通过数据及代码进行的分析例证。
研究目的:
根据研报分析,主要测试了两种价量新因子的选股效果,Neg_pos_ID 和 CO, 其中前者是下跌天数与上涨天数之差的衡量,后者是上涨时的交易活跃度(换手率、或成交额)与下跌时交易活跃度之差。基于该文章,本文对文章中提到的两种价量新因子进行因子有效性分析,从而实现对股票未来收益的预测,为 alpha 因子在的挖掘提供了一定思路。
研究内容:
(1)构建 Neg_pos_ID 因子,然后根据单因子有效性分析的方法,对该因子分别进行因子收益率显著性分析、因子 IC 分析以及分层组合回测分析;
(2)构建 CO 因子,然后进行单因子有效性分析,紧接着与市值、反转、换手率、波动率这四个因子进行组合分析;
(3)对这两个因子进行深入分析,增加了 6 因子(市值、反转、换手、波动、价值、营业利润同比增长率),分析因子对组合收益的贡献及其预测效果;
(4)分析因子敏感性,分别从成交额替代换手率以及分析不同构建期的情况,对 CO 因子进行深入分析。
研究结论:
(1)Neg_pos_ID 在一定程度上是一种价格动量因子,且通过对该因子进行有效性分析来看,该因子有效性较高,有较好的选股效果;
(2)CO 因子的选股效果并非线性,然后通过对该因子与市值、反转、换手、 波动、价值等四个因子的组合特征来看,CO 因子与市值负相关,
与反转因子正相关,与换手率因子正相关,与波动率因子正相关,然后分别对这四个因子进行控制,与原始 CO 因子选股相比,前几个组合的月均收益得到了明显提高。
(3)进一步对这两个因子进行分析,首先对 6 因子(市值、反转、换手、 波动、价值、营业利润同比增长率)与 Neg_pos_ID 以及 CO 分解得到的 CO_pos 和 CO_pos 因子进行横截面回归,得知新因子的增加会提高模型的预测能力,换言之,新因子包含了有用的额外信息。
(4)当以成交额代替换手率时,CO 因子的选股效果得到提升;当选择不同的构建期进行分析时,构建期较短时,选股效果更佳。
1 Neg_pos_ID 因子¶
根据研报内容,提到由 Da,Gurun 和 Warachka 在 2014 年发表的文章《Frog in the pan:Continuous information and momentum》中产生的的因子 $ PosID $ 和 $ NegID $。并在考虑到这两个因子在 A 股市场应用效果不好的情况,对因子进行改进,将上涨和下跌样本同等对待,从而产生了因子 $ Neg\_pos\_ID $。
在每个月最后一个交易日,获取过去一年(本文取 250 个交易日)的涨跌幅数据,计算其中收益为正的天数,记为 $ \%pos $,计算其中收益为负的天数,记为 $ \%neg $,然后根据这两个数据,计算新的因子数据,计算方式如下所示:
1.1 因子数据采集¶
本文设置时间区间为 2010 年至 2017 年,样本范围是全市场所有可交易股票去除 ST 股、上市不足 3 月新股。
from jqdata import *
import datetime
import pandas as pd
import numpy as np
from six import StringIO
import warnings
import time
import pickle
from jqfactor import winsorize_med
from jqfactor import neutralize
from jqfactor import standardlize
import statsmodels.api as sm
from jqfactor import get_factor_values
warnings.filterwarnings("ignore")
#获取指定周期的日期列表 'W、M、Q'
def get_period_date(peroid,start_date, end_date):
#设定转换周期period_type 转换为周是'W',月'M',季度线'Q',五分钟'5min',12天'12D'
stock_data = get_price('000001.XSHE',start_date,end_date,'daily',fields=['close'])
#记录每个周期中最后一个交易日
stock_data['date']=stock_data.index
#进行转换,周线的每个变量都等于那一周中最后一个交易日的变量值
period_stock_data=stock_data.resample(peroid,how='last')
date=period_stock_data.index
pydate_array = date.to_pydatetime()
date_only_array = np.vectorize(lambda s: s.strftime('%Y-%m-%d'))(pydate_array )
date_only_series = pd.Series(date_only_array)
start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
start_date=start_date-datetime.timedelta(days=1)
start_date = start_date.strftime("%Y-%m-%d")
date_list=date_only_series.values.tolist()
date_list.insert(0,start_date)
return date_list
#去除上市距beginDate不足 3 个月的股票
def delect_stop(stocks,beginDate,n=30*3):
stockList = []
beginDate = datetime.datetime.strptime(beginDate, "%Y-%m-%d")
for stock in stocks:
start_date = get_security_info(stock).start_date
if start_date < (beginDate-datetime.timedelta(days = n)).date():
stockList.append(stock)
return stockList
#获取股票池
def get_stock_A(begin_date):
begin_date = str(begin_date)
stockList = get_index_stocks('000002.XSHG',begin_date)+get_index_stocks('399107.XSHE',begin_date)
#剔除ST股
st_data = get_extras('is_st', stockList, count = 1, end_date=begin_date)
stockList = [stock for stock in stockList if not st_data[stock][0]]
#剔除停牌、新股及退市股票
stockList = delect_stop(stockList, begin_date)
return stockList
start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
dateList = get_period_date('M',begin_date, end_date)
factorData = {}
for date in dateList:
stockList = get_stock_A(date)
df_data = get_price(stockList, count = 251, end_date = date, frequency='1d', fields=['close'])['close']
df_data = df_data.pct_change().iloc[1:]
temp = df_data[df_data<0]
Neg_Pos_ID = temp.count() - (250 - temp.count())
Neg_Pos_ID = standardlize(Neg_Pos_ID, inf2nan=True, axis=0)
factorData[date] = pd.DataFrame(Neg_Pos_ID, columns = ["Neg_pos_ID"])
elapsed = (time.clock() - start)
print("Time used:",elapsed)
1.2 因子有效性检验¶
1.2.1 因子收益率有效性检验¶
主要通过 T 检验分析,根据 APT 模型,对历史数据进行进行多元线性回归,从而得到需要分析的因子收益率的 t 值,然后进行以下两个方面的分析:
(1)t 值绝对值序列的均值: 之所以要取绝对值,是因为只要 t 值显著不等于 0 即可以认为在当期,因子和收益率存在明显的相关性。但是这种相关性有的时候为正,有的时候为负,如果不取绝对值,则很多正负抵消,会低估因子的有效性;
(2)t 值绝对值序列大于2的比例: 检验 |t| > 2 的比例主要是为了保证 |t| 平均值的稳定性, 避免出现少数数值特别大的样本值拉高均值。
def factor_t_test(factorData, Field, begin_date, end_date):
dateList = get_period_date('M', begin_date, end_date)
WLS_params = {}
WLS_t_test = {}
for date in dateList[:-1]:
R_T = pd.DataFrame()
#取股票池
stockList = list(factorData[date].index)
#获取横截面收益率
df_close = get_price(stockList, date, dateList[dateList.index(date)+1], 'daily', ['close'])
if df_close.empty:
continue
df_pchg=df_close['close'].iloc[-1,:]/df_close['close'].iloc[0,:]-1
R_T['pchg'] = df_pchg
#获取因子数据
factor_data = factorData[date][Field]
R_T['factor'] = factor_data
R_T = R_T.dropna()
X = R_T['factor']
y = R_T['pchg']
# WLS回归
wls = sm.OLS(y, X)
result = wls.fit()
WLS_params[date] = result.params[-1]
WLS_t_test[date] = result.tvalues[-1]
t_test = pd.Series(WLS_t_test).dropna()
print 't值序列绝对值平均值: ',np.sum(np.abs(t_test.values))/len(t_test)
n = [x for x in t_test.values if np.abs(x)>2]
print 't值序列绝对值大于2的占比——判断因子的显著性是否稳定',len(n)/float(len(t_test))
print 't值序列均值的绝对值除以t值序列的标准差: ',np.abs(t_test.mean())/t_test.std()
return WLS_t_test
WLS_t_test = factor_t_test(factorData, 'Neg_pos_ID', begin_date, end_date)
根据上面结果分析,t 值绝对值序列的均值为 3.68,符合大于 2 的特征,且 t 值绝对值序列大于 2 的比例为 66.67%,根据因子收益率显著性检验的标准,该因子为有效因子。
1.2.2 因子 IC 分析¶
因子 k 的 IC 值一般是指个股第 T 期在因子 k 上的暴露度与 T + 1 期的收益率的相关系数。当得到因子 IC 值序列后,根据以下分析方法进行计算:
(1)IC 值序列的均值及绝对值均值: 判断因子有效性;
(2)IC 值序列的标准差:判断因子稳定性;
(3)IC 值系列的均值与标准差比值(IR):分析分析有效性
(4)IC 值序列大于零(或小于零)的占比:判断因子效果的一致性。
import scipy.stats as st
def factor_IC_analysis(factorData, Field, begin_date, end_date, rule='normal'):
dateList = get_period_date('M', begin_date, end_date)
IC = {}
R_T = pd.DataFrame()
for date in dateList[:-1]:
#取股票池
stockList = list(factorData[date].index)
#获取横截面收益率
df_close=get_price(stockList, date, dateList[dateList.index(date)+1], 'daily', ['close'])
if df_close.empty:
continue
df_pchg=df_close['close'].iloc[-1,:]/df_close['close'].iloc[0,:]-1
R_T['pchg']=df_pchg
#获取因子数据
factor_data = factorData[date][Field]
#数据标准化
factor_data = standardlize(factor_data, inf2nan=True, axis=0)
R_T['factor'] = factor_data
R_T = R_T.dropna()
if rule=='normal':
IC[date]=st.pearsonr(R_T.pchg, R_T['factor'])[0]
elif rule=='rank':
IC[date]=st.pearsonr(R_T.pchg.rank(), R_T['factor'].rank())[0]
IC = pd.Series(IC).dropna()
print 'IC 值序列的均值大小',IC.mean()
print 'IC值序列绝对值的均值大小',np.mean(np.abs(IC))
print 'IC 值序列的标准差',IC.std()
print 'IR 比率(IC值序列均值与标准差的比值)',IC.mean()/IC.std()
n = [x for x in IC.values if x>0]
print 'IC 值序列大于零的占比',len(n)/float(len(IC))
factor_IC_analysis(factorData, 'Neg_pos_ID', begin_date, end_date)
上图给出每月因子与次月收益的相关系数 IC,IC 均值为 -0.043,IR 比率达到了 -0.49。在 2010 至 2017 年中,IC 值小于 0 的占比为 64.3%。
总体倾向于为负,表明当月因子值越小,次月收益越高。
1.2.3 分层组合回测分析¶
为了进一步分析因子有效性,本文对该因子进行分层回测分析,策略步骤如下所示:
(1)在每个月最后一个交易日,统计全 A 股因子的值;
(2)根据因子值按照从小到大的顺序排序,并将其等分为 5 组
(3)每个调仓日对每组股票池进行调仓交易,从而获得 5 组股票组合的月均收益
注:设置每次交易手续费为千 2
def GetPchg(i, Field):
pchg = []
cost = 0.002
for date in dateList[:-1]:
tempData = factorData[date].copy()
tempData = tempData.sort(Field)
stockList = list(tempData.index)
stocks = stockList[int(len(stockList)*(i-1)/5):int(len(stockList)*i/5)]
df_close = get_price(stocks, date, dateList[dateList.index(date)+1], 'daily', ['close'])['close']
pchg.append(np.mean(df_close.iloc[-1] / df_close.iloc[0] - 1) - cost)
return pchg
tempPchg = []
for i in range(1,6):
tempPchg.append(np.mean(GetPchg(i, 'Neg_pos_ID')))
# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;
fig = plt.figure(figsize=(20,8))
ax = fig.add_subplot(111)
plt.bar(range(len(tempPchg)), tempPchg, 0.3)
# 添加图例
plt.legend()
plt.show()
由图可以看出,组合 1 至组合 5 符合单调的走势,且组合 1 能够明显跑赢组合 5,由此可以看出该因子具有一定的选股效果。
2 CO 因子¶
根据研报内容,由 Suk,Sonya 和 Sang 在 2014 年发表的文章 《Overreaction and Stock Return Predictability》中提出了持续过度反应度量指标 CO。研报分析由于 A 股和美国股市的投资者结构存在本质区别,因此按照日度形式计算该指标。计算方式如下所示:在每个月最后一个交易日,分析过去一个月的个股的涨跌情况和换手率情况,然后根据以下公式实现 CO 指标的计算。
2.1 因子数据采集¶
本文设置时间区间为 2010 年至 2017 年,样本范围是全市场所有可交易股票去除 ST 股、上市不足 3 月新股。
from jqdata import *
start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20"]
dateList = get_period_date('M',begin_date, end_date)
for date in dateList:
stockList = get_stock_A(date)
df_close = get_price(stockList, count = 21, end_date = date, frequency='1d', fields=['close'])['close']
df_pchg = df_close.pct_change().iloc[1:]
temp_turnover = pd.DataFrame(index = df_pchg.index, columns = stockList)
for day in df_pchg.index:
df_turnover = get_fundamentals(query(valuation.code, valuation.turnover_ratio).filter(valuation.code.in_(stockList)), date = day)
df_turnover = df_turnover.set_index(['code'])
temp_turnover.loc[day] = df_turnover['turnover_ratio']
tempSum = 0
for i in range(len(temp_turnover)):
tempSum += i+1
for i in range(len(temp_turnover)):
temp_turnover.iloc[i] = temp_turnover.iloc[i] * float(i+1) / tempSum
CO = temp_turnover[df_pchg>0].sum() + (-1*temp_turnover[df_pchg<0]).sum()
temp = get_factor_values(stockList, Fields, end_date = date, count = 1)
factorData[date]["CO"] = CO
factorData[date]["market_cap"] = temp["market_cap"].T
factorData[date]["Price1M"] = temp["Price1M"].T
factorData[date]["VOL20"] = temp["VOL20"].T
factorData[date]["sharpe_ratio_20"] = temp["sharpe_ratio_20"].T
for Field in Fields:
factorData[date][Field+"_neu"] = neutralize(factorData[date]["CO"], how=[Field], date=date, axis=0)
factorData[date] = standardlize(factorData[date], inf2nan=True, axis=0)
elapsed = (time.clock() - start)
print("Time used:",elapsed)
2.2 因子有效性检验¶
2.2.1 因子收益率显著性检验¶
WLS_t_test = factor_t_test(factorData, 'CO', begin_date, end_date)
根据上面结果分析,t 值绝对值序列的均值为 3.03,符合大于 2 的特征,且 t 值绝对值序列大于 2 的比例为 54.76%,根据因子收益率显著性检验的标准,该因子为有效因子。
2.2.2 因子 IC 分析¶
factor_IC_analysis(factorData, 'CO', begin_date, end_date)
上图给出每月因子与次月收益的相关系数 IC,IC 均值为 -0.042,IR 比率达到了 -0.48。在 2010 至 2017 年中,IC 值小于 0 的占比为 71.43%。总体倾向于为负,表明当月因子值越小,次月收益越高。
2.2.3 分层组合回测分析¶
为了进一步分析因子有效性,本文对该因子进行分层回测分析,策略步骤如下所示:
(1)在每个月最后一个交易日,统计全 A 股因子的值;
(2)根据因子值按照从小到大的顺序排序,并将其等分为 5 组
(3)每个调仓日对每组股票池进行调仓交易,从而获得 5 组股票组合的月均收益
注:设置每次交易手续费为千 2
tempPchg = []
for i in range(1,6):
tempPchg.append(np.mean(GetPchg(i, 'CO')))
# 通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;
fig = plt.figure(figsize=(20,8))
ax = fig.add_subplot(111)
plt.bar(range(len(tempPchg)), tempPchg, 0.3)
# 添加图例
plt.legend()
plt.show()
从上图可以看出,5 个组合的收益与 CO 指标值呈现出较为明显的负相关性,但在前 2 组之间负单调性并不明显。从组合 1 和组合 5 来看,组合 1 月均收益显然高于组合 5,但是考虑到该因子单调性较差,因此本文对该因子进行更深入的分析。
2.3 因子组合特征¶
本文对该因子进行更深入的分析,在每个月最后一个交易日,分别采集个股的市值、反转、换手率、波动这四个因子,根据原始因子 CO 的值分析该因子与这四个因子的相关性,具体分析如下。
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20"]
def GetCorr(i):
resultCorr = pd.DataFrame()
for date in dateList:
tempData = factorData[date].copy()
tempData.sort()
temp = tempData.iloc[int(len(tempData)*(i-1)/5):int(len(tempData)*i/5),:]
tempCorr = temp.corr()["CO"]
resultCorr[date] = tempCorr[Fields]
return resultCorr
result = pd.DataFrame()
for i in range(1,6):
result[i] = GetCorr(i).mean(axis = 1)
result
由上可以看出,CO 因子与其余因子呈现出一定的相关性,具体如下所示:
(1)CO 因子与市值负相关,且 CO 值较小的组合对应股票市值较大,因此,如果对市值因子进行控制,那么能够实现对 CO 因子的优化;
(2)CO 因子与反转因子正相关,且相关性较高,CO 最小的股票前期跌幅最大,因此,如果对反转因子进行控制,那么能够实现对 CO 因子的优化;
(3)CO 因子与换手率因子正相关,因此,如果对换手率因子进行控制,那么能够实现对 CO 因子的优化;
(4)CO 因子与波动率因子正相关,CO 最小的股票波动率最大,因此,如果对反转因子进行控制,那么能够实现对 CO 因子的优化。
实现了上述分析后,接下来对 CO 因子进行这四种因子的控制后的情况进行分析。
Fields = ["market_cap_neu", "Price1M_neu", "VOL20_neu", "sharpe_ratio_20_neu"]
resultPchg = pd.DataFrame(index = range(1,6), columns = Fields)
for i in range(1,6):
for Field in Fields:
resultPchg.loc[i, Field] = np.mean(GetPchg(i, Field))
resultPchg
上表展示分别对这四种因子进行控制后,进行分层回测的月均收益表,基本上符合随着 CO 增加,组合收益率呈现先增加后减小的态势。但是与原始 CO 因子选股相比,前几个组合的月均收益得到了明显提高。
3 因子深入研究¶
3.1 横截面收益率回归¶
前文内容对因子 Neg_pos_ID 和因子 CO 进行了分析,根据研报介绍,参考因子 Neg_pos_ID 的构建方式,对因子 CO 进行分解为 CO_pos 和 CO_neg。当 CO > 0 时则将该股票的 CO_pos 设为 CO,CO_neg 设为 0;当 CO < 0 时将该股票的 CO_neg 设为 CO,CO_pos 设为 0。
接下来根据研报内容,进行横截面收益率回归分析,具体回归方程如下所示:
其中,Size 表示市值,Pret 表示反转,Turn 表示换手率,Vol 表示波动率,PB 表示价值,YOY_OP 表示营业利润同比增长率。
接下来根据研报分析情况实现横截面收益率回归,并进行分析。
for date in dateList:
stockList = list(factorData[date].index)
df = get_fundamentals(query(valuation.code, valuation.pb_ratio, indicator.inc_operation_profit_year_on_year).filter(valuation.code.in_(stockList)), date=date)
df = df.set_index(['code'])
factorData[date]['pb_ratio'] = df['pb_ratio']
factorData[date]['inc_operation_profit_year_on_year'] = df['inc_operation_profit_year_on_year']
factorData[date]['CO_neg'] = [i if i<0 else 0 for i in factorData[date]['CO']]
factorData[date]['CO_pos'] = [i if i>0 else 0 for i in factorData[date]['CO']]
factorData[date] = standardlize(factorData[date], inf2nan=True, axis=0)
def LRData(factorData, Fields):
result_t = pd.DataFrame()
result_params = pd.DataFrame()
result_r2 = []
for date in dateList[:-1]:
R_T = pd.DataFrame()
#取股票池
stockList = list(factorData[date].index)
#获取横截面收益率
df_close = get_price(stockList, date, dateList[dateList.index(date)+1], 'daily', ['close'])
if df_close.empty:
continue
df_pchg=df_close['close'].iloc[-1,:]/df_close['close'].iloc[0,:]-1
R_T['pchg'] = df_pchg
#获取因子数据
factor_data = factorData[date]
R_T[Fields] = factor_data[Fields]
R_T = R_T.dropna()
X = R_T[Fields]
y = R_T['pchg']
# WLS回归
wls = sm.OLS(y, X)
result = wls.fit()
tvalues = result.tvalues
params = result.params
r2 = result.rsquared_adj
result_t[date] = tvalues
result_params[date] = params
result_r2.append(r2)
result = pd.DataFrame()
result['t 统计量'] = result_t.mean(axis = 1)
result['参数估计'] = result_params.mean(axis = 1)
print "调整 r2 值: ", np.nanmean(result_r2)
return result.T
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year"]
LRData(factorData, Fields)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID"]
LRData(factorData,Fields)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "CO_neg", "CO_pos"]
LRData(factorData,Fields)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID", "CO_neg", "CO_pos"]
LRData(factorData, Fields)
以上四个表格分别对应研报中表格中提到的方程1 - 方程4,从以上表格数据中可以得到以下结论:
(1)包含市值、反转、换手率、波动率、价值、YOY_OP 的 6 因子模型,在 2010-2017 年的调整 R 方为 3.42%,其中,换手率因子(VOL20)的因子收益率最高,回归得到的系数为 -0.0039,检验 t 值为 -1.99;价值因子(PB)与营业利润同比增长率因子(inc_operation_profit_year_on_year)因子收益率最低,在整个样本期间效果不显著。
(2)从第二个表格来看,调整 R 方略微增加,从 3.42% 增加到 4.23%,增加 Neg_pos_ID 因子后,模型预测能力得到提高;从第三个表格来看,与表格一对比,调整 R 方从 3.42% 增加到 3.76%,但是略低于表格二,可见 CO 两个因子选股效果低于 Neg_pos_ID 因子。
(3)从表格 4 来看,其调整 R 方值最大,达到了 4.55%。从 t 统计量和因子收益率来看,因子 Neg_pos_ID 预测能力最好,其次是换手率因子以及反转因子。
3.2 因子其他计算形式¶
参考研报,分别对以下两方面进行分析:
(1)以成交额代替换手率计算 CO 指标;
(2)采用不同时间长度计算因子值。
3.2.1 以成交额代替换手率计算 CO¶
start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
dateList = get_period_date('M',begin_date, end_date)
for date in dateList:
stockList = get_stock_A(date)
df_data = get_price(stockList, count = 21, end_date = date, frequency='1d', fields=['close', 'money'])
df_pchg = df_data["close"].pct_change().iloc[1:]
temp_amount = df_data["money"].iloc[1:]
tempSum = 0
for i in range(len(temp_amount)):
tempSum += i+1
for i in range(len(temp_amount)):
temp_amount.iloc[i] = temp_amount.iloc[i] * float(i+1) / tempSum
CO = temp_amount[df_pchg>0].sum() + (-1*temp_amount[df_pchg<0]).sum()
factorData[date]["CO_amount"] = CO
factorData[date]['CO_amount_neg'] = [i if i<0 else 0 for i in factorData[date]['CO_amount']]
factorData[date]['CO_amount_pos'] = [i if i>0 else 0 for i in factorData[date]['CO_amount']]
factorData[date] = standardlize(factorData[date], inf2nan=True, axis=0)
elapsed = (time.clock() - start)
print("Time used:",elapsed)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID", "CO_amount_neg", "CO_amount_pos"]
LRData(factorData, Fields)
根据上述结果,参考利用换手率计算 CO 因子的结果,采用成交额计算 CO 因子,在以下方面得到了改进:
(1)从调整 R 方来看,采用成交额的方法有了明显增加;
(2)从 CO_neg 和 CO_pos 这两个因子来看,CO 因子收益率得到了提升。
3.2.2 构建期长短对选股效果的影响¶
start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20"]
dateList = get_period_date('2W',begin_date, end_date)
factorData = {}
for date in dateList:
stockList = get_stock_A(date)
df_close = get_price(stockList, count = 21, end_date = date, frequency='1d', fields=['close'])['close']
df_pchg = df_close.pct_change().iloc[1:]
df_data = get_price(stockList, count = 251, end_date = date, frequency='1d', fields=['close'])['close']
df_data = df_data.pct_change().iloc[1:]
temp = df_data[df_data<0]
Neg_Pos_ID = temp.count() - (250 - temp.count())
Neg_Pos_ID = standardlize(Neg_Pos_ID, inf2nan=True, axis=0)
temp_turnover = pd.DataFrame(index = df_pchg.index, columns = stockList)
for day in df_pchg.index:
df_turnover = get_fundamentals(query(valuation.code, valuation.turnover_ratio).filter(valuation.code.in_(stockList)), date = day)
df_turnover = df_turnover.set_index(['code'])
temp_turnover.loc[day] = df_turnover['turnover_ratio']
tempSum = 0
for i in range(len(temp_turnover)):
tempSum += i+1
for i in range(len(temp_turnover)):
temp_turnover.iloc[i] = temp_turnover.iloc[i] * float(i+1) / tempSum
CO = temp_turnover[df_pchg>0].sum() + (-1*temp_turnover[df_pchg<0]).sum()
temp = get_factor_values(stockList, Fields, end_date = date, count = 1)
tempData = pd.DataFrame()
tempData["Neg_pos_ID"] = Neg_Pos_ID
tempData["CO"] = CO
tempData["market_cap"] = temp["market_cap"].T
tempData["Price1M"] = temp["Price1M"].T
tempData["VOL20"] = temp["VOL20"].T
tempData["sharpe_ratio_20"] = temp["sharpe_ratio_20"].T
df = get_fundamentals(query(valuation.code, valuation.pb_ratio, indicator.inc_operation_profit_year_on_year).filter(valuation.code.in_(stockList)), date=date)
df = df.set_index(['code'])
tempData['pb_ratio'] = df['pb_ratio']
tempData['inc_operation_profit_year_on_year'] = df['inc_operation_profit_year_on_year']
tempData['CO_neg'] = [i if i<0 else 0 for i in tempData['CO']]
tempData['CO_pos'] = [i if i>0 else 0 for i in tempData['CO']]
tempData = standardlize(tempData, inf2nan=True, axis=0)
factorData[date] = tempData
elapsed = (time.clock() - start)
print("Time used:",elapsed)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID", "CO_neg", "CO_pos"]
LRData(factorData, Fields)
start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20"]
dateList = get_period_date('Q',begin_date, end_date)
factorData = {}
for date in dateList:
stockList = get_stock_A(date)
df_close = get_price(stockList, count = 21, end_date = date, frequency='1d', fields=['close'])['close']
df_pchg = df_close.pct_change().iloc[1:]
df_data = get_price(stockList, count = 251, end_date = date, frequency='1d', fields=['close'])['close']
df_data = df_data.pct_change().iloc[1:]
temp = df_data[df_data<0]
Neg_Pos_ID = temp.count() - (250 - temp.count())
Neg_Pos_ID = standardlize(Neg_Pos_ID, inf2nan=True, axis=0)
temp_turnover = pd.DataFrame(index = df_pchg.index, columns = stockList)
for day in df_pchg.index:
df_turnover = get_fundamentals(query(valuation.code, valuation.turnover_ratio).filter(valuation.code.in_(stockList)), date = day)
df_turnover = df_turnover.set_index(['code'])
temp_turnover.loc[day] = df_turnover['turnover_ratio']
tempSum = 0
for i in range(len(temp_turnover)):
tempSum += i+1
for i in range(len(temp_turnover)):
temp_turnover.iloc[i] = temp_turnover.iloc[i] * float(i+1) / tempSum
CO = temp_turnover[df_pchg>0].sum() + (-1*temp_turnover[df_pchg<0]).sum()
temp = get_factor_values(stockList, Fields, end_date = date, count = 1)
tempData = pd.DataFrame()
tempData["Neg_pos_ID"] = Neg_Pos_ID
tempData["CO"] = CO
tempData["market_cap"] = temp["market_cap"].T
tempData["Price1M"] = temp["Price1M"].T
tempData["VOL20"] = temp["VOL20"].T
tempData["sharpe_ratio_20"] = temp["sharpe_ratio_20"].T
df = get_fundamentals(query(valuation.code, valuation.pb_ratio, indicator.inc_operation_profit_year_on_year).filter(valuation.code.in_(stockList)), date=date)
df = df.set_index(['code'])
tempData['pb_ratio'] = df['pb_ratio']
tempData['inc_operation_profit_year_on_year'] = df['inc_operation_profit_year_on_year']
tempData['CO_neg'] = [i if i<0 else 0 for i in tempData['CO']]
tempData['CO_pos'] = [i if i>0 else 0 for i in tempData['CO']]
tempData = standardlize(tempData, inf2nan=True, axis=0)
factorData[date] = tempData
elapsed = (time.clock() - start)
print("Time used:",elapsed)
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID", "CO_neg", "CO_pos"]
LRData(factorData, Fields)
上面结果分别实现了以周为周期和以季度为周期的因子分析结果,与月度为周期的分析结果相比,可以得到如下结论:
(1)构建期为 2 周时,调整 R 方值达到了最大,为 4.25%,构建期为季度时,调整 R 方值最小,为 3.42%,可见构建期较短时能够获得更好地预测结果;
(2)构建期较短时,CO 因子的因子收益率更高,且不管构建期多长,换手率因子和反转因子均具有较好的因子预测性。
结论¶
海通金工本篇报告的研究主要测试了两种价量类新因子的选股效果,即 Neg_pos_ID 和 CO。其中前者是下跌天数与上涨天数之差的衡量,后者是上涨时的换手率与下跌时的换手率之差。
本文通过上述分析,得到了以下结论:
(1)Neg_pos_ID 在一定程度上是一种价格动量因子,且通过对该因子进行有效性分析来看,该因子有效性较高,有较好的选股效果;
(2)CO 因子的选股效果并非线性,然后通过对该因子与市值、反转、换手、 波动、价值等四个因子的组合特征来看,CO 因子与市值负相关,
与反转因子正相关,与换手率因子正相关,与波动率因子正相关,然后分别对这四个因子进行控制,与原始 CO 因子选股相比,前几个组合的月均收益得到了明显提高。
(3)进一步对这两个因子进行分析,首先对 6 因子(市值、反转、换手、 波动、价值、营业利润同比增长率)与 Neg_pos_ID 以及 CO 分解得到的 CO_pos 和 CO_pos 因子进行横截面回归,得知新因子的增加会提高模型的预测能力,换言之,新因子包含了有用的额外信息。
(4)当以成交额代替换手率时,CO 因子的选股效果得到提升;当选择不同的构建期进行分析时,构建期较短时,选股效果更佳。