Py学习  »  Python

【中金固收·量化】债市多因子探究——基于Python实践

中金固定收益研究 • 1 年前 • 470 次点击  


长久以来,固收+的投资者往往将超额回报的重点放在权益和转债上——毕竟这两部分更容易得到差异化的结果。但前提是权益市场收益风险比高,尤其是可以更为灵活调整的转债,能提供一些“进可攻退可守”:显然这些条件不一定时刻满足。但毕竟即便是固收+投资者,债券的仓位也相对更重。此时我们不妨也着眼于纯债本身的因子回报,这里我们进行了一系列尝试。


报告逻辑示意图

资料来源:中金公司研究部


显然,相比于股票、转债而言,债券数据的标准化程度不高、因子的说服力也可能相对弱。但我们的目标并不太高,我们希望因子模型能够帮助我们:


1)更定量化地理解市场超额回报的来源,并能帮我们也更好刻画相关产品,如普通债基;


2)在此基础上观察是否有可行的因子策略。



一、基于指数表现的因子遴选


虽然个券数据与股票相比标准性欠佳,但借助债券市场丰富的指数体系,我们可以比较简单直观地观察到债券市场的因子特征。我们将2017年以来93组中债净价指数的周度收益率进行聚类计算。聚类图除了能直观展现各指数的相关性,还能依据其相关性进行多层分类。从结果来看:


1、期限、信用和流动性是较显著的因子,如下聚类图依据此也做了各级分类;


2、期限因子的重要性较高,在关键期限如1、5、7年,都对指数的回报有明显影响;


图表1:中债各净价指数聚类图

资料来源:万得资讯,中金公司研究部


3、信用因子相较期限因子影响并没有那么大,信用债与利率债整体上会有一定差异,同时利率债内部金融债和国债(以及地方债)有明显区分;而信用债指数内部,评级的影响存疑;


4、流动性因子,相对影响微弱,实用性一般。聚类图中,农发与口行债指数往往在最后还是会与国开债指数被区分开。


图表2:各期限净价曲线(左)与金融债换手率(右)

资料来源:万得资讯,中金公司研究部



二、因子编写的具体方式


不难看出期限、信用的影响力较强,同时,我们还加入动量因子。在这个基础框架下,参照Fama-French-Carhart四因子的构建方式,以期限、信用和动量作为非市场因子,构建一个债市多因子模型。


其中:

1、期限因子LMS(LongMinusShort):我们参照平均待偿期(年)。我们先将长期限各评级加总的收益率,与短期限各评级加总的收益率相减得到【期限和信用的交叉项】;再将长期限各动量加总的收益率,与短期限各动量加总的收益率相减得到【期限和动量的交叉项】,最后将两个交叉项平均值认定为期限因子。


2、信用因子GMP(GoodMinusPoor):我们直接参照平均派息率(%)而非隐含评级,以此能够区分利率债和信用债的情况,具体算法与期限类似。


3、动量因子HMW(HighMinusWeek):我们参照指数前一个月的涨跌幅作为的动量划分。


此外,对于因子的计算,我们标的为中债各财富指数,并采用指数总市值加权


具体计算逻辑如下:



图表3:债市因子构建方式(示意图)

资料来源:中金公司研究部


图表4:债市因子构建方式(程序示意图)


def computerBondFactor(obj, startDate, momwindow=20, window=20):
    # 储存固定周期的涨跌幅数据并计算动量,以及后续我们因子构建是按照市值加权的
    startDate = pd.to_datetime(startDate).strftime('%Y/%#m/%#d')

    ret = obj.DB['CLOSE'].pct_change(window).loc[startDate::window]
    wgt = obj.DB['AMT'].loc[startDate::window]
    obj.DB['1Mmom'] = obj.DB['CLOSE'].pct_change(momwindow).shift(window + 1)

    dictRawdf = {
        'ptm': {'dfRank': obj.DB['MATURITY'].loc[startDate::window].rank(pct=True, axis=1), 'mat': [None, None, None]},
        'credit': {'dfRank': obj.DB['DIVIDEND'].loc[startDate::window].rank(pct=True, axis=1),
                   'mat': [None, None, None]},
        'mom': {'dfRank': obj.DB['1Mmom'].loc[startDate::window].rank(pct=True, axis=1), 'mat': [None, None, None]}}

     # 将每个周期依照期限、信用和动量进行三分(以30%与70%为界)
    for k, v in dictRawdf.items():
        dfRaw = v['dfRank']
        v['mat'][0] = dfRaw.applymap(lambda x: 1 if x > 0.7 else np.nan)
        v['mat'][1] = dfRaw.applymap(lambda x: 1 if (x <= 0.7) & (x >= 0.3) else np.nan)
        v['mat'][2] = dfRaw.applymap(lambda x: 1 if x < 0.3 else np.nan)

    dictFactors = {'LMS': {'keydf': 'ptm', 'comdf': ['credit', 'mom'], 'values': None},
                   'GMP': {'keydf': 'credit', 'comdf': ['ptm', 'mom'], 'values': None},
                   'HMW': {'keydf': 'mom', 'comdf': ['ptm' , 'credit'], 'values': None}}

    # 依据类似Carhart四因子方式构建期限、信用和动量三大因子(市值加权计算因子)
    for k, v in dictFactors.items():
        factorValue = None
        for num, com in enumerate(v['comdf']):
            pctDiff = None
            for i in [0, 2]:
                pct = None
                for j in [0, 1, 2]:
                    mat = dictRawdf[v['keydf']]['mat'][i] * dictRawdf[com]['mat'][j]
                    pctwgtMean = (ret * mat * wgt).sum(axis=1) / (wgt * mat).sum(axis=1)
                    pct = pctwgtMean if j == 0 else pct + pctwgtMean
                    pct.fillna(0, inplace=True)
                pctDiff = pct if i == 0 else pctDiff - pct
                pctDiff.fillna(0, inplace=True)
            factorValue = pctDiff if num == 0 else factorValue + pctDiff
        v['values'] = factorValue / 6.

    dfRet = pd.DataFrame(columns=dictFactors.keys())
    for k in dfRet.columns:
        dfRet[k] = dictFactors[k]['values']

    dfRet.index = [pd.to_datetime(d) for d in dfRet.index]

    return dfRet


资料来源:万得资讯,中金公司研究部


依据2010年以来的因子收益情况,我们观察到


1)期限因子是存在一定周期性的,信用因子长期暴露下优势相对有限,而动量因子暴露优势比较显著;


2)月度来看信用因子与期限因子存在一定正相关。而无论周度还是月度,动量因子整体与期限/信用因子存在一定负相关。


图表5:债市各主要因子收益率

资料来源:万得资讯,中金公司研究部;注:数据区间为2009年12月30日至2022年5月18日



图表6:债市各主要因子相关性情况

资料来源:万得资讯,中金公司研究部;注:数据区间为2009年12月30日至2022年5月18日



面对目前已有的因子数据,我们以下将进行两方面的探索:

1)依据债市多因子,我们可以对债基进行风险归因,并以周度或月度的频率,关注债基的行为与丰富对其的评价维度;

2)对于各因子再进行探究,挖掘因子增强的量化策略。



三、基于债市多因子的债基评价


模仿Carhart模型,我们搭建以下债市四因子模型:



其中,我们使用中债-新综合财富(总值)指数(CBA00101)的收益率,相较于一年期存款利率的超额收益,来构建市场因子。依据模型,我们对市场中几个典型的纯债产品(简称中不含“信用”),自2016年以来的月度收益,进行了风险拆解。我们观察到


1)模型对于各基金解释能力普遍较强,R-Squared基本维持到85%及以上;


2)债基在alpha层面不太明显;


3)市场因子对基金业绩解释能力较强,beta值能一定程度体现债基对纯债资产的风险暴露水平,基本与杠杆有一定挂钩;


4)债基在期限因子层面暴露相对有限,p-value呈现出来的显著值并不高。此外,基金平均而言,有做陡曲线的倾向。


5)信用因子解释力度较强,beta值均为正;而动量因子的稳定暴露较难,样本中仅有一只产品动量暴露偏显著,其收益率是全样本中最高的。


图表7:纯债基月度收益风险拆解

资料来源:万得资讯,中金公司研究部;注:样本债基选取2016年以来综合规模排名处于前列的中长期纯债基;测算周期为2016年1月4日至2022年5月18日



图表8:纯债基月度滚动(24个月)收益风险拆解

资料来源:万得资讯,中金公司研究部;注:市场/信用beta剔除当期p-value小于2%的例


而动态来看,模型整体解释力度仍不弱,此外我们观察到1)市场beta近年有所走弱;2)信用beta也处于趋缓的节奏,而且产品间差距比较大。



四、有关债市多因子量化策略的探究


以上我们主要就多因子在债基评价方面进行定量分析。而再回顾我们所拆解的多因子收益情况,实际上若对于个别因子能做好一定程度的择时,那么对于固收产品而言或许会有显著增强效果。这也是我们开篇所提到的——我们希望在债市因子层面做不同程度的暴露,以此达到另一种维度的“固收+”。以下,我们就期限、信用和动量因子的增强暴露策略做一定的尝试。



1、期限择时策略:


我们将“10年与隔夜利率差”作为基准标量,当其滚动40日均值显著低于滚动120日线,则买入短期限指数;若短均线显著高于长均线,则买入长期限指数。经过测算,策略在收益端的增厚较为显著,2012年迄今年化回报在5.16%(综合指数4.58%,加权平均综合指数4.57%),同时在子类中,该期限策略仍具备显著增强效果。


图表9:期限策略对组合的增强效果

资料来源:万得资讯,中金公司研究部;注:以上回测结果测算周期为2011年12月29日至2022年5月18日;期限切换与各平均策略以指数样本券10日结算量加权;换仓周期为40日


具体操作上,我们短均线使用40日平均,长均线使用120日平均,显著与否参照的是0.5倍120日标准差。


图表10:期限策略的代码实现


def maturityStrategySimple(obj, codes, date, tempCodes, dfAssetBook):
    # 计算10Y与隔夜利差
    srs = (obj.DB['TermStructure'][10] - obj.DB['TermStructure'][0]).loc[:date]

    srsShort = srs.rolling(40).mean().iloc[-1]
    srsLong = srs.rolling(120).mean().iloc[-1]
    srsLongstd = srs.rolling(120).std().iloc[-1]

    tempCodes = obj.DB['MATURITY'].columns
    srsMat = obj.DB['MATURITY'].loc[date, tempCodes].dropna()

    # 短均线显著低于长均线,则买入短久期指数,反之则买入长久期指数
    if  srsShort <= srsLong - srsLongstd * 0.5:
        t = srsMat[srsMat < srsMat.quantile(0.3)].index
    elif srsShort >= srsLong + srsLongstd * 0.5:
        t = srsMat[srsMat > srsMat.quantile(0.3)].index
    else:
        t = tempCodes

    return t


资料来源:中金公司研究部




2、动量因子控制策略:


对于动量因子而言,结果与我们此前报告中提及的“大级别趋势必惩、小趋势顺势可为”基本一致。因此我们在对债市做因子选择时,从进攻性上仍然偏好短期动量,而回撤端我们则考虑市场当前交易量是否处于历史相对高位。


图表11:动量策略的有效性

资料来源:万得资讯,中金公司研究部;注:2012年以来策略区间为2011年12月29日至2022年5月18日;2018年以来策略区间为2017年12月29日至2022年5月18日;策略均为21天换仓,均以各财富指数的市值加权处理


图表12:动量策略的评价结果

资料来源:万得资讯,中金公司研究部;注:2012年以来策略区间为2011年12月29日至2022年5月18日;2018年以来策略区间为2017年12月29日至2022年5月18日;策略均为21天换仓,均以各财富指数的市值加权处理


具体操作上,我们先选择动量处于前1/3的指数,在这个基础上剔除交易量处于250个交易日80%分位数及以上的。


图表13:动量控制策略的程序实现


def momStrategyMax(obj, codes, date, tempCodes, dfAssetBook):
    date = offset(obj, date)
    idx = obj.DB['CLOSE'].index.get_loc(date)

    srs = obj.DB['CLOSE'].iloc[idx - 21:idx][tempCodes].pct_change(20 ).iloc[-1].rank()
    t = srs[srs > srs.quantile(0.333)].index

    srsVol = obj.DB['VOL'].iloc[idx - 251: idx][t].rank(axis=0).iloc[-1] / 252.

    t = srsVol[srsVol < 0.8].index

    return t


资料来源:中金公司研究部




3、信用因子:


在经过期限因子与动量因子调整后,同等条件下做多高票息的策略或许是有效的。因此我们在编制信用因子相关策略时,会先基于期限因子的选择,再在其中选择高票息的,最后选剩余指数中具备动量优势的。


图表14:高票息策略的有效尝试


资料来源:万得资讯,中金公司研究部;注:上图中年化回报/波动/最大回撤单位均为%,2012年以来策略区间为2011年12月29日至2022年5月18日;2018年以来策略区间为2017年12月29日至2022年5月18日;策略均为21天换仓,均以各财富指数的市值加权处理;中债信用债指数中期限最长,隐含评级最低的中债-市场隐含评级AA信用债财富(10年以上)指数,2018年以来年化回报6.43%,Calmar为4.94x;2016年8月成立以来年化回报4.34%,Calmar为0.33x。


图表15:高票息策略的程序实现


def creditStrategy(obj, codes, date, tempCodes, dfAssetBook):
    tempCodes = maturityStrategySimple(obj, codes, date, tempCodes, dfAssetBook)

    date = offset(obj, date)

    srs = obj.DB['DIVIDEND'].loc[date, tempCodes].dropna()
    t = srs[srs > srs.quantile(0.5)].index

    return momStrategyMax(obj, codes, date, t, dfAssetBook)


资料来源:中金公司研究部



五、利用债市多因子构建的组合


以上我们所作的因子增强策略,均是以各财富指数为标的。而落实到实务层面,大部分纯债债券是没法像转债一样有标准的结构数据,以此直接做量化策略。同时个券流动性偏弱,所以个券做量化策略的效果可能也并不理想。


因此对于纯债投资者,或许可以参照以上因子增强策略在久期、信用层面的选择,落到个券上再有动量考虑;而对于能投债基的投资者,例如FOF、保险、理财等,可以找相应的指数债基操作,也可以依据因子增强策略的选择,选择在某因子上有稳定暴露的产品。以下,我们梳理在信用与动量层面有稳定暴露的纯债基产品。


图表16:信用因子暴露稳定的债基

资料来源:万得资讯,中金公司研究部


图表17:动量因子暴露相对稳定的债基

资料来源:万得资讯,中金公司研究部



风险


因子出现较大变动,货币政策出现较大转向,信用债出现超预期风险




文章来源

本文摘自:2022年5月27日已经发布的《债市多因子探究——基于Python实践


杨 冰 SAC执业证书编号:S0080515120002;SFC CE Ref: BOM868

罗 凡 SAC执业证书编号:S0080120070107

陈健恒 SAC执业证书编号:S0080511030011;SFC CE Ref: BBM220




法律声明

向上滑动参见完整法律声明及二维码


Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/135097
 
470 次点击