【转】选股因子系列:价格形态选股因子

0
回复
4534
查看
[复制链接]

16

主题

21

回帖

146

积分

注册会员

积分
146
来源: 2019-8-22 05:36:12 显示全部楼层 |阅读模式
本帖最后由 宽聚量化 于 2019-8-22 05:40 编辑

本文参考海通证券《选股因子系列研究(十八)-价格形态选股因子》内容,感谢分析师冯佳睿、袁林青在研报中提供的思路和方法,以下为我们通过数据及代码进行的分析例证。
研究目的:
本文参考海通证券冯佳睿、袁林青撰写的《选股因子系列研究(十八)——价格形态选股因子》,根据研报分析,主要测试了开盘冲高、盘低回升以及均价偏离这三种价格类因子。基于该文章,本文对文章中提到的三种价格类因子进行因子有效性分析,从而实现对股票未来收益的预测,为 alpha 因子的挖掘提供了一定思路。
研究内容:
(1)构建分别以月和半月为观察期的三种价格类因子;
(2)对构建出来的因子进行因子有效性分析,分别包括因子 IC/IR 分析,分层回测组合分析等,并根据结果分析因子的预测能力以及不同观察期对预测能力的影响;
(3)对每个因子,加入行业、市值、贝塔、动量、残差波动率以及非线性市值这 6 个因子进行正交化处理,分析正交化后因子对组合收益的贡献及其预测效果;
(4)对因子进行多因子模型回测分析,分析不同因子对组合收益带来的贡献等。
研究结论:
(1)通过对这三种价格类因子的因子有效性分析结果来看,三种因子均有较好的选股能力,针对不同的观察期,月度因子比半月度因子具有更强的选股能力,但是相对而言,这三种因子的分层回测单调性不足;
(2)通过将这三种因子与行业、市值、贝塔、动量、残差波动率以及非线性市值这 6 个因子进行正交化处理,分析结果可知正交化因子预测稳定性得到较大提升,因子的分层回测单调性得到增强;
(3)对多因子模型进行回测分析,分别从横截面收益率回归和纯多头组合这两方面进行分析,根据分析结果来看,在加入因子 HighOpen 或者因子 VwapClose 后,模型收益能力和风险控制能力相比原始模型均得到了较大提升。
1 因子构建


通过对 K 线的研究,不难发现,常见价格信息,如高、开、低、收等,也能够很好地实现对股票收益的刻画,且对于大多数散户而言,K 线是其主要研究对象,从中提取特征信息,如 K 线的上影线、下影线等等。
参考研报内容,考虑引入开盘、盘高、盘低以及均价构建相关指标刻画股票日内的形态特征。共引入三个指标:开盘冲高、盘低回升以及均价偏离。具体指标计算方式如下所示:
(1)开盘冲高:log(盘高/开盘价)。该指标越大,表示股票盘中冲高幅度越大。
(2)盘低回升:log(收盘价/盘低)。该指标越大,表示股票从盘低回升的幅度也就越大。
(3)均价偏离:log(均价/收盘价)。该指标体现了股票成交均价相对于收盘价的偏离。
紧接着,设计因子实现上述指标,在每个月或者每半个月的时间窗口下,计算上述三个因子过去 1 个月或者半个月的均值,具体计算公式如下所示:

yb19082212.png

1.1 日期列表获取在每个月的月末对因子数据进行提取,因此需要对每个月的月末日期进行统计。
输入参数分别为 peroid、start_date 和 end_date,其中 peroid 进行周期选择,可选周期为周(W)、月(M)和季(Q),start_date和end_date 分别为开始日期和结束日期。
函数返回值为对应的日期。本文选取开始日期为 2013.1.1,结束日期为 2018.1.1。周期如月取 “W”,半月取 “2W”。
  1. from jqdata import *
  2. import datetime
  3. import pandas as pd
  4. import numpy as np
  5. from six import StringIO
  6. import warnings
  7. import time
  8. import pickle
  9. import scipy.stats as st
  10. from jqfactor import winsorize_med
  11. from jqfactor import neutralize
  12. from jqfactor import standardlize
  13. import statsmodels.api as sm
  14. import seaborn as sns
  15. warnings.filterwarnings("ignore")
复制代码

  1. #获取指定周期的日期列表 'W、M、Q'
  2. def get_period_date(peroid,start_date, end_date):
  3.     #设定转换周期period_type  转换为周是'W',月'M',季度线'Q',五分钟'5min',12天'12D'
  4.     stock_data = get_price('000001.XSHE',start_date,end_date,'daily',fields=['close'])
  5.     #记录每个周期中最后一个交易日
  6.     stock_data['date']=stock_data.index
  7.     #进行转换,周线的每个变量都等于那一周中最后一个交易日的变量值
  8.     period_stock_data=stock_data.resample(peroid,how='last')
  9.     date=period_stock_data.index
  10.     pydate_array = date.to_pydatetime()
  11.     date_only_array = np.vectorize(lambda s: s.strftime('%Y-%m-%d'))(pydate_array )
  12.     date_only_series = pd.Series(date_only_array)
  13.     start_date = datetime.datetime.strptime(start_date, "%Y-%m-%d")
  14.     start_date=start_date-datetime.timedelta(days=1)
  15.     start_date = start_date.strftime("%Y-%m-%d")
  16.     date_list=date_only_series.values.tolist()
  17.     date_list.insert(0,start_date)
  18.     return date_list
复制代码



1.2 股票列表获取


股票池: 全 A 股
股票筛选:剔除 ST 股票,剔除上市 3 个月内的股票,每只股票视作一个样本

  1. #去除上市距beginDate不足 3 个月的股票
  2. def delect_stop(stocks,beginDate,n=30*3):
  3.     stockList = []
  4.     beginDate = datetime.datetime.strptime(beginDate, "%Y-%m-%d")
  5.     for stock in stocks:
  6.         start_date = get_security_info(stock).start_date
  7.         if start_date < (beginDate-datetime.timedelta(days = n)).date():
  8.             stockList.append(stock)
  9.     return stockList

  10. #获取股票池
  11. def get_stock_A(begin_date):
  12.     begin_date = str(begin_date)
  13.     stockList = get_index_stocks('000002.XSHG',begin_date)+get_index_stocks('399107.XSHE',begin_date)
  14.     #剔除ST股
  15.     st_data = get_extras('is_st', stockList, count = 1, end_date=begin_date)
  16.     stockList = [stock for stock in stockList if not st_data[stock][0]]
  17.     #剔除停牌、新股及退市股票
  18.     stockList = delect_stop(stockList, begin_date)
  19.     return stockList
复制代码

1.3 数据获取


参考上面实现因子构建的公式,在每个月以及每半个月最后一天,分别根据对应的时间窗口,对因子进行计算,并将计算得到的因子数据分别保存在变量 factorData_M 以及 factorData_2W 中。





  1. start = time.clock()
  2. # 时间窗口
  3. N = 20
  4. begin_date = '2013-01-01'
  5. end_date = '2018-01-01'
  6. # 获取月度日期列表
  7. dateList = get_period_date('M',begin_date, end_date)
  8. factorData_M = {}
  9. for date in dateList:
  10.     stockList = get_stock_A(date)
  11.     # 获取价格类数据
  12.     df_data = get_price(stockList, count = N, end_date = date, frequency='1d', fields=['high', 'open', 'low', 'close', 'avg'])
  13.     # 因子计算
  14.     temp1 = log(df_data["high"] / df_data["open"]).mean()
  15.     temp2 = log(df_data["close"] / df_data["low"]).mean()
  16.     temp3 = log(df_data["avg"] / df_data["close"]).mean()
  17.     tempData = pd.DataFrame()
  18.     tempData["HighOpen"] = temp1
  19.     tempData["CloseLow"] = temp2
  20.     tempData["VwapClose"] = temp3
  21.     tempData = standardlize(tempData, axis=0)
  22.     factorData_M[date] = tempData
  23. elapsed = (time.clock() - start)
  24. print("Time used:",elapsed)
复制代码
('Time used:', 105.55157799999999)

  1. start = time.clock()
  2. N = 10
  3. begin_date = '2013-01-01'
  4. end_date = '2018-01-01'
  5. # 获取半月日期列表
  6. dateList = get_period_date('2W',begin_date, end_date)
  7. factorData_2W = {}
  8. for date in dateList:
  9.     stockList = get_stock_A(date)
  10.     # 获取价格类数据
  11.     df_data = get_price(stockList, count = N, end_date = date, frequency='1d', fields=['high', 'open', 'low', 'close', 'avg'])
  12.     # 因子计算
  13.     temp1 = log(df_data["high"] / df_data["open"]).mean()
  14.     temp2 = log(df_data["close"] / df_data["low"]).mean()
  15.     temp3 = log(df_data["avg"] / df_data["close"]).mean()
  16.     tempData = pd.DataFrame()
  17.     tempData["HighOpen"] = temp1
  18.     tempData["CloseLow"] = temp2
  19.     tempData["VwapClose"] = temp3
  20.     tempData = standardlize(tempData, axis=0)
  21.     factorData_2W[date] = tempData
  22. elapsed = (time.clock() - start)
  23. print("Time used:",elapsed)
复制代码






('Time used:', 216.62505500000003)




2 因子有效性分析


2.1 因子 IC 分析


因子 k 的 IC 值一般是指个股第 T 期在因子 k 上的暴露度与 T + 1 期的收益率的相关系数。当得到因子 IC 值序列后,根据以下分析方法进行计算:
(1)IC 值序列的均值: 判断因子有效性;
(2)IC 值系列的均值与标准差比值(IR):分析分析有效性
常见 IC 分析方式分为以下两种:normal IC 以及 rank IC,接下来从这两个角度,对不同因子的 IC 以及 IR 进行分析。

  1. def factor_IC_analysis(factorData, Field, begin_date, end_date, period):  
  2.     dateList = get_period_date(period,begin_date, end_date)
  3.     IC_norm = {}
  4.     IC_rank = {}
  5.     R_T = pd.DataFrame()
  6.     for date in dateList[:-1]:
  7.         #取股票池
  8.         stockList = list(factorData[date].index)
  9.         #获取横截面收益率
  10.         df_close=get_price(stockList, date, dateList[dateList.index(date)+1], 'daily', ['close'])
  11.         if df_close.empty:
  12.             continue
  13.         df_pchg=df_close['close'].iloc[-1,:]/df_close['close'].iloc[0,:]-1
  14.         R_T['pchg']=df_pchg
  15.         #获取因子数据
  16.         factor_data = factorData[date][Field]
  17.         #数据标准化
  18.         factor_data = standardlize(factor_data, inf2nan=True, axis=0)
  19.         R_T['factor'] = factor_data
  20.         R_T = R_T.dropna()
  21.         IC_norm[date]=st.pearsonr(R_T.pchg, R_T['factor'])[0]
  22.         IC_rank[date]=st.pearsonr(R_T.pchg.rank(), R_T['factor'].rank())[0]
  23.     IC_norm = pd.Series(IC_norm).dropna()
  24.     IC_rank = pd.Series(IC_rank).dropna()
  25.     return (IC_norm, IC_rank)
  26. # 因子 IC 分析
  27. IndexList = ["HighOpen_2W", "CloseLow_2W", "VwapClose_2W", "HighOpen_M", "CloseLow_M", "VwapClose_M"]
  28. ColumnsList = ["IC", "IR", "RankIC", "RankIR"]
  29. IC = pd.DataFrame(index = IndexList, columns = ColumnsList)
  30. for i in ["2W", "M"]:
  31.     for j in ["HighOpen", "CloseLow", "VwapClose"]:
  32.         if i == "2W":
  33.             tempIC = factor_IC_analysis(factorData_2W, j, begin_date, end_date, i)
  34.         if i == "M":
  35.             tempIC = factor_IC_analysis(factorData_M, j, begin_date, end_date, i)
  36.         # 分析结果保存
  37.         IC.loc[j + "_" + i, "IC"] = tempIC[0].mean()
  38.         IC.loc[j + "_" + i, "IR"] = tempIC[0].mean() / tempIC[0].std()
  39.         IC.loc[j + "_" + i, "RankIC"] = tempIC[1].mean()
  40.         IC.loc[j + "_" + i, "RankIR"] = tempIC[1].mean() / tempIC[1].std()
  41. IC = IC.astype(float)
  42. fig = plt.figure(figsize=(15,6))
  43. ax = fig.add_subplot(111)
  44. sns.heatmap(IC, annot=True, vmax=1, vmin = 0)
  45. ax.set_title("IC / IR", fontsize=21)
  46. fig.show()
复制代码










yb19082201.png
上表给出的结果为以月为周期的三个因子以及以半月为周期的三个因子的 IC、IR 以及 Rank IC、Rank IR 的值,根据表中结果可知,各因子都有较强的选股能力,具体可得到的结论如下所示:
(1)因子 HighOpen 与 VwapClose 均为正向因子,而因子 CloseLow 与他们的方向相反,为反向因子。
(2)对于因子 HighOpen 与 VwapClose 而言,以月为周期的预测能力明显更强于以半月为周期;对于因子 CloseLow 而言,以半月为周期的预测能力更强于以月为周期。
(3)以半月为周期时,三个因子的 normal IC 绝对值均能够达到 0.03,以月为周期时,三个因子的 normal IC 绝对值能够达到 0.4 以上。此外。从 IR 来看,相对而言以月为周期有着更为明显的提升。



2.2 分层组合回测分析


为了进一步分析因子有效性,本文对该因子进行分层回测分析,策略步骤如下所示:
(1)在每个月或者每半个月最后一个交易日,统计全 A 股因子的值;
(2)根据因子值按照从小到大的顺序排序,并将其等分为 10 组;
(3)每个调仓日对每组股票池进行调仓交易,从而获得 10 组股票组合的月均收益。
首先,对以月为调仓期的情况进行分析。

  1. def GetPchg(i, factorData, Field, begin_date, end_date, period):
  2.     dateList = get_period_date(period,begin_date, end_date)
  3.     pchg = []
  4.     # 交易手续费
  5.     cost = 0.002
  6.     for date in dateList[:-1]:
  7.         tempData = factorData[date].copy()
  8.         # 因子排序
  9.         tempData = tempData.sort(Field)
  10.         stockList = list(tempData.index)
  11.         # 获取交易股票池
  12.         stocks = stockList[int(len(stockList)*(i-1)/10):int(len(stockList)*i/10)]
  13.         df_close = get_price(stocks, date, dateList[dateList.index(date)+1], 'daily', ['close'])['close']
  14.         pchg.append(np.mean(df_close.iloc[-1] / df_close.iloc[0] - 1) - cost)
  15.     return pchg
  16. # 分层组合回测
  17. pchg_M = {}
  18. fig = plt.figure(figsize=(15,6))
  19. bar_width = 0.3
  20. x = np.array(range(1,11))
  21. for j in ["HighOpen", "CloseLow", "VwapClose"]:
  22.     tempPchg = []
  23.     for k in range(1,11):
  24.         pchg_M[j+str(k)] = GetPchg(k, factorData_M, j, begin_date, end_date, "M")
  25.         tempPchg.append(np.mean(pchg_M[j+str(k)]))
  26.     plt.bar(x, tempPchg, bar_width, label = j)
  27.     x = x + bar_width
  28. plt.xticks(range(1, len(tempPchg)+1))
  29. # 添加图例
  30. plt.legend()
  31. plt.show()
复制代码




yb19082202.png
上图展示的是以月为周期时,各个组合的月均收益情况,从分组收益可以看出,因子 HighOpen 以及 CloseOpen除了组合 1 外,其他组合基本符合两边组别收益低,中间组别收益高的非线性趋势,因子 VwapClose 基本符合这样的情况。
接下来,对以半月为调仓期的情况进行具体分析。
  1. pchg_2W = {}
  2. fig = plt.figure(figsize=(15,6))
  3. bar_width = 0.3
  4. x = np.array(range(1,11))
  5. for j in ["HighOpen", "CloseLow", "VwapClose"]:
  6.     tempPchg = []
  7.     for k in range(1,11):
  8.         pchg_2W[j+str(k)] = GetPchg(k, factorData_2W, j, begin_date, end_date, "2W")
  9.         tempPchg.append(np.mean(pchg_2W[j+str(k)]))
  10.     plt.bar(x, tempPchg, bar_width, label = j)
  11.     x = x + bar_width
  12. plt.xticks(range(1, len(tempPchg)+1))
  13. # 添加图例
  14. plt.legend()
  15. plt.show()
复制代码
yb19082203.png
上图展示的以半月为调仓期的组合情况,从图中可知,基本上走势和以月为调仓期的情况相似,但是,以半月为调仓期的组合比以月为调仓期的组合月均收益更低,该结论与 IC 分析的结果一致。
接下来,对于每个组合的情况,通过下表的具体月均收益进行提现。
  1. # 分层组合回测的具体数据以及多空组合
  2. IndexList = range(1,11) + ["longshort"]
  3. ColumnsList = ["HighOpen_2W", "CloseLow_2W", "VwapClose_2W", "HighOpen_M", "CloseLow_M", "VwapClose_M"]
  4. result = pd.DataFrame(index = IndexList, columns = ColumnsList)
  5. for i in ["2W", "M"]:
  6.     for j in ["HighOpen", "CloseLow", "VwapClose"]:
  7.         for k in range(1,11):
  8.             if i == "2W":
  9.                 temp = np.mean(pchg_2W[j+str(k)])
  10.                 result.loc[k, j+"_"+i] = temp
  11.                 # 获取多空组合数据
  12.                 result.loc["longshort", j+"_"+i] = np.mean(pchg_2W[j+str(1)]) + np.mean(pchg_2W[j+str(10)])
  13.             if i == "M":
  14.                 temp = np.mean(pchg_M[j+str(k)])
  15.                 result.loc[k, j+"_"+i] = temp
  16.                 # 获取多空组合数据
  17.                 result.loc["longshort", j+"_"+i] = np.mean(pchg_M[j+str(1)]) + np.mean(pchg_M[j+str(10)])
  18. result = result.astype(float)
  19. fig = plt.figure(figsize=(15,6))
  20. ax = fig.add_subplot(111)
  21. sns.heatmap(result, annot=True, vmax=1, vmin = 0)
  22. fig.show()
复制代码
yb19082204.png
上表展示了不同组合的月均收益以及对应的多空组合,从多空组合收益来看,三个因子均在以月为调仓期的情况下能够获得更高的收益,且因子 HighOpen > CloseLow > VwapClose。
接下来,对不同因子不同构建期的组合,选择前 10% 股票构建的组合每年收益以及胜率(是否获得正收益)进行分析。
  1. IndexList = ["2013", "2014", "2015", "2016", "2017"]
  2. result = pd.DataFrame(index = IndexList)
  3. for period in ["2W", "M"]:
  4.     for factor in ["HighOpen", "CloseLow", "VwapClose"]:
  5.         rate = []
  6.         ret = []
  7.         for k in range(len(IndexList)):
  8.             if period == "2W":
  9.                 temp = pchg_2W[factor+str(1)][k*len(pchg_2W[factor+str(1)])/len(IndexList):(k+1)*len(pchg_2W[factor+str(1)])/len(IndexList)]
  10.                 rate.append(len([i for i in temp if i>0]) / float(len(temp)))
  11.                 tempSum = 1
  12.                 for a in temp:
  13.                     tempSum *= 1+a
  14.                 ret.append(tempSum - 1)
  15.             if period == "M":
  16.                 temp = pchg_M[factor+str(1)][k*len(pchg_M[factor+str(1)])/len(IndexList):(k+1)*len(pchg_M[factor+str(1)])/len(IndexList)]
  17.                 rate.append(len([i for i in temp if i>0]) / float(len(temp)))
  18.                 tempSum = 1
  19.                 for a in temp:
  20.                     tempSum *= 1+a
  21.                 ret.append(tempSum - 1)
  22.         result["TotalRet_" + factor + "_" + period] = ret
  23.         result["WinRate_" + factor + "_" + period] = rate

  24. result = result.astype(float)
  25. fig = plt.figure(figsize=(15,6))
  26. ax = fig.add_subplot(111)
  27. sns.heatmap(result, annot=True, vmax=1, vmin = 0)
  28. fig.show()
复制代码
yb19082205.png

上表展示了不同组合的每年收益以及对应的胜率,从表中可以看出:
(1)每个组合均在除 2017 年以外的每一年获得正收益且胜率基本上大于 60%;
(2)以月为调仓期的组合能够获得很明显更高的收益。



3 因子正交化处理


第 2 章对因子的有效性进行了具体分析,但是考虑到分层回测的单调性不好,因此引用行业、市值、贝塔、动量、残差波动率以及非线性市值 6 个因子,进行正交处理并构建对应的正交因子,然后对正交因子进行因子有效性分析。



3.1 数据获取


  1. start = time.clock()
  2. begin_date = '2013-01-01'
  3. end_date = '2018-01-01'
  4. dateList = get_period_date('M',begin_date, end_date)
  5. for date in dateList:
  6.     tempData = factorData_M[date]
  7.     # 对原始因子进行中性化处理
  8.     tempData = neutralize(tempData, how=["sw_l1", "size", "beta", "momentum", "residual_volatility", "non_linear_size"], date=date, axis=0, fillna=None, add_constant=False)
  9.     factorData_M[date] = tempData
  10. elapsed = (time.clock() - start)
  11. print("Time used:",elapsed)
复制代码

('Time used:', 95.68705699999987)
  1. start = time.clock()
  2. begin_date = '2013-01-01'
  3. end_date = '2018-01-01'
  4. dateList = get_period_date('2W',begin_date, end_date)
  5. for date in dateList:
  6.     tempData = factorData_2W[date]
  7.     # 对原始因子进行中性化处理
  8.     tempData = neutralize(tempData, how=["sw_l1", "size", "beta", "momentum", "residual_volatility", "non_linear_size"], date=date, axis=0, fillna=None, add_constant=False)
  9.     factorData_2W[date] = tempData
  10. elapsed = (time.clock() - start)
  11. print("Time used:",elapsed)
复制代码


('Time used:', 211.58337600000004)





3.2 因子有效性分析


3.2.1 因子 IC 分析


首先,对正交化后的各个因子进行 IC 分析,分析内容和前面类似。



  1. IndexList = ["HighOpen_2W", "CloseLow_2W", "VwapClose_2W", "HighOpen_M", "CloseLow_M", "VwapClose_M"]
  2. ColumnsList = ["IC", "IR", "RankIC", "RankIR"]
  3. IC = pd.DataFrame(index = IndexList, columns = ColumnsList)
  4. for i in ["2W", "M"]:
  5.     for j in ["HighOpen", "CloseLow", "VwapClose"]:
  6.         if i == "2W":
  7.             # 获取 IC 分析结果
  8.             tempIC = factor_IC_analysis(factorData_2W, j, begin_date, end_date, i)
  9.         if i == "M":
  10.             tempIC = factor_IC_analysis(factorData_M, j, begin_date, end_date, i)
  11.         # 结果保存
  12.         IC.loc[j + "_" + i, "IC"] = tempIC[0].mean()
  13.         IC.loc[j + "_" + i, "IR"] = tempIC[0].mean() / tempIC[0].std()
  14.         IC.loc[j + "_" + i, "RankIC"] = tempIC[1].mean()
  15.         IC.loc[j + "_" + i, "RankIR"] = tempIC[1].mean() / tempIC[1].std()
  16. # 结果展示
  17. IC = IC.astype(float)
  18. fig = plt.figure(figsize=(15,6))
  19. ax = fig.add_subplot(111)
  20. sns.heatmap(IC, annot=True, vmax=1, vmin = 0)
  21. ax.set_title("IC / IR", fontsize=21)
  22. fig.show()
复制代码


yb19082207.png

上表展示了正交化后的各个因子 IC 值,从表中可以看出,正交化后因子与不进行正交化之前,IC 值变化不明显,但是 IR 得到了明显提升,可见正交化操作能够提高因子的稳定性。



3.2.2 分层组合回测分析
  1. pchg_M = {}
  2. fig = plt.figure(figsize=(15,6))
  3. bar_width = 0.3
  4. x = np.array(range(1,11))
  5. for j in ["HighOpen", "CloseLow", "VwapClose"]:
  6.     tempPchg = []
  7.     # 获取分层回测结果
  8.     for k in range(1,11):
  9.         pchg_M[j+str(k)] = GetPchg(k, factorData_M, j, begin_date, end_date, "M")
  10.         tempPchg.append(np.mean(pchg_M[j+str(k)]))
  11.     plt.bar(x, tempPchg, bar_width, label = j)
  12.     x = x + bar_width
  13. plt.xticks(range(1, len(tempPchg)+1))
  14. # 添加图例
  15. plt.legend()
  16. plt.show()
复制代码
yb19082208.png
  1. pchg_2W = {}
  2. fig = plt.figure(figsize=(15,6))
  3. bar_width = 0.3
  4. x = np.array(range(1,11))
  5. for j in ["HighOpen", "CloseLow", "VwapClose"]:
  6.     tempPchg = []
  7.     # 获取分层回测结果
  8.     for k in range(1,11):
  9.         pchg_2W[j+str(k)] = GetPchg(k, factorData_2W, j, begin_date, end_date, "2W")
  10.         tempPchg.append(np.mean(pchg_2W[j+str(k)]))
  11.     plt.bar(x, tempPchg, bar_width, label = j)
  12.     x = x + bar_width
  13. plt.xticks(range(1, len(tempPchg)+1))
  14. # 添加图例
  15. plt.legend()
  16. plt.show()
复制代码
yb19082209.png

上面两张图展示的分别是以月为调仓期和以半月为调仓期的结果,从图中可以看出,正交化后因子,分层单调性有了很明显的提升,由次可以看出正交化结果能够使得因子有效性得到进一步加强。



4 多因子模型回测


4.1 横截面收益率回归


接下来,从回归法的角度讨论因子在加入到模型后的额外选股能力具体回归方程如下所示:
yb19082213.png

其中,size 表示市值,beta 表示股票相对于市场的波动敏感度,momentum 表示过去两年里相对强势的股票与弱势股票之间的差异,residual_volatility 表示剥离了市场风险后的波动率高低产生的收益率差异,non_linear_size 表示无法由规模因子解释的但与规模有关的收益差异


  1. [font=Segoe UI][size=3][color=#001000]from jqfactor import get_factor_values

  2. start = time.clock()
  3. begin_date = '2013-01-01'
  4. end_date = '2018-01-01'
  5. dateList = get_period_date('M',begin_date, end_date)
  6. for date in dateList:
  7.     tempData = factorData_M[date]
  8.     stockList = list(tempData.index)
  9.     # 获取 5 因子数据并保存
  10.     temp = get_factor_values(stockList, ["size", "beta", "momentum", "residual_volatility", "non_linear_size"], end_date = date, count = 1)
  11.     tempData["size"] = temp["size"].iloc[0]
  12.     tempData["beta"] = temp["beta"].iloc[0]
  13.     tempData["momentum"] = temp["momentum"].iloc[0]
  14.     tempData["residual_volatility"] = temp["residual_volatility"].iloc[0]
  15.     tempData["non_linear_size"] = temp["non_linear_size"].iloc[0]
  16.     tempData = standardlize(tempData, axis=0)
  17.     factorData_M[date] = tempData
  18. elapsed = (time.clock() - start)
  19. print("Time used:",elapsed)[/color][/size][/font]
复制代码
('Time used:', 30.592061999999714)
  1. def LRData(factorData, Fields):
  2.     result_t = pd.DataFrame()
  3.     result_params = pd.DataFrame()
  4.     result_r2 = []
  5.     for date in dateList[:-1]:
  6.         R_T = pd.DataFrame()
  7.         #取股票池
  8.         stockList = list(factorData[date].index)
  9.         #获取横截面收益率
  10.         df_close = get_price(stockList, date, dateList[dateList.index(date)+1], 'daily', ['close'])
  11.         if df_close.empty:
  12.             continue
  13.         df_pchg=df_close['close'].iloc[-1,:]/df_close['close'].iloc[0,:]-1
  14.         R_T['pchg'] = df_pchg
  15.         #获取因子数据
  16.         factor_data = factorData[date]
  17.         R_T[Fields] = factor_data[Fields]
  18.         R_T = R_T.dropna()
  19.         X = R_T[Fields]
  20.         y = R_T['pchg']   
  21.         # WLS回归
  22.         wls = sm.OLS(y, X)
  23.         result = wls.fit()
  24.         tvalues = result.tvalues
  25.         params = result.params
  26.         r2 = result.rsquared_adj
  27.         result_t[date] = tvalues
  28.         result_params[date] = params
  29.         result_r2.append(r2)
  30.     result = pd.DataFrame()
  31.     result['t 统计量'] = result_t.mean(axis = 1)
  32.     result['因子收益率'] = result_params.mean(axis = 1)
  33.     print "调整 r2 值: ", np.nanmean(result_r2)

  34.     return result.T
  35. # 对多因子模型进行回归
  36. Fields = ["size", "beta", "momentum", "residual_volatility", "non_linear_size", "HighOpen", "CloseLow", "VwapClose"]
  37. LRData(factorData_M, Fields)
复制代码



调整 r2 值:  0.0683815804623


yb19082210.png
上表展示的结果为三个以月为调仓期的因子在加入市值、贝塔、动量、残差波动率以及非线性市值这 5 个因子后,各个因子的 t 统计量以及对应的因子收益率,根据表中结果可知,size、non_linear_size 以及 residual_volatility 对股票收益贡献最大,其次是因子 VwapClose。



4.2 纯多头组合


参考前面分析的模型,可分为原始模型(5 因子模型)以及添加不同因子的改进模型,基于 2013 - 2018 年这 5 年的数据,对每个模型,通过等权的方式,实现对未来收益的预测,并筛选出预测收益最高的 100 只股票,构建相对应的组合,下面对每个模型的组合进行分析。

  1. def GetPchg_100(factorData, Field, begin_date, end_date):
  2.     dateList = get_period_date('M',begin_date, end_date)
  3.     pchg = []
  4.     # 交易手续费
  5.     cost = 0.002
  6.     for date in dateList[:-1]:
  7.         tempData = factorData[date].copy()
  8.         # 各因子等权
  9.         temp = -tempData["size"] - tempData["beta"] +  tempData["momentum"] - tempData["residual_volatility"] - tempData["non_linear_size"]
  10.         if Field == "HighOpen":
  11.             temp += -tempData["HighOpen"]
  12.         if Field == "CloseLow":
  13.             temp += tempData["CloseLow"]
  14.         if Field == "VwapClose":
  15.             temp += tempData["VwapClose"]
  16.         # 因子排序
  17.         temp.sort(ascending = False)
  18.         stockList = list(temp.index)
  19.         # 获取前 100 只股票作为股票池
  20.         stocks = stockList[:100]
  21.         df_close = get_price(stocks, date, dateList[dateList.index(date)+1], 'daily', ['close'])['close']
  22.         pchg.append(np.mean(df_close.iloc[-1] / df_close.iloc[0] - 1) - cost)
  23.     # 风险收益指标计算
  24.     netValue = 1
  25.     for i in pchg:
  26.         netValue *= 1+i
  27.     totalRet = netValue - 1
  28.     annRet = pow(1+totalRet, 0.2) - 1
  29.     winRate = len([i for i in pchg if i>0]) / float(len(pchg))
  30.     volati = np.std(pchg)
  31.     SR = (annRet - 0.04) / volati
  32.     return [totalRet, annRet, volati, winRate, SR]

  33. Fields = ["Origin", "HighOpen", "CloseLow", "VwapClose"]
  34. result = pd.DataFrame(index = Fields, columns = ["TotalRet", "AnnRet", "Vola", "WinRate", "SR"])

  35. for field in Fields:
  36.     result.loc[field,:] = GetPchg_100(factorData_M, field, begin_date, end_date)
  37. result
复制代码




yb19082211.png
上表展示了不同模型的组合收益情况,从表中可知,在加入因子 HighOpen 或者因子 VwapClose 后,模型收益能力和风险控制能力相比原始模型均得到了较大提升。加入因子 HighOpen 对模型收益贡献最大,且对应模型的波动性最低,使得模型的夏普比率在所有模型中最大。



结论


海通金工本篇报告的研究主要测试了三种价格类因子的选股效果,即 HighOpen、CloseLow 和 VwapClose。 本文通过上述分析,得到了以下结论:
(1)通过对这三种价格类因子的因子有效性分析结果来看,三种因子均有较好的选股能力,针对不同的观察期,月度因子比半月度因子具有更强的选股能力,但是相对而言,这三种因子的分层回测单调性不足;
(2)通过将这三种因子与行业、市值、贝塔、动量、残差波动率以及非线性市值这 6 个因子进行正交化处理,分析结果可知正交化因子预测稳定性得到较大提升,因子的分层回测单调性得到增强;
(3)对多因子模型进行回测分析,分别从横截面收益率回归和纯多头组合这两方面进行分析,根据分析结果来看,在加入因子 HighOpen 或者因子 VwapClose 后,模型收益能力和风险控制能力相比原始模型均得到了较大提升。

本研报转自:聚宽



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 免费注册
关注微信