说明:这篇文章来自 2020-2021 年的 WordPress 备份。原始图片附件未随 XML 一起保存,旧服务器图片地址也已失效,因此这里保留正文并移除了失效图片。

价量新因子测试

研究目的:

本文参考海通证券冯佳睿、袁林青撰写的《选股因子系列研究(十八)——价格形态选股因子》,根据研报分析,主要测试了开盘冲高、盘低回升以及均价偏离这三种价格类因子。基于该文章,本文对文章中提到的三种价格类因子进行因子有效性分析,从而实现对股票未来收益的预测,为 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 ,然后根据这两个数据,计算新的因子数据,计算方式如下所示:

Neg_pos_ID=%neg−%pos

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_date = get_price(stockList, count = 251,end_date=date, frequency = '1d',fields = ['close'])['close']
    #pct_change表示当前元素与先前元素的相差百分比,当然指定periods=n,表示当前元素与先前n个元素的相差百分比。默认为1
    df_date = df_date.pct_change().iloc[1:]  #每天相对于前一天的涨跌幅
    temp = df_date[df_date<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)
print(factorData)

('Time used:', 319.861577)

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.68306186203 t值序列绝对值大于2的占比——判断因子的显著性是否稳定 0.666666666667 t值序列均值的绝对值除以t值序列的标准差: 0.379593650011

根据上面结果分析,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)  #inf:infinite无限大
        R_T['factor'] = factor_data
        R_T = R_T.dropna()
        if rule == 'normal':
#             IC[date] = st.pearsonr(R_T.pchg, R_T['factor'])[0]
             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[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.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值序列均值大小 -0.043363427016258914 IC值序列绝对值的均值大小 0.07778510125754556 IC值序列的标准差 0.08850819835610833 IR比率 -0.4899368399951869 IC值序列大于零的比例 0.36904761904761907

上表给出每月因子与次月收益的相关系数 IC,IC均值为-0.043,IR比率为-0.49.在2010到2017年间,IC值小于0的占比为0.63,总体倾向为负,表明当月因子值越小,次月收益越高、

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_values(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')))
fig = plt.figure(figsize=(20,8))
ax = fig.add_subplot(111)    
plt.bar(range(len(tempPchg)), tempPchg, 0.3)
# 添加图例
plt.legend()
plt.show()      

2 CO 因子

根据研报内容,由 Suk,Sonya 和 Sang 在 2014 年发表的文章 《Overreaction and Stock Return Predictability》中提出了持续过度反应度量指标 CO。研报分析由于 A 股和美国股市的投资者结构存在本质区别,因此按照日度形式计算该指标。计算方式如下所示:在每个月最后一个交易日,分析过去一个月的个股的涨跌情况和换手率情况,然后根据以下公式实现 CO 指标的计算。

用上涨时的交易活跃度(换手率、或成交额)与下跌时交易活跃度之差表示

2.1 因子数据采集

本文设置时间区间为 2010 年至 2017 年,样本范围是全市场所有可交易股票去除 ST 股、上市不足 3 月新股。

start = time.clock()
begin_date = '2010-01-01'
end_date = '2017-01-01'
# 市值  最近一个月的股价增长率  20日平均换手率   20日夏普比率(每承受一单位的总风险,会产生多少超额的报酬)
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:]
    #df_pchg.index为日期(date前一个月的开市日)
    temp_turnover = pd.DataFrame(index = df_pchg.index, columns = stockList)
    for day in df_pchg.index:
        #get_fundamentals:查询财务数据   code股票代码 turnover_ratio换手率
        #get_fundamentals返回一个dataframe,columns索引为要查询的字段
        df_turnover = get_fundamentals(query(valuation.code, \
                                        valuation.turnover_ratio).filter\
                                        (valuation.code.in_(stockList)),\
                                        date = day)  
        #设index为code,股票代码
        df_turnover = df_turnover.set_index(['code'])
        #将df_turnover的turnover_ratio(换手率)列复制到temp_turnover的day行
        temp_turnover.loc[day] = df_turnover['turnover_ratio']
    tempSum = 0
    for i in range(len(temp_turnover)):    #len(temp_turnover)为行数(前一个月开市天数)
        tempSum += i+1
    for i in range(len(temp_turnover)):    #乘以Wi(CO公式)
        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() #CO公式
    #获取因子值 返回一个 dict: key 是因子名称, value 是 pandas.dataframe。
    #dataframe 的 index 是日期, column 是股票代码
    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:
        #中性化函数:将序列与 how 中输入的相关变量做回归取残差,去除序列与相关变量的相关性
        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)
print(factorData)

2.2 因子有效性检验

2.2.1 因子收益率显著性检验

#见1.2.1
WLS_t_test = factor_t_test(factorData, 'CO', begin_date, end_date)

输出: t值序列绝对值平均值: 3.1390689096177615 t值序列绝对值大于2的占比——判断因子的显著性是否稳定 0.5952380952380952 t值序列均值的绝对值除以t值序列的标准差: 0.5315264180334107

t值绝对值平均值为3.14, 符合大于2的特征,且t值序列绝对值大于2的占比为59.5%,根据因子收益率显著性检验的标准,该因子为有效因子

2.2.2 因子 IC 分析

#见1.2.2
factor_IC_analysis(factorData, 'CO', begin_date, end_date)

输出: IC值序列均值大小 -0.04150199179395132 IC值序列绝对值的均值大小 0.07374672739993604 IC值序列的标准差 0.08452195586246326 IR比率 -0.4910202487680792 IC值序列大于零的比例 0.27380952380952384

IC值序列均值大小为-0.041,IR比率达到了-0.49.IC值小于零的占比为0.73.总体倾向为负,表明当月因子值越小,次月收益越高。

2.2.3 分层组合回测分析

为了进一步分析因子有效性,本文对该因子进行分层回测分析,策略步骤如下所示: (1)在每个月最后一个交易日,统计全 A 股因子的值; (2)根据因子值按照从小到大的顺序排序,并将其等分为 5 组 (3)每个调仓日对每组股票池进行调仓交易,从而获得 5 组股票组合的月均收益 注:设置每次交易手续费为千 2

tempPchg = []
for i in range(1, 6):
    #GetPchg见1.2.3 返回第i组的平均收益率
    tempPchg.append(np.mean(GetPchg(i, 'CO')))
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指标值呈现出较为明显的负相关性,但在前两组间负单调性并不明显。从组合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_values('CO') #升序
        temp = tempData.iloc[int(len(tempData)*(i-1)/5):int(len(tempData)*(i)/5),:]
        tempCorr = temp.corr()["CO"]
        #corr()返回dataframe,columns为各个因子,index也为这些因子,value为横轴与纵轴因子的相关系数
        resultCorr[date] = tempCorr[Fields]  #co与各因子的相关系数代表相关性
    return resultCorr  #index为除co的因子,columns为date,values为co与各因子的相关系数
# GetCorr(1)
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", "Price1M", "VOL20", "sharpe_ratio_20"]
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。 接下来根据研报内容,进行横截面收益率回归分析,具体回归方程如下所示:

$ r_{i,t} = \alphat + \beta{1,t}Size{i,t} + \beta{2,t}Pret{i,t} + \beta{3,t}Turn{i,t} + \beta{4,t}Vol{i,t} + \beta{5,t}PB{i,t} + \beta{6,t}YOY_OP{i,t} + \epsilon{i,t} $

其中,Size 表示市值,Pret 表示反转,Turn 表示换手率,Vol 表示波动率,PB 表示价值,YOY_OP 表示营业利润同比增长率。 接下来根据研报分析情况实现横截面收益率回归,并进行分析。

for date in dateList:
    stockList = list(factorData[date].index)
    #pb_ratioa为市盈率    indicator.inc_operation_profit_year_on_year营业利润同比增长率
    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 = sm.OLS(y, x)
        result = wls.fit()
        tvalues = result.tvalues
        params = result.params
        r2 = result.rsquared_adj    #校正决定系数用于判定一个多元线性回归方程的拟合程度(0-1)越大越好
        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)

调整 r2 值: 0.03425017596289862

Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", \
          "inc_operation_profit_year_on_year", "Neg_Pos_ID"]
LRData(factorData,Fields)

调整 r2 值: 0.0423381606029065

Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", \
          "inc_operation_profit_year_on_year", "CO_neg", "CO_pos"]
LRData(factorData,Fields)

调整 r2 值: 0.03736065330273979

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)

调整 r2 值: 0.04523055137630058

以上四个表格分别对应研报中表格中提到的方程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)

调整 r2 值: 0.0482490866918

指标 market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year Neg_pos_ID CO_amount_neg CO_amount_pos
t 统计量 -0.293751 -1.142062 -1.946750 0.673835 0.111308 0.168377 -1.400564 1.035387 -1.434746
参数估计 -0.000868 -0.003306 -0.004074 0.002023 -0.000085 0.000148 -0.003454 0.002783 -0.004192

根据上述结果,参考利用换手率计算 CO 因子的结果,采用成交额计算 CO 因子,在以下方面得到了改进: (1)从调整 R 方来看,采用成交额的方法有了明显增加; (2)从 CO_neg 和 CO_pos 这两个因子来看,CO 因子收益率得到了提升。

3.2.2 构建期长短对选股效果的影响

将dataList换成以2W、Q为周期的 求出相应的factorData

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)

以上面的代码求出r2和每个因子对应的t值和参数,得到结论

上面结果分别实现了以周为周期和以季度为周期的因子分析结果,与月度为周期的分析结果相比,可以得到如下结论: (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 因子的选股效果得到提升;当选择不同的构建期进行分析时,构建期较短时,选股效果更佳。