本文是对开源金工报告《A股行业动量的精细结构》的复制,欢迎指正!
报告主要观点如下
其中关于动量效应和行业动量纵向切割的部分,已经在上一篇中复制过,本文复制报告关于行业动量的横向切割部分。
需要说明的几点是:
测试时直接取最佳参数lambda = 60%,不做参数寻优。并将牵引力因子和黄金律因子、传统行业动量因子做对比分析。
IC均值4%,ICIR较低,因子稳定性一般。
与黄金律因子、传统动量因子的对比分析
其中,f为牵引力因子,其他四个因子沿用上一篇中的符号定义,M0表示日内动量因子、M1为隔夜反转因子、M为M0,M1打分和生成的黄金律因子。从上图可以看出,牵引力因子IC和ICIR都高于其他因子。
整体来看,牵引力因子和传统动量因子、黄金律因子的相关性都很低,说明可能会有一定的信息增益。更细致的增益分析可以进行因子正交化和famamacbeth回归,偷懒不做啦。
另外动量因子一般在更短周期上会表现更好,所以如果放在周频上可能会有提升,有兴趣的童鞋可以自己测一下。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime
zxcode = ['CI005001.INDX','CI005002.INDX','CI005003.INDX','CI005004.INDX','CI005005.INDX',
'CI005006.INDX','CI005007.INDX','CI005008.INDX','CI005009.INDX','CI005010.INDX',
'CI005011.INDX','CI005012.INDX','CI005013.INDX','CI005014.INDX','CI005015.INDX',
'CI005016.INDX','CI005017.INDX','CI005018.INDX','CI005019.INDX','CI005020.INDX',
'CI005021.INDX','CI005022.INDX','CI005023.INDX','CI005024.INDX','CI005025.INDX',
'CI005026.INDX','CI005027.INDX','CI005028.INDX','CI005029.INDX','CI005030.INDX']
swcode = ['801010.INDX','801020.INDX','801030.INDX','801040.INDX','801050.INDX','801080.INDX',
'801110.INDX','801120.INDX','801130.INDX','801140.INDX','801150.INDX','801160.INDX',
'801170.INDX','801180.INDX','801210.INDX','801230.INDX','801720.INDX','801730.INDX',
'801740.INDX','801750.INDX','801760.INDX','801770.INDX','801780.INDX','801790.INDX',
'801880.INDX','801890.INDX']
startdate = datetime.date(2005,12,25)
enddate = datetime.date(2019,12,31)
zxclass = get_industry_mapping(source='citics', market='cn')
zxclass = zxclass.first_industry_name.unique().tolist()
indexname = all_instruments(type='INDX', market='cn')
zxname = pd.DataFrame(zxcode,columns = ['classcode'])
zxname = pd.merge(zxname,indexname,left_on = ['classcode'],right_on = ['order_book_id'],how = 'left')
zxname = zxname[['classcode','symbol']]
zxname['classname'] = zxname.symbol.apply(lambda x:x[3:])
pricezx = get_price(zxcode, start_date=startdate, end_date=enddate,fields = ['open','close'],expect_df = True)
pricezx = pricezx.reset_index()
pricezx.columns = ['classcode','tradedate','s_dq_open','s_dq_close']
# 中信行业价格
pricezx = pd.merge(pricezx,zxname,left_on = ['classcode'],right_on = ['classcode'])
pricezx = pricezx.drop(['classcode','symbol'],axis = 1)
pricezx.head()
# 月度交易日序列
alldates = get_trading_dates(startdate, enddate)
monthdate = pd.DataFrame(alldates,columns = ['tradedate'])
monthdate['ym'] = monthdate.tradedate.apply(lambda x:x.year*100 + x.month)
monthdate = monthdate.groupby('ym').last()
monthdate = monthdate.tradedate.tolist()
monthdate[:5]
def getFactor(dateend,ind,lambdas,indtype = 'zx'):
datestart = get_previous_trading_date(dateend,20,market='cn')
# 获取行业成分股
if indtype == 'zx':
slist = get_industry(ind, source='citics', date = dateend, market='cn')
else:
slist = index_components(ind, date=dateend,market='cn')
# 计算涨跌幅
price_in = get_price(slist, start_date=datestart, end_date=datestart,fields = ['close'],expect_df = True)
price_out = get_price(slist, start_date=dateend, end_date=dateend,fields = ['close'],expect_df = True)
price = pd.concat([price_in,price_out],axis = 1)
ret = price_out.reset_index(level = 1).close/price_in.reset_index(level = 1).close - 1
ret = pd.DataFrame(ret)
ret.columns = ['ret']
# 获取成分股过去一个月的成交额
samount = get_price(slist, start_date=datestart, end_date=datestart,fields = ['total_turnover'],expect_df = True)
# 累加成交量
tot_amt = samount.reset_index().groupby(['order_book_id']).sum().reset_index()
tot_amt = tot_amt.set_index(['order_book_id'])
tot_amt.columns = ['amt']
# 判断龙头股和非龙头股
tot_amt = tot_amt.sort_values(by = 'amt',ascending = False)
tot_amt['percent'] =tot_amt.cumsum()/tot_amt.sum()
tot_amt['stocktype'] = 'L'
tot_amt.loc[tot_amt.percent > max(tot_amt.percent[0],lambdas),'stocktype'] = 'S'
# 合并
retv = pd.merge(ret,tot_amt,left_index = True,right_index = True)
# 切割计算因子
f = retv.ret.groupby(retv.stocktype).mean()
fvalue = f['L'] - f['S']
return [dateend,ind,fvalue]
allfzx = []
lambdas = 0.6
for ind in zxclass:
print(ind + ' 开始!')
for i in range(1,len(monthdate)):
dateend = monthdate[i]
fzx = getFactor(dateend,ind,lambdas,indtype = 'zx')
allfzx.append(fzx)
print("{} 完成!".format(ind))
allfzx = pd.DataFrame(allfzx,columns = ['tradedate','classname','f'])
allfzx.head()
def getRet(price,freq ='m',if_shift = True):
price = price.copy()
if freq == 'w':
price['weeks'] = price['tradedate'].apply(lambda x:x.isocalendar()[0]*100 + x.isocalendar()[1])
ret = price.groupby(['weeks','classname']).last().reset_index()
del ret['weeks']
elif freq =='m':
price['ym'] = price.tradedate.apply(lambda x:x.year*100 + x.month)
ret = price.groupby(['ym','classname']).last().reset_index()
del ret['ym']
ret = ret[['tradedate','classname','s_dq_close']]
if if_shift:
ret = ret.groupby('classname').apply(lambda x:x.set_index('tradedate').s_dq_close.pct_change(1).shift(-1))
else:
ret = ret.groupby('classname').apply(lambda x:x.set_index('tradedate').s_dq_close.pct_change(1))
ret = ret.reset_index()
ret['tradedate'] = ret['tradedate'].apply(lambda x:x.date())
ret = ret.rename(columns = {ret.columns[2]:'ret'})
return ret
def getICSeries(factors,ret,method):
# method = 'spearman';factors = fall.copy();
icall = pd.DataFrame()
fall = pd.merge(factors,ret,left_on = ['tradedate','classname'],right_on = ['tradedate','classname'])
icall = fall.groupby('tradedate').apply(lambda x:x.corr(method = method)['ret']).reset_index()
icall = icall.drop(['ret'],axis = 1).set_index('tradedate')
return icall
def GroupTestAllFactors(factors,ret,groups):
"""
一次性测试多个因子
"""
fnames = factors.columns
fall = pd.merge(factors,ret,left_on = ['classname','tradedate'],right_on = ['classname','tradedate'])
Groupret = []
Groupic = []
for f in fnames: # f= fnames[2]
if ((f != 'classname')&(f != 'tradedate')):
fuse = fall[['classname','tradedate','ret',f]]
fuse['groups'] = fuse[f].groupby(fuse.tradedate).apply(lambda x:np.ceil(x.rank()/(len(x)/groups)))
result = fuse.groupby(['tradedate','groups']).apply(lambda x:x.ret.mean())
result = result.unstack().reset_index()
result.insert(0,'factor',f)
Groupret.append(result)
# print(f)
Groupret = pd.concat(Groupret,axis = 0).reset_index(drop = True)
Groupret = Groupret.fillna(0)
Groupnav = Groupret.iloc[:,2:].groupby(Groupret.factor).apply(lambda x:(1 + x).cumprod())
Groupnav = pd.concat([Groupret[['tradedate','factor']],Groupnav],axis = 1)
return Groupnav
开源证券:A股行业动量的精细结构——市场微观结构研究系列(4)