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

2019-10-21 14:36:04 浏览数 (1)

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

背景

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

调仓频率:周度/月度

区间: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

0 人点赞