前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >研报复制(六):行业轮动的黄金律

研报复制(六):行业轮动的黄金律

作者头像
量化小白
发布2019-10-21 14:36:04
1.5K0
发布2019-10-21 14:36:04
举报

本文是对方正报告《行业轮动的黄金律:日内动量与隔夜反转》的复制,欢迎指正!

背景

标的:申万一级行业/中信一级行业

调仓频率:周度/月度

区间: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
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 量化小白躺平记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 复制结果
  • 代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档