请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  数理科学 帖子:3353071 新帖:31

价量新因子测试

Tango发表于:8 月 14 日 20:00回复(1)

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

研究目的:

根据研报分析,主要测试了两种价量新因子的选股效果,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 因子的选股效果得到提升;当选择不同的构建期进行分析时,构建期较短时,选股效果更佳。

全部回复

0/140

量化课程

    移动端课程