繁簡切換您正在訪問的是FX168財經網,本網站所提供的內容及信息均遵守中華人民共和國香港特別行政區當地法律法規。

FX168财经网>人物频道>帖子

价量新因子测试

作者/adadsfd 2019-08-14 20:00 0 来源: FX168财经网人物频道

本文参考海通证券《价量新因子测试》内容,感谢分析师冯佳睿、罗蕾在研报中提供的思路和方法。以下为我们通过数据及代码进行的分析例证。

研究目的:

根据研报分析,主要测试了两种价量新因子的选股效果,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 $,然后根据这两个数据,计算新的因子数据,计算方式如下所示:

$ 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_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)
('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)
        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 值序列的均值大小 -0.043350446545
IC值序列绝对值的均值大小 0.0778466922662
IC 值序列的标准差 0.0885433566301
IR 比率(IC值序列均值与标准差的比值) -0.489595698592
IC 值序列大于零的占比 0.357142857143

上图给出每月因子与次月收益的相关系数 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 指标的计算。

$CO_t = \sum_{j=1}^{D_t}w_j*SV_{t-j} $
$SV = \begin{cases} turn_t, & \mbox{if } r_t > 0 \\ 0, & \mbox{if } r_t = 0 \\ -turn_t, & \mbox{if } r_t < 0="" \end{cases}="" $="">
其中,每日 SV 的加权权重为指数移动平均,离当前时点越近,权重越高,即
$w_1 = \frac{D_t}{1+2+...+D_t}, ... , w_{D_t} = \frac{1}{1+2+...+D_t} $

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)
('Time used:', 615.507732)

2.2 因子有效性检验¶

2.2.1 因子收益率显著性检验¶

WLS_t_test = factor_t_test(factorData, 'CO', begin_date, end_date)
t值序列绝对值平均值:  3.03123088187
t值序列绝对值大于2的占比——判断因子的显著性是否稳定 0.547619047619
t值序列均值的绝对值除以t值序列的标准差:  0.50372937926

根据上面结果分析,t 值绝对值序列的均值为 3.03,符合大于 2 的特征,且 t 值绝对值序列大于 2 的比例为 54.76%,根据因子收益率显著性检验的标准,该因子为有效因子。

2.2.2 因子 IC 分析¶

factor_IC_analysis(factorData, 'CO', begin_date, end_date)
IC 值序列的均值大小 -0.0419709331665
IC值序列绝对值的均值大小 0.0751874827247
IC 值序列的标准差 0.0880281255306
IR 比率(IC值序列均值与标准差的比值) -0.476790036292
IC 值序列大于零的占比 0.285714285714

上图给出每月因子与次月收益的相关系数 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
1 2 3 4 5
market_cap -0.045627 -0.045205 -0.045500 -0.051767 -0.030496
Price1M 0.634905 0.586709 0.620945 0.608039 0.621211
VOL20 0.305054 0.334768 0.347552 0.343193 0.335396
sharpe_ratio_20 0.382016 0.301380 0.300753 0.344787 0.339058

由上可以看出,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
market_cap_neu Price1M_neu VOL20_neu sharpe_ratio_20_neu
1 0.01118752 0.008848652 0.01152886 0.01148184
2 0.01528558 0.01300151 0.01322405 0.01481027
3 0.01385451 0.01462124 0.01438949 0.01384884
4 0.0122903 0.01335404 0.0115991 0.0121474
5 0.009497096 0.01228783 0.01137642 0.009826408

上表展示分别对这四种因子进行控制后,进行分层回测的月均收益表,基本上符合随着 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} = \alpha_t + \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)
    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)
调整 r2 值:  0.0342236689759
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year
t 统计量 -0.670897 -1.458196 -1.985521 0.618002 0.117408 0.180471
参数估计 -0.001925 -0.003690 -0.003936 0.002143 -0.000403 0.000256
Fields = ["market_cap", "Price1M", "VOL20", "sharpe_ratio_20", "pb_ratio", "inc_operation_profit_year_on_year", "Neg_pos_ID"]
LRData(factorData,Fields)
调整 r2 值:  0.0423218469701
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year Neg_pos_ID
t 统计量 -0.647590 -1.60623 -2.323896 0.575091 0.094821 0.176276 -1.566019
参数估计 -0.001861 -0.00426 -0.005027 0.001808 -0.000254 0.000214 -0.003945
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.0376717067262
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year CO_neg CO_pos
t 统计量 -0.630128 -1.197882 -1.170621 0.678315 0.127619 0.173568 0.690222 -0.633429
参数估计 -0.001828 -0.003238 -0.002385 0.002442 -0.000472 0.000248 0.001564 -0.002804
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.0454910410259
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year Neg_pos_ID CO_neg CO_pos
t 统计量 -0.613414 -1.297390 -1.481418 0.632669 0.105028 0.169123 -1.504933 0.629689 -0.609231
参数估计 -0.001779 -0.003758 -0.003484 0.002099 -0.000320 0.000204 -0.003780 0.001461 -0.002694

以上四个表格分别对应研报中表格中提到的方程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)
('Time used:', 553.1901480000001)
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 构建期长短对选股效果的影响¶

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)
('Time used:', 2377.565786000001)
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.0425473267997
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year Neg_pos_ID CO_neg CO_pos
t 统计量 -0.546350 -1.762363 -0.843000 0.598260 0.147674 0.135119 -0.911740 0.854600 -0.220412
参数估计 -0.000918 -0.004028 -0.001848 0.001197 0.000496 0.000169 -0.001653 0.001363 -0.000716
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)
('Time used:', 278.6685179999995)
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.0342384467265
market_cap Price1M VOL20 sharpe_ratio_20 pb_ratio inc_operation_profit_year_on_year Neg_pos_ID CO_neg CO_pos
t 统计量 -0.775198 -0.788211 -1.946423 0.268222 -0.086756 0.018334 -1.900873 0.302416 -0.521499
参数估计 -0.005194 -0.005446 -0.007839 0.001528 0.000223 -0.000057 -0.005896 0.000732 -0.003314

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

分享到:
举报财经168客户端下载

全部回复

0/140

投稿 您想发表你的观点和看法?

更多人气分析师

  • 张亦巧

    人气2192文章4145粉丝45

    暂无个人简介信息

  • 王启蒙现货黄金

    人气296文章3215粉丝8

    本人做分析师以来,并专注于贵金属投资市场,尤其是在现货黄金...

  • 指导老师

    人气1864文章4423粉丝52

    暂无个人简介信息

  • 李冉晴

    人气2320文章3821粉丝34

    李冉晴,专业现贷实盘分析师。

  • 梁孟梵

    人气2176文章3177粉丝39

    qq:2294906466 了解群指导添加微信mfmacd

  • 张迎妤

    人气1896文章3305粉丝34

    个人专注于行情技术分析,消息面解读剖析,给予您第一时间方向...

  • 金泰铬J

    人气2328文章3925粉丝51

    投资问答解咨询金泰铬V/信tgtg67即可获取每日的实时资讯、行情...

  • 金算盘

    人气2696文章7761粉丝125

    高级分析师,混过名校,厮杀于股市和期货、证券市场多年,专注...

  • 金帝财神

    人气4760文章8329粉丝119

    本文由资深分析师金帝财神微信:934295330,指导黄金,白银,...

FX168财经

FX168财经学院

FX168财经

FX168北美