本文是对方正报告《行业轮动的黄金律:日内动量与隔夜反转》的复制,欢迎指正!
背景
标的:申万一级行业/中信一级行业
调仓频率:周度/月度
区间:2006.1-2017.11
原文是对申万一级行业做的,这里对申万、中信都测了一下, 频率上原文是月频,这里分别测了月频和周频,时间区间同研报
每期初,根据因子值,平均分为5组,看每组的收益情况,这里一共有四个因子:
- 传统动量因子mom15:15日涨跌幅
- 日内动量因子M0:每日收盘价和开盘价算的收益率,15日合成
- 隔夜反转因子M1:今开和昨收计算的收益率,15日合成
- M:M0和M1的rank求和
这里需要说明的是,合成上,报告说的比较模糊,这里我是用复利累乘的方法合成的,累加效果差不多。
从报告结论来看,M明显优于传统动量因子,我自己测试结果来看,首先,申万一级指数上M确实要优于传统动量因子,但中信行业上只有月度上有增强,周度增强不是很明显。此外M0和M1中,起作用的主要是M0,如果用每月的收益率做动量,效果明显好于15日的情况,最后,周频效果好于月频,量价因子衰减很快。
复制结果
- 申万一级行业-月度-M0
- 申万一级行业-月度-M1
- 申万一级行业-月度-M
- 申万一级行业-月度-mom15
周度这里只给出M和mom15的结果
- 申万一级行业-月度-M
- 申万一级行业-周度-mom15
多空组合的夏普、回撤、年化收益如下,超额是策略相对于行业等权的结果
限于篇幅,中信一级行业只给出表格统计了
代码
代码语言:javascript复制datas = pd.read_csv('中信一级行业指数日度行情序列.csv',encoding = 'gbk')
#datas = pd.read_csv('申万一级行业指数日度行情序列.csv',encoding = 'gbk')
# 计算日内收益率
# 计算收益率
datas['mom15'] = datas.s_dq_close.groupby(datas.classname).apply(lambda x:x.pct_change(15))
datas['ret_in_day'] = datas.s_dq_close/datas.s_dq_open - 1
ret_in_day = datas[['tradedate','classname','ret_in_day']].copy()
ret_in_day['tradedate'] = ret_in_day.tradedate.apply(getdate)
#ret_in_day.to_csv('中信一级行业指数日度日内收益率.csv',index = False,encoding = 'gbk')
# 计算隔夜收益率
ret_after_day = datas.groupby('classname').apply(ret_after_days).T
ret_after_day = ret_after_day.stack(dropna = False)
ret_after_day = ret_after_day.reset_index()
ret_after_day = ret_after_day.rename(columns = {ret_after_day.columns[2]:'ret_after_day'})
ret_after_day['tradedate'] = ret_after_day.tradedate.apply(getdate)
datas['tradedate'] = datas.tradedate.apply(getdate)
ret_w = getRet(datas,freq ='w',if_shift = True)
ret_m = getRet(datas,freq ='m',if_shift = True)
mom15 = datas[['tradedate','classname','mom15']].copy()
factors = pd.merge(ret_in_day,ret_after_day,left_on = ['tradedate','classname'],right_on = ['tradedate','classname'])
factors = pd.merge(factors,mom15,left_on = ['tradedate','classname'],right_on = ['tradedate','classname'])
factors['log_ret_in_day'] = np.log(factors.ret_in_day 1)
factors['log_ret_after_day'] = np.log(factors.ret_after_day 1)
factors['M0'] = factors.log_ret_in_day.groupby(factors.classname).apply(lambda x: x.rolling(15).sum())
factors['M1'] = factors.log_ret_after_day.groupby(factors.classname).apply(lambda x:x.rolling(15).sum())
# 转换为正常收益率
factors['M0'] = np.exp(factors.M0) - 1
factors['M1'] = np.exp(factors.M1) - 1
factors['score_inday'] = factors.M0.groupby(factors.tradedate).rank()
factors['score_afterday'] = (-factors.M1).groupby(factors.tradedate).rank()
factors['M'] = factors['score_inday'] factors['score_afterday']
factors = factors.drop(['log_ret_in_day','log_ret_after_day','ret_in_day','ret_after_day','score_inday','score_afterday'],axis = 1)
groups = 5
startdate = datetime.date(2006,1,1)
enddate = datetime.date(2017,11,30)
f1 = factors.loc[(factors.tradedate>= startdate) &(factors.tradedate <= enddate)].reset_index(drop = True).copy()
nav_w,ic_w = GroupTestAllFactors(f1,ret_w,groups)
nav_m,ic_m = GroupTestAllFactors(f1,ret_m,groups)
nav_w.loc[nav_w.factor == 'M0'].set_index('tradedate').plot(figsize = (10,5))
nav_w.loc[nav_w.factor == 'M1'].set_index('tradedate').plot(figsize = (10,5))
nav_w.loc[nav_w.factor == 'M'].set_index('tradedate').plot(figsize = (10,5))
nav_w.loc[nav_w.factor == 'mom15'].set_index('tradedate').plot(figsize = (10,5))
nav_m.loc[nav_m.factor == 'M0'].set_index('tradedate').plot(figsize = (10,5))
nav_m.loc[nav_m.factor == 'M1'].set_index('tradedate').plot(figsize = (10,5))
nav_m.loc[nav_m.factor == 'M'].set_index('tradedate').plot(figsize = (10,5))
nav_m.loc[nav_m.factor == 'mom15'].set_index('tradedate').plot(figsize = (10,5))
num = 5
fp_w,Gnav_w = LSTestAllFactors(f1,ret_w,num,'w')
fp_m,Gnav_m = LSTestAllFactors(f1,ret_m,num,'m')
函数定义
代码语言:javascript复制def getRet(price,freq ='d',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.T.stack(dropna = False).reset_index()
ret = ret.rename(columns = {ret.columns[2]:'ret'})
return ret
def getdate(x):
if type(x) == str:
return pd.Timestamp(x).date()
else:
return datetime.date(int(str(x)[:4]),int(str(x)[4:6]),int(str(x)[6:]))
# 隔夜收益率
def ret_after_days(x):
x = x.set_index('tradedate')
r = x.s_dq_open/x.s_dq_close.shift(1) - 1
return r