前言
本篇是量化系列的第一篇文章。《量化十万个为什么》系列旨在讨论一些自己心中的疑问,并且通过尝试解答这些问题来提升自己对于市场的认知水平。
PS:博主水平很辣鸡,请大家轻喷,多多指教!
一、为什么提这个问题?
前面一篇文章讨论了对于所有个股来说,跑赢指数的分位数大概是多少,接下来我们看一看,对于行业指数来说,分位数是多少,并且对于行业轮动的周期一般又是多少。
二、分析
我们从选取申万一级作为行业指数,时间选取2015年至2020年
# 获取申万一级的所有代码
start_date = '20150101'
end_date = '20201231'
index_dic = index_manager.GetTotalIndexDic()
index_df = index_manager.GetTotalIndexInfo(type='DataFrame')
sw_df = index_df[index_df['IndexClassName'] == '申万一级指数']# 从网上下载数据
data_dic= {}
for index_code in sw_df['IndexStockName']:tmp_code = index_code[0:6]index_dic[index_code].PrintMyself()df = index_manager.Internet.GetSWIndex(tmp_code,start_date=start_date, end_date=end_date,data_type='Day')data_dic.update({index_code:df})time.sleep(10)
(PS:这里有一个申万一级命名的小bug,申万一级的休闲服务等同于申万一级的餐饮旅游,代码:801210.SW;申万一级的电子等同于申万一级的电子元器件,代码:801080.SW)
同时还要注意下,申万一级在2016年4月18日和2016年4月19日是没有数据的(原因未知),可以使用tick数据按照当时的分类来计算。
然后来讨论这样子一个问题,假如你开了上帝视角,获得了未来一段时间内所有的一级行业的走势,理论上说,在T+1的规则下,我们应该每天换入涨幅最高的那个行业,但是这个对于现实操作没有任何指导意义(我们研究的目的还是在行业指数和benchmark之间找到某些周期性的规律,或者说找到相对平稳的时间序列)。无论如何,我们先来看一看,之前个股的结论在行业指数上市一个什么效果。
# 从 tushare 上获取 沪深300 数据
benchmark_df = pro.index_daily(ts_code='399300.SZ', start_date=start_date, end_date=end_date)
benchmark_df = benchmark_df.sort_values(by='trade_date').reset_index(drop=True)
benchmark_date_se = pd.to_datetime(benchmark_df['trade_date'].astype(str))
benchmark_date_se.name = 'datetime'
benchmark_df.index = benchmark_date_sebenchmark_price = benchmark_df['close']test_day_num_lst = [1,2,3,5,10,20,30]
answer_dic = {}
for past_day_num in test_day_num_lst:benchmark_price = benchmark_df['close']benchmark_return = (benchmark_price - benchmark_price.shift(past_day_num))/benchmark_price.shift(past_day_num)total_answer_df = pd.DataFrame(index=benchmark_date_se)for stock_name in data_dic.keys():stock_df = copy.copy(data_dic[stock_name])close_price = stock_df['Close'].astype(float)stock_return = (close_price - close_price.shift(past_day_num))/close_price.shift(past_day_num)# 把结果合并比较tmp_df = pd.DataFrame(columns=['result','stockreturn','bmreturn'],index=benchmark_date_se)tmp_df['bmreturn'] = benchmark_returntmp_df.loc[stock_return.index,'stockreturn'] = stock_returnstock_higher_index = tmp_df[tmp_df['stockreturn'] > tmp_df['bmreturn']].indexstock_lower_index = tmp_df[tmp_df['bmreturn'] > tmp_df['stockreturn']].indextmp_df.loc[stock_higher_index,'result'] = 1tmp_df.loc[stock_lower_index,'result'] = 0total_answer_df[stock_name] = tmp_df['result']total_merge_df = copy.copy(total_answer_df).Ttmp_se = total_merge_df.fillna(0).sum()/(len(total_merge_df) - total_merge_df.isnull().sum())answer_dic.update({past_day_num:tmp_se})
结果如下:
持仓周期 | 均值 | 方差 |
---|---|---|
1 日 | 0.493 | 0.253 |
2 日 | 0.49 | 0.254 |
3 日 | 0.488 | 0.256 |
5 日 | 0.485 | 0.255 |
10 日 | 0.472 | 0.251 |
20 日 | 0.458 | 0.26 |
30 日 | 0.444 | 0.252 |
30日概率如下图
接着我们来考虑策略,假设我们要尽量减少换仓次数,拉长持仓周期,对于跑赢指数这个目标来说,一般而言有 0.4*28 = 11.2个行业指数可以跑赢沪深300,因此我们假设,对于一定周期的持仓来说,只要return在前10名的,即为优秀,我们看一下优秀行业的持续时间有多少。
我们以过去20天的return取横向取rank,先看看效果怎么样:
past_day_num = 20
total_df = pd.DataFrame(index=benchmark_df.index)
for stock_name in data_dic.keys():stock_df = copy.copy(data_dic[stock_name])close_price = stock_df['Close'].astype(float)stock_return = (close_price - close_price.shift(past_day_num))/close_price.shift(past_day_num)total_df[stock_name] = stock_return
rank_df = total_df.rank(axis=1,ascending=False)
我们取两个行业来看看,分别是: 纺织服装(801130.SW)和 银行 (801780.SW)
可以看到,在某些时间段,行业轮动还是非常明显的,例如在2015年前半年,纺织服装就明显地要强于银行。我们把指数画出来看看。
从指数的走势上看,纺织服装确实在2015年非常地强势,到了2017年开始才慢慢变熊。从直观上判断,这种行业周期要持续好几年,但是这个结论显然不是我们想要的,毕竟我们很少做几年的持仓。
为了找出短时间内行业轮动的周期,我们对前面求得的rank_df做傅里叶变换,求出一个大致的行业周期。我们来看一看,计算机和纺织服装的傅里叶变换
我们尝试取最高能量的前3名,做加权平均,求周期
from scipy.fftpack import fft,ifft
period_dic = {}
for stock_name in rank_df.columns:se = rank_df[stock_name]y = (se.dropna()).values#归一化处理yf1=abs(fft(y))/((len(y)/2)) #由于对称性,只取一半区间yf2 = yf1[range(int(len(y)/2))] xf = np.arange(len(y)) xf2 = xf[range(int(len(y)/2))]threshold = np.mean(yf2) + 1*np.std(yf2)df = pd.DataFrame({'X':xf2[1:],'Y':yf2[1:]})tmp_df = df.sort_values(by='Y',ascending=False).iloc[0:3].reset_index()ave_x = np.sum(tmp_df['X']*tmp_df['Y'])/tmp_df['Y'].sum()T = len(yf2)/ave_xperiod_dic.update({stock_name:T})
结果如下:
行业名称 | 行业代码 | 周期(日) |
---|---|---|
纺织服装 | 801130.SW | 146.49 |
公用事业 | 801160.SW | 63.52 |
国防军工 | 801740.SW | 28.78 |
综合 | 801230.SW | 35.16 |
银行 | 801780.SW | 45.38 |
传媒 | 801760.SW | 56.46 |
钢铁 | 801040.SW | 66.43 |
有色金属 | 801050.SW | 47.65 |
机械设备 | 801890.SW | 61.85 |
房地产 | 801180.SW | 34.69 |
建筑装饰 | 801720.SW | 106.87 |
电气设备 | 801730.SW | 53.4 |
非银金融 | 801790.SW | 59.16 |
轻工制造 | 801140.SW | 53.63 |
建筑材料 | 801710.SW | 72.39 |
家用电器 | 801110.SW | 53.54 |
汽车 | 801880.SW | 46.61 |
食品饮料 | 801120.SW | 49.89 |
农林牧渔 | 801010.SW | 70.96 |
商业贸易 | 801200.SW | 110.26 |
化工 | 801030.SW | 53.01 |
交通运输 | 801170.SW | 53.66 |
通信 | 801770.SW | 46.93 |
医药生物 | 801150.SW | 64.58 |
采掘 | 801020.SW | 61.08 |
计算机 | 801750.SW | 37.17 |
休闲服务 | 801210.SW | 122.88 |
电子 | 801080.SW | 61.95 |
总结
由上面的表格可以看出,可以看到,行业轮动的周期,实际上相差还是蛮大的,最快的和最慢的差了有差不多5倍之多。而且从傅里叶变换的图形来看,频域上的图形并没有特别突出的某几根线。因此这个结论是否有实践意义,还有待后面进一步的验证。